GUI
GUI is the source package
# Open?(go)
# New?(go)
# View?(go)
# CreateNode?(go)
# extractStylesheets?(go)
# extractStyleTags?(go)
# localizePath?(go)
# encapsulateText?(go)
# matchFactory?(go)
# removeWhitespace?(go)
# removeHTMLComments?(go)
1package gui
2
3import (
4 "bufio"
5 _ "embed"
6 "gui/cstyle"
7 "gui/cstyle/plugins/block"
8 "gui/cstyle/plugins/flex"
9 "gui/cstyle/plugins/inline"
10 "gui/window"
11
12 "gui/element"
13 "gui/events"
14 "gui/utils"
15 "net/url"
16 "os"
17 "path/filepath"
18 "regexp"
19 "strconv"
20 "strings"
21
22 rl "github.com/gen2brain/raylib-go/raylib"
23 "golang.org/x/net/html"
24)
25
26// _ "net/http/pprof"
27
28//go:embed master.css
29var mastercss string
30
31type Window struct {
32 CSS cstyle.CSS
33 Document element.Node
34 Adapter func()
35}
36
37func Open(path string) Window {
38 window := New()
39
40 styleSheets, styleTags, htmlNodes := parseHTMLFromFile(path)
41
42 for _, v := range styleSheets {
43 window.CSS.StyleSheet(v)
44 }
45
46 for _, v := range styleTags {
47 window.CSS.StyleTag(v)
48 }
49
50 CreateNode(htmlNodes, &window.Document)
51
52 return window
53}
54
55func New() Window {
56 css := cstyle.CSS{
57 Width: 800,
58 Height: 450,
59 }
60
61 css.StyleTag(mastercss)
62 // This is still apart of computestyle
63 // css.AddPlugin(position.Init())
64 css.AddPlugin(inline.Init())
65 css.AddPlugin(block.Init())
66 css.AddPlugin(flex.Init())
67
68 el := element.Node{}
69 document := el.CreateElement("ROOT")
70 document.Style["width"] = "800px"
71 document.Style["height"] = "450px"
72 document.Properties.Id = "ROOT"
73 return Window{
74 CSS: css,
75 Document: document,
76 }
77}
78
79func (w *Window) Render(state map[string]element.State) []element.State {
80 flatDoc := flatten(w.Document)
81 store := []element.State{}
82
83 for _, v := range flatDoc {
84 // wont work unless state is a pointer to the og
85 // s := state[v.Properties.Id]
86 // s.RenderCount++
87 // state[v.Properties.Id] = s
88 store = append(store, state[v.Properties.Id])
89 }
90 return store
91}
92
93func flatten(n element.Node) []element.Node {
94 var nodes []element.Node
95 nodes = append(nodes, n)
96
97 children := n.Children
98 if len(children) > 0 {
99 for _, ch := range children {
100 chNodes := flatten(ch)
101 nodes = append(nodes, chNodes...)
102 }
103 }
104 return nodes
105}
106
107// !ISSUE: Need to make it skip over non modified elements
108
109func View(data *Window, width, height int32) {
110 debug := false
111 data.Document.Style["width"] = strconv.Itoa(int(width)) + "px"
112 data.Document.Style["height"] = strconv.Itoa(int(height)) + "px"
113
114 wm := window.NewWindowManager()
115 wm.FPS = true
116
117 wm.OpenWindow(width, height)
118 defer wm.CloseWindow()
119
120 evts := map[string]element.EventList{}
121
122 eventStore := &evts
123
124 state := map[string]element.State{}
125
126 state["ROOT"] = element.State{
127 Style: map[string]string{
128 "width": strconv.Itoa(int(width)) + "px",
129 "height": strconv.Itoa(int(height)) + "px",
130 },
131 }
132
133 shouldStop := false
134
135 // Main game loop
136 for !wm.WindowShouldClose() && !shouldStop {
137 rl.BeginDrawing()
138 if !shouldStop && debug {
139 shouldStop = true
140 }
141 // Check if the window size has changed
142 newWidth := int32(rl.GetScreenWidth())
143 newHeight := int32(rl.GetScreenHeight())
144
145 if newWidth != width || newHeight != height {
146 rl.ClearBackground(rl.RayWhite)
147 // Window has been resized, handle the event
148 width = newWidth
149 height = newHeight
150
151 data.CSS.Width = float32(width)
152 data.CSS.Height = float32(height)
153
154 state["ROOT"].Style["width"] = strconv.Itoa(int(width)) + "px"
155 state["ROOT"].Style["height"] = strconv.Itoa(int(height)) + "px"
156 }
157
158 eventStore = events.GetEvents(&data.Document.Children[0], &state, eventStore)
159 data.CSS.ComputeNodeStyle(&data.Document.Children[0], &state)
160 rd := data.Render(state)
161 wm.LoadTextures(rd)
162 wm.Draw(rd)
163
164 events.RunEvents(eventStore)
165
166 rl.EndDrawing()
167 }
168}
169
170func CreateNode(node *html.Node, parent *element.Node) {
171 if node.Type == html.ElementNode {
172 newNode := parent.CreateElement(node.Data)
173 for _, attr := range node.Attr {
174 if attr.Key == "class" {
175 classes := strings.Split(attr.Val, " ")
176 for _, class := range classes {
177 newNode.ClassList.Add(class)
178 }
179 } else if attr.Key == "id" {
180 newNode.Id = attr.Val
181 } else if attr.Key == "contenteditable" && (attr.Val == "" || attr.Val == "true") {
182 newNode.Properties.Editable = true
183 } else if attr.Key == "href" {
184 newNode.Href = attr.Val
185 } else if attr.Key == "src" {
186 newNode.Src = attr.Val
187 } else if attr.Key == "title" {
188 newNode.Title = attr.Val
189 } else {
190 newNode.SetAttribute(attr.Key, attr.Val)
191 }
192 }
193 newNode.InnerText = strings.TrimSpace(utils.GetInnerText(node))
194 // Recursively traverse child nodes
195 for child := node.FirstChild; child != nil; child = child.NextSibling {
196 if child.Type == html.ElementNode {
197 CreateNode(child, &newNode)
198 }
199 }
200 parent.AppendChild(newNode)
201
202 } else {
203 for child := node.FirstChild; child != nil; child = child.NextSibling {
204 if child.Type == html.ElementNode {
205 CreateNode(child, parent)
206 }
207 }
208 }
209
210}
211
212func parseHTMLFromFile(path string) ([]string, []string, *html.Node) {
213 file, _ := os.Open(path)
214 defer file.Close()
215
216 scanner := bufio.NewScanner(file)
217 var htmlContent string
218
219 for scanner.Scan() {
220 htmlContent += scanner.Text() + "\n"
221 }
222
223 htmlContent = removeHTMLComments(htmlContent)
224
225 doc, _ := html.Parse(strings.NewReader(encapsulateText(removeWhitespaceBetweenTags(htmlContent))))
226
227 // Extract stylesheet link tags and style tags
228 stylesheets := extractStylesheets(doc, filepath.Dir(path))
229 styleTags := extractStyleTags(doc)
230
231 return stylesheets, styleTags, doc
232}
233
234func extractStylesheets(n *html.Node, baseDir string) []string {
235 var stylesheets []string
236
237 var dfs func(*html.Node)
238 dfs = func(node *html.Node) {
239 if node.Type == html.ElementNode && node.Data == "link" {
240 var href string
241 isStylesheet := false
242
243 for _, attr := range node.Attr {
244 if attr.Key == "rel" && attr.Val == "stylesheet" {
245 isStylesheet = true
246 } else if attr.Key == "href" {
247 href = attr.Val
248 }
249 }
250
251 if isStylesheet {
252 resolvedHref := localizePath(baseDir, href)
253 stylesheets = append(stylesheets, resolvedHref)
254 }
255 }
256
257 for c := node.FirstChild; c != nil; c = c.NextSibling {
258 dfs(c)
259 }
260 }
261
262 dfs(n)
263 return stylesheets
264}
265
266func extractStyleTags(n *html.Node) []string {
267 var styleTags []string
268
269 var dfs func(*html.Node)
270 dfs = func(node *html.Node) {
271 if node.Type == html.ElementNode && node.Data == "style" {
272 var styleContent strings.Builder
273 for c := node.FirstChild; c != nil; c = c.NextSibling {
274 if c.Type == html.TextNode {
275 styleContent.WriteString(c.Data)
276 }
277 }
278 styleTags = append(styleTags, styleContent.String())
279 }
280
281 for c := node.FirstChild; c != nil; c = c.NextSibling {
282 dfs(c)
283 }
284 }
285
286 dfs(n)
287 return styleTags
288}
289
290func localizePath(rootPath, filePath string) string {
291 // Check if the file path has a scheme, indicating it's a URL
292 u, err := url.Parse(filePath)
293 if err == nil && u.Scheme != "" {
294 return filePath
295 }
296
297 // Join the root path and the file path to create an absolute path
298 absPath := filepath.Join(rootPath, filePath)
299
300 // If the absolute path is the same as the original path, return it
301 if absPath == filePath {
302 return filePath
303 }
304
305 return "./" + absPath
306}
307
308func encapsulateText(htmlString string) string {
309 openOpen := regexp.MustCompile(`(<\w+[^>]*>)([^<]+)(<\w+[^>]*>)`)
310 closeOpen := regexp.MustCompile(`(</\w+[^>]*>)([^<]+)(<\w+[^>]*>)`)
311 closeClose := regexp.MustCompile(`(<\/\w+[^>]*>)([^<]+)(<\/\w+[^>]*>)`)
312 a := matchFactory(openOpen)
313 t := openOpen.ReplaceAllStringFunc(htmlString, a)
314 // fmt.Println(t)
315 b := matchFactory(closeOpen)
316 u := closeOpen.ReplaceAllStringFunc(t, b)
317 // fmt.Println(u)
318 c := matchFactory(closeClose)
319 v := closeClose.ReplaceAllStringFunc(u, c)
320 // fmt.Println(v)
321 return v
322}
323
324func matchFactory(re *regexp.Regexp) func(string) string {
325 return func(match string) string {
326 submatches := re.FindStringSubmatch(match)
327 if len(submatches) != 4 {
328 return match
329 }
330
331 // Process submatches
332 if len(removeWhitespace(submatches[2])) > 0 {
333 return submatches[1] + "<notaspan>" + submatches[2] + "</notaspan>" + submatches[3]
334 } else {
335 return match
336 }
337 }
338}
339func removeWhitespace(htmlString string) string {
340 // Remove extra white space
341 reSpaces := regexp.MustCompile(`\s+`)
342 htmlString = reSpaces.ReplaceAllString(htmlString, " ")
343
344 // Trim leading and trailing white space
345 htmlString = strings.TrimSpace(htmlString)
346
347 return htmlString
348}
349
350func removeHTMLComments(htmlString string) string {
351 re := regexp.MustCompile(`<!--[\s\S]*?-->`)
352 return re.ReplaceAllString(htmlString, "")
353}
354
355// important to allow the notspans to be injected, the spaces after removing the comments cause the regexp to fail
356func removeWhitespaceBetweenTags(html string) string {
357 // Create a regular expression to match spaces between angle brackets
358 re := regexp.MustCompile(`>\s+<`)
359 // Replace all matches of spaces between angle brackets with "><"
360 return re.ReplaceAllString(html, "><")
361}