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