CStyle
daslk
# StyleSheet?(go)
# StyleTag?(go)
# GetStyles?(go)
# AddPlugin?(go)
See /cstyle/plugins/
# ComputeNodeStyle?(go)
# parseBorderShorthand?(go)
# CompleteBorder?(go)
# genTextNode?(go)
1package cstyle
2
3// package aui/goldie
4// https://pkg.go.dev/automated.sh/goldie
5// https://pkg.go.dev/automated.sh/aui
6// https://pkg.go.dev/automated.sh/oat
7
8import (
9 "fmt"
10 "gui/color"
11 "gui/element"
12 "gui/font"
13 "gui/parser"
14 "gui/utils"
15 "os"
16 "slices"
17 "sort"
18 "strings"
19)
20
21type Plugin struct {
22 Styles map[string]string
23 Level int
24 Handler func(*element.Node, *map[string]element.State)
25}
26
27type CSS struct {
28 Width float32
29 Height float32
30 StyleSheets []map[string]map[string]string
31 Plugins []Plugin
32 Document *element.Node
33}
34
35func (c *CSS) StyleSheet(path string) {
36 // Parse the CSS file
37 dat, err := os.ReadFile(path)
38 utils.Check(err)
39 styles := parser.ParseCSS(string(dat))
40
41 c.StyleSheets = append(c.StyleSheets, styles)
42}
43
44func (c *CSS) StyleTag(css string) {
45 styles := parser.ParseCSS(css)
46 c.StyleSheets = append(c.StyleSheets, styles)
47}
48
49var inheritedProps = []string{
50 "color",
51 "cursor",
52 "font",
53 "font-family",
54 "font-size",
55 "font-style",
56 "font-weight",
57 "letter-spacing",
58 "line-height",
59 "text-align",
60 "text-indent",
61 "text-justify",
62 "text-shadow",
63 "text-transform",
64 "visibility",
65 "word-spacing",
66 "display",
67}
68
69func (c *CSS) GetStyles(n element.Node) map[string]string {
70 styles := map[string]string{}
71
72 if n.Parent != nil {
73 ps := c.GetStyles(*n.Parent)
74 for _, v := range inheritedProps {
75 if ps[v] != "" {
76 styles[v] = ps[v]
77 }
78 }
79 }
80 for k, v := range n.Style {
81 styles[k] = v
82 }
83 hovered := false
84 if slices.Contains(n.ClassList.Classes, ":hover") {
85 hovered = true
86 }
87
88 for _, styleSheet := range c.StyleSheets {
89 for selector := range styleSheet {
90 // fmt.Println(selector, n.Properties.Id)
91 key := selector
92 if strings.Contains(selector, ":hover") && hovered {
93 selector = strings.Replace(selector, ":hover", "", -1)
94 }
95 if element.TestSelector(selector, &n) {
96 for k, v := range styleSheet[key] {
97 styles[k] = v
98 }
99 }
100
101 }
102 }
103
104 // !FLAG: why is this needed, the "attribute" is n.Style that should be mapped during init
105 inline := parser.ParseStyleAttribute(n.GetAttribute("style") + ";")
106 styles = utils.Merge(styles, inline)
107 // add hover and focus css events
108
109 return styles
110}
111
112func (c *CSS) AddPlugin(plugin Plugin) {
113 c.Plugins = append(c.Plugins, plugin)
114}
115
116func CheckNode(n *element.Node, state *map[string]element.State) {
117 s := *state
118 self := s[n.Properties.Id]
119
120 fmt.Println(n.TagName, n.Properties.Id)
121 fmt.Printf("ID: %v\n", n.Id)
122 fmt.Printf("Parent: %v\n", n.Parent.TagName)
123 fmt.Printf("Classes: %v\n", n.ClassList.Classes)
124 fmt.Printf("Text: %v\n", self.Text.Text)
125 fmt.Printf("X: %v, Y: %v\n", self.X, self.Y)
126 fmt.Printf("Width: %v, Height: %v\n", self.Width, self.Height)
127 fmt.Printf("Styles: %v\n", n.Style)
128 fmt.Printf("Background: %v\n", self.Background)
129 fmt.Printf("Border: %v\n\n\n", self.Border)
130}
131
132func (c *CSS) ComputeNodeStyle(n *element.Node, state *map[string]element.State) *element.Node {
133 // Head is not renderable
134 if utils.IsParent(*n, "head") {
135 return n
136 }
137 plugins := c.Plugins
138 // !FLAG: This should add to state.Style instead as the element.Node should be un effected by the engine
139 // currently this adds styles to the style attribute that the use did not explisitly set
140
141 n.Style = c.GetStyles(*n)
142 s := *state
143 self := s[n.Properties.Id]
144 parent := s[n.Parent.Properties.Id]
145
146 self.Background = color.Parse(n.Style, "background")
147 self.Border, _ = CompleteBorder(n.Style)
148
149 fs, _ := utils.ConvertToPixels(n.Style["font-size"], parent.EM, parent.Width)
150 self.EM = fs
151
152 if n.Style["display"] == "none" {
153 self.X = 0
154 self.Y = 0
155 self.Width = 0
156 self.Height = 0
157 return n
158 }
159
160 wh := utils.GetWH(*n)
161 width := wh.Width
162 height := wh.Height
163
164 x, y := parent.X, parent.Y
165
166 var top, left, right, bottom bool = false, false, false, false
167
168 m := utils.GetMP(*n, "margin")
169 p := utils.GetMP(*n, "padding")
170
171 self.Margin = m
172 self.Padding = p
173
174 if n.Style["position"] == "absolute" {
175 bas := utils.GetPositionOffsetNode(n)
176 base := s[bas.Properties.Id]
177 if n.Style["top"] != "" {
178 v, _ := utils.ConvertToPixels(n.Style["top"], self.EM, parent.Width)
179 y = v + base.Y
180 top = true
181 }
182 if n.Style["left"] != "" {
183 v, _ := utils.ConvertToPixels(n.Style["left"], self.EM, parent.Width)
184 x = v + base.X
185 left = true
186 }
187 if n.Style["right"] != "" {
188 v, _ := utils.ConvertToPixels(n.Style["right"], self.EM, parent.Width)
189 x = (base.Width - width) - v
190 right = true
191 }
192 if n.Style["bottom"] != "" {
193 v, _ := utils.ConvertToPixels(n.Style["bottom"], self.EM, parent.Width)
194 y = (base.Height - height) - v
195 bottom = true
196 }
197 } else {
198 for i, v := range n.Parent.Children {
199 if v.Properties.Id == n.Properties.Id {
200 if i-1 > 0 {
201 sib := n.Parent.Children[i-1]
202 sibling := s[sib.Properties.Id]
203 if n.Style["display"] == "inline" {
204 if sib.Style["display"] == "inline" {
205 y = sibling.Y
206 } else {
207 y = sibling.Y + sibling.Height
208 }
209 } else {
210 y = sibling.Y + sibling.Height
211 }
212 }
213 break
214 } else if n.Style["display"] != "inline" {
215 vState := s[v.Properties.Id]
216 y += vState.Margin.Top + vState.Margin.Bottom + vState.Padding.Top + vState.Padding.Bottom + vState.Height
217 }
218 }
219 }
220
221 // Display modes need to be calculated here
222
223 relPos := !top && !left && !right && !bottom
224
225 if left || relPos {
226 x += m.Left
227 }
228 if top || relPos {
229 y += m.Top
230 }
231 if right {
232 x -= m.Right
233 }
234 if bottom {
235 y -= m.Bottom
236 }
237
238 // fmt.Println(n.InnerText, len(n.Children))
239
240 if !utils.ChildrenHaveText(n) {
241 // Confirm text exists
242 if len(n.InnerText) > 0 {
243 innerWidth := width
244 innerHeight := height
245 (*state)[n.Properties.Id] = self
246 self = genTextNode(n, &innerWidth, &innerHeight, p, state)
247 width = innerWidth + p.Left + p.Right
248 height = innerHeight
249 }
250 }
251
252 self.X = x
253 self.Y = y
254 self.Width = width
255 self.Height = height
256
257 (*state)[n.Properties.Id] = self
258 (*state)[n.Parent.Properties.Id] = parent
259
260 // CheckNode(n, state)
261
262 // Call children here
263
264 var childYOffset float32
265 for i, v := range n.Children {
266 v.Parent = n
267 n.Children[i] = *c.ComputeNodeStyle(&v, state)
268 if n.Style["height"] == "" {
269 cState := s[n.Children[i].Properties.Id]
270 if n.Children[i].Style["position"] != "absolute" && cState.Y > childYOffset {
271 childYOffset = cState.Y
272 self.Height += cState.Height
273 self.Height += cState.Margin.Top
274 self.Height += cState.Margin.Bottom
275 self.Height += cState.Padding.Top
276 self.Height += cState.Padding.Bottom
277 }
278
279 }
280 }
281
282 (*state)[n.Properties.Id] = self
283
284 // Sorting the array by the Level field
285 sort.Slice(plugins, func(i, j int) bool {
286 return plugins[i].Level < plugins[j].Level
287 })
288
289 for _, v := range plugins {
290 matches := true
291 for name, value := range v.Styles {
292 if n.Style[name] != value && !(value == "*") {
293 matches = false
294 }
295 }
296 if matches {
297 v.Handler(n, state)
298 }
299 }
300
301 return n
302}
303
304func parseBorderShorthand(borderShorthand string) (element.Border, error) {
305 // Split the shorthand into components
306 borderComponents := strings.Fields(borderShorthand)
307
308 // Ensure there are at least 1 component (width or style or color)
309 if len(borderComponents) >= 1 {
310 width := "0px" // Default width
311 style := "solid"
312 borderColor := "#000000" // Default color
313
314 // Extract style and color if available
315 if len(borderComponents) >= 1 {
316 width = borderComponents[0]
317 }
318
319 // Extract style and color if available
320 if len(borderComponents) >= 2 {
321 style = borderComponents[1]
322 }
323 if len(borderComponents) >= 3 {
324 borderColor = borderComponents[2]
325 }
326
327 parsedColor, _ := color.Color(borderColor)
328
329 return element.Border{
330 Width: width,
331 Style: style,
332 Color: parsedColor,
333 Radius: "", // Default radius
334 }, nil
335 }
336
337 return element.Border{}, fmt.Errorf("invalid border shorthand format")
338}
339
340func CompleteBorder(cssProperties map[string]string) (element.Border, error) {
341 border, err := parseBorderShorthand(cssProperties["border"])
342 border.Radius = cssProperties["border-radius"]
343
344 return border, err
345}
346
347func genTextNode(n *element.Node, width, height *float32, p element.MarginPadding, state *map[string]element.State) element.State {
348 s := *state
349 self := s[n.Properties.Id]
350 parent := s[n.Parent.Properties.Id]
351
352 bold, italic := false, false
353
354 if n.Style["font-weight"] == "bold" {
355 bold = true
356 }
357
358 if n.Style["font-style"] == "italic" {
359 italic = true
360 }
361
362 if self.Text.Font == nil {
363 f, _ := font.LoadFont(n.Style["font-family"], int(self.EM), bold, italic)
364 self.Text.Font = f
365 }
366
367 letterSpacing, _ := utils.ConvertToPixels(n.Style["letter-spacing"], self.EM, *width)
368 wordSpacing, _ := utils.ConvertToPixels(n.Style["word-spacing"], self.EM, *width)
369 lineHeight, _ := utils.ConvertToPixels(n.Style["line-height"], self.EM, *width)
370 if lineHeight == 0 {
371 lineHeight = self.EM + 3
372 }
373
374 self.Text.LineHeight = int(lineHeight)
375 self.Text.WordSpacing = int(wordSpacing)
376 self.Text.LetterSpacing = int(letterSpacing)
377 wb := " "
378
379 if n.Style["word-wrap"] == "break-word" {
380 wb = ""
381 }
382
383 if n.Style["text-wrap"] == "wrap" || n.Style["text-wrap"] == "balance" {
384 wb = ""
385 }
386
387 var dt float32
388
389 if n.Style["text-decoration-thickness"] == "auto" || n.Style["text-decoration-thickness"] == "" {
390 dt = 3
391 } else {
392 dt, _ = utils.ConvertToPixels(n.Style["text-decoration-thickness"], self.EM, *width)
393 }
394
395 col := color.Parse(n.Style, "font")
396
397 self.Text.Color = col
398 self.Text.DecorationColor = color.Parse(n.Style, "decoration")
399 self.Text.Align = n.Style["text-align"]
400 self.Text.WordBreak = wb
401 self.Text.WordSpacing = int(wordSpacing)
402 self.Text.LetterSpacing = int(letterSpacing)
403 self.Text.WhiteSpace = n.Style["white-space"]
404 self.Text.DecorationThickness = int(dt)
405 self.Text.Overlined = n.Style["text-decoration"] == "overline"
406 self.Text.Underlined = n.Style["text-decoration"] == "underline"
407 self.Text.LineThrough = n.Style["text-decoration"] == "linethrough"
408 self.Text.EM = int(self.EM)
409 self.Text.Width = int(parent.Width)
410 self.Text.Text = n.InnerText
411
412 if n.Style["word-spacing"] == "" {
413 self.Text.WordSpacing = font.MeasureSpace(&self.Text)
414 }
415 if parent.Width != 0 && n.Style["display"] != "inline" && n.Style["width"] == "" {
416 *width = (parent.Width - p.Right) - p.Left
417 } else if n.Style["width"] == "" {
418 *width = utils.Max(*width, float32(font.MeasureLongest(&self)))
419 } else if n.Style["width"] != "" {
420 *width, _ = utils.ConvertToPixels(n.Style["width"], self.EM, parent.Width)
421 }
422
423 self.Text.Width = int(*width)
424 self.Width = *width
425 fmt.Println(n.TagName, n.Style["width"], *width)
426 h := font.Render(&self)
427 if n.Style["height"] == "" {
428 *height = h
429 }
430
431 return self
432
433}