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