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 "bytes"
6 "crypto/sha256"
7 _ "embed"
8 "encoding/json"
9 "fmt"
10 "gui/cstyle"
11 "gui/cstyle/plugins/flex"
12 "gui/cstyle/plugins/inline"
13 "gui/cstyle/plugins/textAlign"
14 "gui/cstyle/transformers/text"
15 "gui/window"
16 "time"
17
18 "gui/element"
19 "gui/events"
20 "gui/utils"
21 "net/url"
22 "os"
23 "path/filepath"
24 "regexp"
25 "strconv"
26 "strings"
27
28 rl "github.com/gen2brain/raylib-go/raylib"
29 "golang.org/x/net/html"
30)
31
32// _ "net/http/pprof"
33
34//go:embed master.css
35var mastercss string
36
37type Window struct {
38 CSS cstyle.CSS
39 Document element.Node
40 Adapter func()
41}
42
43func Open(path string) Window {
44 window := New()
45
46 styleSheets, styleTags, htmlNodes := parseHTMLFromFile(path)
47
48 for _, v := range styleSheets {
49 window.CSS.StyleSheet(v)
50 }
51
52 for _, v := range styleTags {
53 window.CSS.StyleTag(v)
54 }
55
56 CreateNode(htmlNodes, &window.Document)
57
58 return window
59}
60
61func New() Window {
62 css := cstyle.CSS{
63 Width: 800,
64 Height: 450,
65 }
66
67 css.StyleTag(mastercss)
68 // This is still apart of computestyle
69 // css.AddPlugin(position.Init())
70 css.AddPlugin(inline.Init())
71 // css.AddPlugin(block.Init())
72 css.AddPlugin(textAlign.Init())
73 // css.AddPlugin(inlineText.Init())
74 css.AddPlugin(flex.Init())
75
76 css.AddTransformer(text.Init())
77
78 el := element.Node{}
79 document := el.CreateElement("ROOT")
80 document.Style["width"] = "800px"
81 document.Style["height"] = "450px"
82 document.Properties.Id = "ROOT"
83 return Window{
84 CSS: css,
85 Document: document,
86 }
87}
88
89func (w *Window) Render(doc element.Node, state *map[string]element.State) []element.State {
90 s := *state
91
92 flatDoc := flatten(doc)
93
94 store := []element.State{}
95
96 keys := []string{}
97
98 for _, v := range flatDoc {
99 store = append(store, s[v.Properties.Id])
100 keys = append(keys, v.Properties.Id)
101 }
102
103 // Create a set of keys to keep
104 keysSet := make(map[string]struct{}, len(keys))
105 for _, key := range keys {
106 keysSet[key] = struct{}{}
107 }
108
109 // Iterate over the map and delete keys not in the set
110 for k := range s {
111 if _, found := keysSet[k]; !found {
112 delete(s, k)
113 }
114 }
115
116 return store
117}
118
119func flatten(n element.Node) []element.Node {
120 var nodes []element.Node
121 nodes = append(nodes, n)
122
123 children := n.Children
124 if len(children) > 0 {
125 for _, ch := range children {
126 chNodes := flatten(ch)
127 nodes = append(nodes, chNodes...)
128 }
129 }
130 return nodes
131}
132
133func View(data *Window, width, height int32) {
134 debug := false
135 data.Document.Style["width"] = strconv.Itoa(int(width)) + "px"
136 data.Document.Style["height"] = strconv.Itoa(int(height)) + "px"
137
138 wm := window.NewWindowManager()
139 wm.FPSCounterOn = true
140
141 wm.OpenWindow(width, height)
142 defer wm.CloseWindow()
143
144 evts := map[string]element.EventList{}
145
146 eventStore := &evts
147
148 state := map[string]element.State{}
149
150 shouldStop := false
151
152 var hash []byte
153 var rd []element.State
154
155 lastChange := time.Now()
156
157 // Main game loop
158 for !wm.WindowShouldClose() && !shouldStop {
159 // fmt.Println("######################")
160 rl.BeginDrawing()
161 if !shouldStop && debug {
162 shouldStop = true
163 }
164 // Check if the window size has changed
165 newWidth := int32(rl.GetScreenWidth())
166 newHeight := int32(rl.GetScreenHeight())
167
168 resize := false
169
170 if newWidth != width || newHeight != height {
171 resize = true
172 rl.ClearBackground(rl.RayWhite)
173 // Window has been resized, handle the event
174 width = newWidth
175 height = newHeight
176
177 data.CSS.Width = float32(width)
178 data.CSS.Height = float32(height)
179
180 data.Document.Style["width"] = strconv.Itoa(int(width)) + "px"
181 data.Document.Style["height"] = strconv.Itoa(int(height)) + "px"
182 }
183
184 newHash, _ := hashStruct(&data.Document.Children[0])
185 eventStore = events.GetEvents(&data.Document.Children[0], &state, eventStore)
186 if !bytes.Equal(hash, newHash) || resize {
187 if wm.FPS != 30 {
188 wm.SetFPS(30)
189 }
190 lastChange = time.Now()
191 hash = newHash
192 newDoc := CopyNode(data.CSS, data.Document.Children[0], &data.Document)
193
194 newDoc = data.CSS.Transform(newDoc)
195
196 data.CSS.ComputeNodeStyle(&newDoc, &state)
197 rd = data.Render(newDoc, &state)
198 wm.LoadTextures(rd)
199
200 // AddHTML(&newDoc)
201 // fmt.Println(newDoc.QuerySelector("body").InnerHTML)
202
203 AddHTML(&data.Document)
204 fmt.Println(time.Since(lastChange))
205 }
206 wm.Draw(rd)
207
208 // could use a return value that indicates whether or not a event has ran to ramp/deramp fps based on activity
209
210 events.RunEvents(eventStore)
211 // ran := events.RunEvents(eventStore)
212
213 if time.Since(lastChange) > 5*time.Second {
214 if wm.FPS != 1 {
215 wm.SetFPS(1)
216 }
217 }
218
219 rl.EndDrawing()
220 }
221}
222
223func CopyNode(c cstyle.CSS, node element.Node, parent *element.Node) element.Node {
224 n := element.Node{}
225 n.TagName = node.TagName
226 n.InnerText = node.InnerText
227 n.Style = node.Style
228 n.Id = node.Id
229 n.ClassList = node.ClassList
230 n.Href = node.Href
231 n.Src = node.Src
232 n.Title = node.Title
233 n.Attribute = node.Attribute
234 n.Value = node.Value
235 n.ScrollY = node.ScrollY
236 n.InnerHTML = node.InnerHTML
237 n.OuterHTML = node.OuterHTML
238 n.Properties.Id = node.Properties.Id
239 n.Properties.Focusable = node.Properties.Focusable
240 n.Properties.Focused = node.Properties.Focused
241 n.Properties.Editable = node.Properties.Editable
242 n.Properties.Hover = node.Properties.Hover
243 n.Properties.Selected = node.Properties.Selected
244
245 n.Parent = parent
246
247 n.Style = c.GetStyles(n)
248
249 for _, v := range node.Children {
250 n.Children = append(n.Children, CopyNode(c, v, &n))
251 }
252 return n
253}
254
255func CreateNode(node *html.Node, parent *element.Node) {
256 if node.Type == html.ElementNode {
257 newNode := parent.CreateElement(node.Data)
258 for _, attr := range node.Attr {
259 if attr.Key == "class" {
260 classes := strings.Split(attr.Val, " ")
261 for _, class := range classes {
262 newNode.ClassList.Add(class)
263 }
264 } else if attr.Key == "id" {
265 newNode.Id = attr.Val
266 } else if attr.Key == "contenteditable" && (attr.Val == "" || attr.Val == "true") {
267 newNode.Properties.Editable = true
268 } else if attr.Key == "href" {
269 newNode.Href = attr.Val
270 } else if attr.Key == "src" {
271 newNode.Src = attr.Val
272 } else if attr.Key == "title" {
273 newNode.Title = attr.Val
274 } else {
275 newNode.SetAttribute(attr.Key, attr.Val)
276 }
277 }
278 newNode.InnerText = strings.TrimSpace(utils.GetInnerText(node))
279 // Recursively traverse child nodes
280 for child := node.FirstChild; child != nil; child = child.NextSibling {
281 if child.Type == html.ElementNode {
282 CreateNode(child, &newNode)
283 }
284 }
285 parent.AppendChild(newNode)
286
287 } else {
288 for child := node.FirstChild; child != nil; child = child.NextSibling {
289 if child.Type == html.ElementNode {
290 CreateNode(child, parent)
291 }
292 }
293 }
294}
295
296func AddHTML(n *element.Node) {
297 // Head is not renderable
298 n.InnerHTML = utils.InnerHTML(*n)
299 tag, closing := utils.NodeToHTML(*n)
300 n.OuterHTML = tag + n.InnerHTML + closing
301 for i := range n.Children {
302 AddHTML(&n.Children[i])
303 }
304}
305
306func parseHTMLFromFile(path string) ([]string, []string, *html.Node) {
307 file, _ := os.Open(path)
308 defer file.Close()
309
310 scanner := bufio.NewScanner(file)
311 var htmlContent string
312
313 for scanner.Scan() {
314 htmlContent += scanner.Text() + "\n"
315 }
316
317 htmlContent = removeHTMLComments(htmlContent)
318
319 doc, _ := html.Parse(strings.NewReader(encapsulateText(removeWhitespaceBetweenTags(htmlContent))))
320
321 // Extract stylesheet link tags and style tags
322 stylesheets := extractStylesheets(doc, filepath.Dir(path))
323 styleTags := extractStyleTags(doc)
324
325 return stylesheets, styleTags, doc
326}
327
328func extractStylesheets(n *html.Node, baseDir string) []string {
329 var stylesheets []string
330
331 var dfs func(*html.Node)
332 dfs = func(node *html.Node) {
333 if node.Type == html.ElementNode && node.Data == "link" {
334 var href string
335 isStylesheet := false
336
337 for _, attr := range node.Attr {
338 if attr.Key == "rel" && attr.Val == "stylesheet" {
339 isStylesheet = true
340 } else if attr.Key == "href" {
341 href = attr.Val
342 }
343 }
344
345 if isStylesheet {
346 resolvedHref := localizePath(baseDir, href)
347 stylesheets = append(stylesheets, resolvedHref)
348 }
349 }
350
351 for c := node.FirstChild; c != nil; c = c.NextSibling {
352 dfs(c)
353 }
354 }
355
356 dfs(n)
357 return stylesheets
358}
359
360func extractStyleTags(n *html.Node) []string {
361 var styleTags []string
362
363 var dfs func(*html.Node)
364 dfs = func(node *html.Node) {
365 if node.Type == html.ElementNode && node.Data == "style" {
366 var styleContent strings.Builder
367 for c := node.FirstChild; c != nil; c = c.NextSibling {
368 if c.Type == html.TextNode {
369 styleContent.WriteString(c.Data)
370 }
371 }
372 styleTags = append(styleTags, styleContent.String())
373 }
374
375 for c := node.FirstChild; c != nil; c = c.NextSibling {
376 dfs(c)
377 }
378 }
379
380 dfs(n)
381 return styleTags
382}
383
384func localizePath(rootPath, filePath string) string {
385 // Check if the file path has a scheme, indicating it's a URL
386 u, err := url.Parse(filePath)
387 if err == nil && u.Scheme != "" {
388 return filePath
389 }
390
391 // Join the root path and the file path to create an absolute path
392 absPath := filepath.Join(rootPath, filePath)
393
394 // If the absolute path is the same as the original path, return it
395 if absPath == filePath {
396 return filePath
397 }
398
399 return "./" + absPath
400}
401
402func encapsulateText(htmlString string) string {
403 openOpen := regexp.MustCompile(`(<\w+[^>]*>)([^<]+)(<\w+[^>]*>)`)
404 closeOpen := regexp.MustCompile(`(</\w+[^>]*>)([^<]+)(<\w+[^>]*>)`)
405 closeClose := regexp.MustCompile(`(<\/\w+[^>]*>)([^<]+)(<\/\w+[^>]*>)`)
406 a := matchFactory(openOpen)
407 t := openOpen.ReplaceAllStringFunc(htmlString, a)
408 // fmt.Println(t)
409 b := matchFactory(closeOpen)
410 u := closeOpen.ReplaceAllStringFunc(t, b)
411 // fmt.Println(u)
412 c := matchFactory(closeClose)
413 v := closeClose.ReplaceAllStringFunc(u, c)
414 // fmt.Println(v)
415 return v
416}
417
418func matchFactory(re *regexp.Regexp) func(string) string {
419 return func(match string) string {
420 submatches := re.FindStringSubmatch(match)
421 if len(submatches) != 4 {
422 return match
423 }
424
425 // Process submatches
426 if len(removeWhitespace(submatches[2])) > 0 {
427 return submatches[1] + "<notaspan>" + submatches[2] + "</notaspan>" + submatches[3]
428 } else {
429 return match
430 }
431 }
432}
433func removeWhitespace(htmlString string) string {
434 // Remove extra white space
435 reSpaces := regexp.MustCompile(`\s+`)
436 htmlString = reSpaces.ReplaceAllString(htmlString, " ")
437
438 // Trim leading and trailing white space
439 htmlString = strings.TrimSpace(htmlString)
440
441 return htmlString
442}
443
444func removeHTMLComments(htmlString string) string {
445 re := regexp.MustCompile(`<!--[\s\S]*?-->`)
446 return re.ReplaceAllString(htmlString, "")
447}
448
449// important to allow the notspans to be injected, the spaces after removing the comments cause the regexp to fail
450func removeWhitespaceBetweenTags(html string) string {
451 // Create a regular expression to match spaces between angle brackets
452 re := regexp.MustCompile(`>\s+<`)
453 // Replace all matches of spaces between angle brackets with "><"
454 return re.ReplaceAllString(html, "><")
455}
456
457// Function to hash a struct using SHA-256
458func hashStruct(s interface{}) ([]byte, error) {
459 // Convert struct to JSON
460 jsonData, err := json.Marshal(s)
461 if err != nil {
462 return nil, err
463 }
464
465 // Hash the JSON data using SHA-256
466 hasher := sha256.New()
467 hasher.Write(jsonData)
468 hash := hasher.Sum(nil)
469
470 return hash, nil
471}