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
69// need to get rid of the .props for the most part all styles should be computed dynamically
70// can keep like focusable and stuff that describes the element
71
72// currently the append child does not work due to the props and other stuff not existing so it fails
73// moving to a real time style compute would fix that
74
75// :hover is parsed correctly but because the hash func doesn't invalidate it becuase the val
76// is updated in the props. change to append :hover to style to create the effect
77// or merge the class with the styles? idk have to think more
78
79func (c *CSS) GetStyles(n element.Node) map[string]string {
80 styles := map[string]string{}
81
82 if n.Parent != nil {
83 ps := c.GetStyles(*n.Parent)
84 for _, v := range inheritedProps {
85 if ps[v] != "" {
86 styles[v] = ps[v]
87 }
88 }
89 }
90 for k, v := range n.Style {
91 styles[k] = v
92 }
93 hovered := false
94 if slices.Contains(n.ClassList.Classes, ":hover") {
95 hovered = true
96 }
97
98 for _, styleSheet := range c.StyleSheets {
99 for selector := range styleSheet {
100 // fmt.Println(selector, n.Properties.Id)
101 key := selector
102 if strings.Contains(selector, ":hover") && hovered {
103 selector = strings.Replace(selector, ":hover", "", -1)
104 }
105 if element.TestSelector(selector, &n) {
106 for k, v := range styleSheet[key] {
107 styles[k] = v
108 }
109 }
110
111 }
112 }
113 inline := parser.ParseStyleAttribute(n.GetAttribute("style") + ";")
114 styles = utils.Merge(styles, inline)
115 // add hover and focus css events
116
117 return styles
118}
119
120func (c *CSS) AddPlugin(plugin Plugin) {
121 c.Plugins = append(c.Plugins, plugin)
122}
123
124func CheckNode(n *element.Node, state *map[string]element.State) {
125 s := *state
126 self := s[n.Properties.Id]
127
128 fmt.Println(n.TagName, n.Properties.Id)
129 fmt.Printf("ID: %v\n", n.Id)
130 fmt.Printf("Parent: %v\n", n.Parent.TagName)
131 fmt.Printf("Classes: %v\n", n.ClassList.Classes)
132 fmt.Printf("Text: %v\n", self.Text.Text)
133 fmt.Printf("X: %v, Y: %v\n", self.X, self.Y)
134 fmt.Printf("Width: %v, Height: %v\n", self.Width, self.Height)
135 fmt.Printf("Styles: %v\n", n.Style)
136 fmt.Printf("Background: %v\n", self.Background)
137 fmt.Printf("Border: %v\n\n\n", self.Border)
138}
139
140func (c *CSS) ComputeNodeStyle(n *element.Node, state *map[string]element.State) *element.Node {
141 // Head is not renderable
142 if utils.IsParent(*n, "head") {
143 return n
144 }
145 plugins := c.Plugins
146 n.Style = c.GetStyles(*n)
147 s := *state
148 self := s[n.Properties.Id]
149 parent := s[n.Parent.Properties.Id]
150
151 self.Background = color.Parse(n.Style, "background")
152 self.Border, _ = CompleteBorder(n.Style)
153
154 fs, _ := utils.ConvertToPixels(n.Style["font-size"], parent.EM, parent.Width)
155 self.EM = fs
156
157 if n.Style["display"] == "none" {
158 self.X = 0
159 self.Y = 0
160 self.Width = 0
161 self.Height = 0
162 return n
163 }
164
165 wh := utils.GetWH(*n)
166 width := wh.Width
167 height := wh.Height
168
169 x, y := parent.X, parent.Y
170
171 var top, left, right, bottom bool = false, false, false, false
172
173 m := utils.GetMP(*n, "margin")
174 p := utils.GetMP(*n, "padding")
175
176 self.Margin = m
177 self.Padding = p
178
179 if n.Style["position"] == "absolute" {
180 bas := utils.GetPositionOffsetNode(n)
181 base := s[bas.Properties.Id]
182 if n.Style["top"] != "" {
183 v, _ := utils.ConvertToPixels(n.Style["top"], self.EM, parent.Width)
184 y = v + base.Y
185 top = true
186 }
187 if n.Style["left"] != "" {
188 v, _ := utils.ConvertToPixels(n.Style["left"], self.EM, parent.Width)
189 x = v + base.X
190 left = true
191 }
192 if n.Style["right"] != "" {
193 v, _ := utils.ConvertToPixels(n.Style["right"], self.EM, parent.Width)
194 x = (base.Width - width) - v
195 right = true
196 }
197 if n.Style["bottom"] != "" {
198 v, _ := utils.ConvertToPixels(n.Style["bottom"], self.EM, parent.Width)
199 y = (base.Height - height) - v
200 bottom = true
201 }
202 } else {
203 for i, v := range n.Parent.Children {
204 if v.Properties.Id == n.Properties.Id {
205 if i-1 > 0 {
206 sib := n.Parent.Children[i-1]
207 sibling := s[sib.Properties.Id]
208 if n.Style["display"] == "inline" {
209 if sib.Style["display"] == "inline" {
210 y = sibling.Y
211 } else {
212 y = sibling.Y + sibling.Height
213 }
214 } else {
215 y = sibling.Y + sibling.Height
216 }
217 }
218 break
219 } else if n.Style["display"] != "inline" {
220 vState := s[v.Properties.Id]
221 y += vState.Margin.Top + vState.Margin.Bottom + vState.Padding.Top + vState.Padding.Bottom + vState.Height
222 }
223 }
224 }
225
226 // Display modes need to be calculated here
227
228 relPos := !top && !left && !right && !bottom
229
230 if left || relPos {
231 x += m.Left
232 }
233 if top || relPos {
234 y += m.Top
235 }
236 if right {
237 x -= m.Right
238 }
239 if bottom {
240 y -= m.Bottom
241 }
242
243 if len(n.Children) == 0 {
244 // Confirm text exists
245 if len(n.InnerText) > 0 {
246 innerWidth := width
247 innerHeight := height
248 (*state)[n.Properties.Id] = self
249 self = genTextNode(n, &innerWidth, &innerHeight, p, state)
250 width = innerWidth + p.Left + p.Right
251 height = innerHeight
252 }
253 }
254
255 self.X = x
256 self.Y = y
257 self.Width = width
258 self.Height = height
259
260 (*state)[n.Properties.Id] = self
261 (*state)[n.Parent.Properties.Id] = parent
262
263 // CheckNode(n, state)
264
265 // Call children here
266
267 var childYOffset float32
268 for i, v := range n.Children {
269 v.Parent = n
270 n.Children[i] = *c.ComputeNodeStyle(&v, state)
271 if n.Style["height"] == "" {
272 cState := s[n.Children[i].Properties.Id]
273 if n.Children[i].Style["position"] != "absolute" && cState.Y > childYOffset {
274 childYOffset = cState.Y
275 self.Height += cState.Height
276 self.Height += cState.Margin.Top
277 self.Height += cState.Margin.Bottom
278 self.Height += cState.Padding.Top
279 self.Height += cState.Padding.Bottom
280 }
281
282 }
283 }
284
285 (*state)[n.Properties.Id] = self
286
287 // Sorting the array by the Level field
288 sort.Slice(plugins, func(i, j int) bool {
289 return plugins[i].Level < plugins[j].Level
290 })
291
292 for _, v := range plugins {
293 matches := true
294 for name, value := range v.Styles {
295 if n.Style[name] != value && !(value == "*") {
296 matches = false
297 }
298 }
299 if matches {
300 v.Handler(n, state)
301 }
302 }
303
304 return n
305}
306
307func parseBorderShorthand(borderShorthand string) (element.Border, error) {
308 // Split the shorthand into components
309 borderComponents := strings.Fields(borderShorthand)
310
311 // Ensure there are at least 1 component (width or style or color)
312 if len(borderComponents) >= 1 {
313 width := "0px" // Default width
314 style := "solid"
315 borderColor := "#000000" // Default color
316
317 // Extract style and color if available
318 if len(borderComponents) >= 1 {
319 width = borderComponents[0]
320 }
321
322 // Extract style and color if available
323 if len(borderComponents) >= 2 {
324 style = borderComponents[1]
325 }
326 if len(borderComponents) >= 3 {
327 borderColor = borderComponents[2]
328 }
329
330 parsedColor, _ := color.Color(borderColor)
331
332 return element.Border{
333 Width: width,
334 Style: style,
335 Color: parsedColor,
336 Radius: "", // Default radius
337 }, nil
338 }
339
340 return element.Border{}, fmt.Errorf("invalid border shorthand format")
341}
342
343func CompleteBorder(cssProperties map[string]string) (element.Border, error) {
344 border, err := parseBorderShorthand(cssProperties["border"])
345 border.Radius = cssProperties["border-radius"]
346
347 return border, err
348}
349
350func genTextNode(n *element.Node, width, height *float32, p element.MarginPadding, state *map[string]element.State) element.State {
351 s := *state
352 self := s[n.Properties.Id]
353 parent := s[n.Parent.Properties.Id]
354
355 bold, italic := false, false
356
357 if n.Style["font-weight"] == "bold" {
358 bold = true
359 }
360
361 if n.Style["font-style"] == "italic" {
362 italic = true
363 }
364
365 if self.Text.Font == nil {
366 f, _ := font.LoadFont(n.Style["font-family"], int(self.EM), bold, italic)
367 self.Text.Font = f
368 }
369
370 letterSpacing, _ := utils.ConvertToPixels(n.Style["letter-spacing"], self.EM, *width)
371 wordSpacing, _ := utils.ConvertToPixels(n.Style["word-spacing"], self.EM, *width)
372 lineHeight, _ := utils.ConvertToPixels(n.Style["line-height"], self.EM, *width)
373 if lineHeight == 0 {
374 lineHeight = self.EM + 3
375 }
376
377 self.Text.LineHeight = int(lineHeight)
378 self.Text.WordSpacing = int(wordSpacing)
379 self.Text.LetterSpacing = int(letterSpacing)
380 wb := " "
381
382 if n.Style["word-wrap"] == "break-word" {
383 wb = ""
384 }
385
386 if n.Style["text-wrap"] == "wrap" || n.Style["text-wrap"] == "balance" {
387 wb = ""
388 }
389
390 var dt float32
391
392 if n.Style["text-decoration-thickness"] == "auto" || n.Style["text-decoration-thickness"] == "" {
393 dt = 2
394 } else {
395 dt, _ = utils.ConvertToPixels(n.Style["text-decoration-thickness"], self.EM, *width)
396 }
397
398 col := color.Parse(n.Style, "font")
399
400 self.Text.Color = col
401 self.Text.Align = n.Style["text-align"]
402 self.Text.WordBreak = wb
403 self.Text.WordSpacing = int(wordSpacing)
404 self.Text.LetterSpacing = int(letterSpacing)
405 self.Text.WhiteSpace = n.Style["white-space"]
406 self.Text.DecorationThickness = int(dt)
407 self.Text.Overlined = n.Style["text-decoration"] == "overline"
408 self.Text.Underlined = n.Style["text-decoration"] == "underline"
409 self.Text.LineThrough = n.Style["text-decoration"] == "linethrough"
410 self.Text.EM = int(self.EM)
411 self.Text.Width = int(parent.Width)
412 self.Text.Text = n.InnerText
413
414 if n.Style["word-spacing"] == "" {
415 self.Text.WordSpacing = font.MeasureSpace(&self.Text)
416 }
417 if parent.Width != 0 && n.Style["display"] != "inline" && n.Style["width"] == "" {
418 *width = (parent.Width - p.Right) - p.Left
419 } else if n.Style["width"] == "" {
420 *width = utils.Max(*width, float32(font.MeasureLongest(&self)))
421 } else if n.Style["width"] != "" {
422 *width, _ = utils.ConvertToPixels(n.Style["width"], self.EM, parent.Width)
423 }
424
425 self.Text.Width = int(*width)
426 h := font.Render(&self)
427 if n.Style["height"] == "" {
428 *height = h
429 }
430
431 return self
432
433}