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