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 "fmt"
7 "gui/cstyle"
8 "gui/cstyle/plugins/block"
9 "gui/cstyle/plugins/flex"
10 "gui/cstyle/plugins/inline"
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(flex.Init())
68
69 el := element.Node{}
70 document := el.CreateElement("ROOT")
71 document.Style["width"] = "800px"
72 document.Style["height"] = "450px"
73 document.Properties.Id = "ROOT"
74 return Window{
75 CSS: css,
76 Document: document,
77 }
78}
79
80func (w *Window) Render(state map[string]element.State) []element.State {
81 flatDoc := flatten(w.Document)
82 store := []element.State{}
83
84 for _, v := range flatDoc {
85 // wont work unless state is a pointer to the og
86 // s := state[v.Properties.Id]
87 // s.RenderCount++
88 // state[v.Properties.Id] = s
89 store = append(store, state[v.Properties.Id])
90 }
91 return store
92}
93
94func flatten(n element.Node) []element.Node {
95 var nodes []element.Node
96 nodes = append(nodes, n)
97
98 children := n.Children
99 if len(children) > 0 {
100 for _, ch := range children {
101 chNodes := flatten(ch)
102 nodes = append(nodes, chNodes...)
103 }
104 }
105 return nodes
106}
107
108func View(data *Window, width, height int32) {
109 debug := false
110 data.Document.Style["width"] = strconv.Itoa(int(width)) + "px"
111 data.Document.Style["height"] = strconv.Itoa(int(height)) + "px"
112
113 wm := window.NewWindowManager()
114 wm.FPS = false
115
116 wm.OpenWindow(width, height)
117 defer wm.CloseWindow()
118
119 evts := map[string]element.EventList{}
120
121 eventStore := &evts
122
123 state := map[string]element.State{}
124
125 shouldStop := false
126
127 // Main game loop
128 for !wm.WindowShouldClose() && !shouldStop {
129 rl.BeginDrawing()
130 if !shouldStop && debug {
131 shouldStop = true
132 }
133 // Check if the window size has changed
134 newWidth := int32(rl.GetScreenWidth())
135 newHeight := int32(rl.GetScreenHeight())
136
137 if newWidth != width || newHeight != height {
138 rl.ClearBackground(rl.RayWhite)
139 // Window has been resized, handle the event
140 width = newWidth
141 height = newHeight
142
143 data.CSS.Width = float32(width)
144 data.CSS.Height = float32(height)
145
146 data.Document.Style["width"] = strconv.Itoa(int(width)) + "px"
147 data.Document.Style["height"] = strconv.Itoa(int(height)) + "px"
148 }
149
150 eventStore = events.GetEvents(&data.Document.Children[0], &state, eventStore)
151 data.CSS.ComputeNodeStyle(&data.Document.Children[0], &state)
152 rd := data.Render(state)
153 wm.LoadTextures(rd)
154 wm.Draw(rd)
155
156 events.RunEvents(eventStore)
157
158 rl.EndDrawing()
159 }
160}
161
162func CreateNode(node *html.Node, parent *element.Node) {
163 if node.Type == html.ElementNode {
164 newNode := parent.CreateElement(node.Data)
165 for _, attr := range node.Attr {
166 if attr.Key == "class" {
167 classes := strings.Split(attr.Val, " ")
168 for _, class := range classes {
169 newNode.ClassList.Add(class)
170 }
171 } else if attr.Key == "id" {
172 newNode.Id = attr.Val
173 } else if attr.Key == "contenteditable" && (attr.Val == "" || attr.Val == "true") {
174 newNode.Properties.Editable = true
175 } else if attr.Key == "href" {
176 newNode.Href = attr.Val
177 } else if attr.Key == "src" {
178 newNode.Src = attr.Val
179 } else if attr.Key == "title" {
180 newNode.Title = attr.Val
181 } else {
182 newNode.SetAttribute(attr.Key, attr.Val)
183 }
184 }
185 newNode.InnerText = strings.TrimSpace(utils.GetInnerText(node))
186 // Recursively traverse child nodes
187 for child := node.FirstChild; child != nil; child = child.NextSibling {
188 if child.Type == html.ElementNode {
189 CreateNode(child, &newNode)
190 }
191 }
192 parent.AppendChild(newNode)
193
194 } else {
195 for child := node.FirstChild; child != nil; child = child.NextSibling {
196 if child.Type == html.ElementNode {
197 CreateNode(child, parent)
198 }
199 }
200 }
201
202}
203
204func parseHTMLFromFile(path string) ([]string, []string, *html.Node) {
205 file, _ := os.Open(path)
206 defer file.Close()
207
208 scanner := bufio.NewScanner(file)
209 var htmlContent string
210
211 for scanner.Scan() {
212 htmlContent += scanner.Text() + "\n"
213 }
214
215 fmt.Println(htmlContent)
216
217 htmlContent = removeHTMLComments(htmlContent)
218
219 doc, _ := html.Parse(strings.NewReader(encapsulateText(removeWhitespaceBetweenTags(htmlContent))))
220
221 // Extract stylesheet link tags and style tags
222 stylesheets := extractStylesheets(doc, filepath.Dir(path))
223 styleTags := extractStyleTags(doc)
224
225 return stylesheets, styleTags, doc
226}
227
228func extractStylesheets(n *html.Node, baseDir string) []string {
229 var stylesheets []string
230
231 var dfs func(*html.Node)
232 dfs = func(node *html.Node) {
233 if node.Type == html.ElementNode && node.Data == "link" {
234 var href string
235 isStylesheet := false
236
237 for _, attr := range node.Attr {
238 if attr.Key == "rel" && attr.Val == "stylesheet" {
239 isStylesheet = true
240 } else if attr.Key == "href" {
241 href = attr.Val
242 }
243 }
244
245 if isStylesheet {
246 resolvedHref := localizePath(baseDir, href)
247 stylesheets = append(stylesheets, resolvedHref)
248 }
249 }
250
251 for c := node.FirstChild; c != nil; c = c.NextSibling {
252 dfs(c)
253 }
254 }
255
256 dfs(n)
257 return stylesheets
258}
259
260func extractStyleTags(n *html.Node) []string {
261 var styleTags []string
262
263 var dfs func(*html.Node)
264 dfs = func(node *html.Node) {
265 if node.Type == html.ElementNode && node.Data == "style" {
266 var styleContent strings.Builder
267 for c := node.FirstChild; c != nil; c = c.NextSibling {
268 if c.Type == html.TextNode {
269 styleContent.WriteString(c.Data)
270 }
271 }
272 styleTags = append(styleTags, styleContent.String())
273 }
274
275 for c := node.FirstChild; c != nil; c = c.NextSibling {
276 dfs(c)
277 }
278 }
279
280 dfs(n)
281 return styleTags
282}
283
284func localizePath(rootPath, filePath string) string {
285 // Check if the file path has a scheme, indicating it's a URL
286 u, err := url.Parse(filePath)
287 if err == nil && u.Scheme != "" {
288 return filePath
289 }
290
291 // Join the root path and the file path to create an absolute path
292 absPath := filepath.Join(rootPath, filePath)
293
294 // If the absolute path is the same as the original path, return it
295 if absPath == filePath {
296 return filePath
297 }
298
299 return "./" + absPath
300}
301
302func encapsulateText(htmlString string) string {
303 // !ISSUE: openOpen not correctly matching string after updating the next issue
304 openOpen := regexp.MustCompile(`(<\w+[^>]*>)([^<]+)(<\w+[^>]*>)`)
305 closeOpen := regexp.MustCompile(`(</\w+[^>]*>)([^<]+)(<\w+[^>]*>)`)
306 closeClose := regexp.MustCompile(`(<\/\w+[^>]*>)([^<]+)(<\/\w+[^>]*>)`)
307 a := matchFactory(openOpen)
308 t := openOpen.ReplaceAllStringFunc(htmlString, a)
309 // fmt.Println(t)
310 b := matchFactory(closeOpen)
311 u := closeOpen.ReplaceAllStringFunc(t, b)
312 // fmt.Println(u)
313 c := matchFactory(closeClose)
314 v := closeClose.ReplaceAllStringFunc(u, c)
315 // fmt.Println(v)
316 return v
317}
318
319func matchFactory(re *regexp.Regexp) func(string) string {
320 return func(match string) string {
321 submatches := re.FindStringSubmatch(match)
322 if len(submatches) != 4 {
323 return match
324 }
325
326 // Process submatches
327 if len(removeWhitespace(submatches[2])) > 0 {
328 return submatches[1] + "<notaspan>" + submatches[2] + "</notaspan>" + submatches[3]
329 } else {
330 return match
331 }
332 }
333}
334func removeWhitespace(htmlString string) string {
335 // Remove extra white space
336 reSpaces := regexp.MustCompile(`\s+`)
337 htmlString = reSpaces.ReplaceAllString(htmlString, " ")
338
339 // Trim leading and trailing white space
340 htmlString = strings.TrimSpace(htmlString)
341
342 return htmlString
343}
344
345func removeHTMLComments(htmlString string) string {
346 // !ISSUE: Updated from <!--(.*?)--> and broke the openOpen matching
347 re := regexp.MustCompile(`<!--[\s\S]*?-->`)
348 return re.ReplaceAllString(htmlString, "")
349}
350
351// important to allow the notspans to be injected, the spaces after removing the comments cause the regexp to fail
352func removeWhitespaceBetweenTags(html string) string {
353 // Create a regular expression to match spaces between angle brackets
354 re := regexp.MustCompile(`>\s+<`)
355 // Replace all matches of spaces between angle brackets with "><"
356 return re.ReplaceAllString(html, "><")
357}