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