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