Flexbox
Flexbox is a high level positioning plugin that allows for dynamic positioning of elements based apon container sizing. To learn more about flexbox refer to this MDN article, in this document we will go into the impleamentation of flexbox rather than the usage of it.
"display": "flex", "justify-content": "*", "align-content": "*", "align-items": "*", "flex-wrap": "*", "flex-direction": "*",
# Flex Properties
justify-content | align-content | align-items | flex-wrap | flex-direction |
---|---|---|---|---|
normal | normal | normal | nowrap | row |
center | center | center | wrap | column |
flex-start | flex-start | flex-start | row-reverse | |
flex-end | flex-end | flex-end | column-reverse | |
space-between | space-between | stretch | ||
space-around | space-around | baseline | ||
space-evenly | stretch |
# order?(go)
asd
1package flex
2
3import (
4 "gui/cstyle"
5 "gui/cstyle/plugins/inline"
6 "gui/element"
7 "gui/utils"
8 "sort"
9 "strings"
10)
11
12// !ISSUES: Text disapearing (i think its the inline plugin)
13// + height adjust on wrap
14// + full screen positioning issues
15
16func Init() cstyle.Plugin {
17 return cstyle.Plugin{
18 Selector: func(n *element.Node) bool {
19 styles := map[string]string{
20 "display": "flex",
21 }
22 matches := true
23 for name, value := range styles {
24 if n.Style[name] != value && !(value == "*") && n.Style[name] != "" {
25 matches = false
26 }
27 }
28 return matches
29 },
30 Level: 3,
31 Handler: func(n *element.Node, state *map[string]element.State) {
32 s := *state
33 self := s[n.Properties.Id]
34
35 verbs := strings.Split(n.Style["flex-direction"], "-")
36 flexDirection := verbs[0]
37 if flexDirection == "" {
38 flexDirection = "row"
39 }
40 flexReversed := false
41 if len(verbs) > 1 {
42 flexReversed = true
43 }
44
45 var flexWrapped bool
46 if n.Style["flex-wrap"] == "wrap" {
47 flexWrapped = true
48 } else {
49 flexWrapped = false
50 }
51
52 hAlign := n.Style["align-content"]
53 if hAlign == "" {
54 hAlign = "normal"
55 }
56 vAlign := n.Style["align-items"]
57 if vAlign == "" {
58 vAlign = "normal"
59 }
60 justify := n.Style["justify-items"]
61 if justify == "" {
62 justify = "normal"
63 }
64 // fmt.Println(flexDirection, flexReversed, flexWrapped, hAlign, vAlign, justify)
65
66 if flexDirection == "row" {
67
68 // Reverse elements
69 if flexReversed {
70 flexReverse(n, state)
71 }
72 // Get inital sizing
73 textTotal := 0
74 textCounts := []int{}
75 widths := []float32{}
76 innerSizes := [][]float32{}
77 minWidths := []float32{}
78 maxWidths := []float32{}
79 for _, v := range n.Children {
80 count := countText(v)
81 textTotal += count
82 textCounts = append(textCounts, count)
83
84 minw := getMinWidth(&v, state)
85 minWidths = append(minWidths, minw)
86
87 maxw := getMaxWidth(&v, state)
88 maxWidths = append(maxWidths, maxw)
89
90 w, h := getInnerSize(&v, state)
91 innerSizes = append(innerSizes, []float32{w, h})
92 }
93 selfWidth := (self.Width - self.Padding.Left) - self.Padding.Right
94 // if the elements are less than the size of the parent, don't change widths. Just set mins
95 if !flexWrapped {
96 if add2d(innerSizes, 0) < selfWidth {
97 for i, v := range n.Children {
98 vState := s[v.Properties.Id]
99
100 w := innerSizes[i][0]
101 w -= vState.Margin.Left + vState.Margin.Right + (vState.Border.Width * 2)
102 widths = append(widths, w)
103 }
104 } else {
105 // Modifiy the widths so they aren't under the mins
106 for i, v := range n.Children {
107 vState := s[v.Properties.Id]
108
109 w := ((selfWidth / float32(textTotal)) * float32(textCounts[i]))
110 w -= vState.Margin.Left + vState.Margin.Right + (vState.Border.Width * 2)
111
112 if w < minWidths[i] {
113 selfWidth -= minWidths[i] + vState.Margin.Left + vState.Margin.Right + (vState.Border.Width * 2)
114 textTotal -= textCounts[i]
115 textCounts[i] = 0
116 }
117
118 }
119 for i, v := range n.Children {
120 vState := s[v.Properties.Id]
121
122 w := ((selfWidth / float32(textTotal)) * float32(textCounts[i]))
123 w -= vState.Margin.Left + vState.Margin.Right + (vState.Border.Width * 2)
124 // (w!=w) is of NaN
125 if w < minWidths[i] || (w != w) {
126 w = minWidths[i]
127 }
128 widths = append(widths, w)
129 }
130 }
131 // Apply the new widths
132 fState := s[n.Children[0].Properties.Id]
133 for i, v := range n.Children {
134 vState := s[v.Properties.Id]
135
136 vState.Width = widths[i]
137 xStore := vState.X
138 if i > 0 {
139 sState := s[n.Children[i-1].Properties.Id]
140 vState.X = sState.X + sState.Width + sState.Margin.Right + vState.Margin.Left + sState.Border.Width + vState.Border.Width
141 propagateOffsets(&v, xStore, vState.Y, vState.X, fState.Y, state)
142 }
143
144 vState.Y = fState.Y
145
146 (*state)[v.Properties.Id] = vState
147 deInline(&v, state)
148 applyInline(&v, state)
149 applyBlock(&v, state)
150 }
151
152 // Set the heights based on the tallest one
153 if n.Style["height"] == "" {
154
155 innerSizes = [][]float32{}
156 for _, v := range n.Children {
157 w, h := getInnerSize(&v, state)
158 innerSizes = append(innerSizes, []float32{w, h})
159 }
160 sort.Slice(innerSizes, func(i, j int) bool {
161 return innerSizes[i][1] > innerSizes[j][1]
162 })
163 } else {
164 innerSizes[0][1] = self.Height
165 }
166 for _, v := range n.Children {
167 vState := s[v.Properties.Id]
168 vState.Height = innerSizes[0][1]
169 (*state)[v.Properties.Id] = vState
170 }
171 // Shift to the right if reversed
172 if flexReversed {
173 last := s[n.Children[len(n.Children)-1].Properties.Id]
174 offset := (self.X + self.Width - self.Padding.Right) - (last.X + last.Width + last.Margin.Right + last.Border.Width)
175 for i, v := range n.Children {
176 vState := s[v.Properties.Id]
177 propagateOffsets(&n.Children[i], vState.X, vState.Y, vState.X+offset, vState.Y, state)
178 vState.X += offset
179
180 (*state)[v.Properties.Id] = vState
181 }
182 }
183 } else {
184 // Flex Wrapped
185 sum := innerSizes[0][0]
186 for i := 0; i < len(n.Children); i++ {
187 v := n.Children[i]
188 vState := s[v.Properties.Id]
189
190 // if the next plus current will break then
191 w := innerSizes[i][0]
192 if i > 0 {
193 sib := s[n.Children[i-1].Properties.Id]
194 if maxWidths[i] > selfWidth {
195 w = selfWidth - vState.Margin.Left - vState.Margin.Right - (vState.Border.Width * 2)
196 }
197 if w+sum > selfWidth {
198 sum = w
199 } else {
200 propagateOffsets(&v, vState.X, vState.Y, vState.X, sib.Y, state)
201 vState.Y = sib.Y
202 (*state)[v.Properties.Id] = vState
203 sum += w
204 }
205 }
206
207 widths = append(widths, w)
208 }
209
210 // Move the elements into the correct position
211 rows := [][]int{}
212 start := 0
213 maxH := float32(0)
214 var prevOffset float32
215 for i := 0; i < len(n.Children); i++ {
216 v := n.Children[i]
217 vState := s[v.Properties.Id]
218
219 vState.Width = widths[i]
220 xStore := vState.X
221 yStore := vState.Y
222
223 if i > 0 {
224 sib := s[n.Children[i-1].Properties.Id]
225 if vState.Y+prevOffset == sib.Y {
226 yStore += prevOffset
227
228 if vState.Height < sib.Height {
229 vState.Height = sib.Height
230 }
231 // Shift right if on a row with sibling
232 xStore = sib.X + sib.Width + sib.Margin.Right + sib.Border.Width + vState.Margin.Left + vState.Border.Width
233 } else {
234 // Shift under sibling
235 yStore = sib.Y + sib.Height + sib.Margin.Top + sib.Margin.Bottom + sib.Border.Width*2
236 prevOffset = yStore - vState.Y
237 rows = append(rows, []int{start, i, int(maxH)})
238 start = i
239 maxH = 0
240 }
241 propagateOffsets(&v, vState.X, vState.Y, xStore, yStore, state)
242 }
243 vState.X = xStore
244 vState.Y = yStore
245
246 (*state)[v.Properties.Id] = vState
247 deInline(&v, state)
248 applyInline(&v, state)
249 applyBlock(&v, state)
250 _, h := getInnerSize(&v, state)
251 h = utils.Max(h, vState.Height)
252 maxH = utils.Max(maxH, h)
253 vState.Height = h
254 (*state)[v.Properties.Id] = vState
255 }
256 if start < len(n.Children)-1 {
257 rows = append(rows, []int{start, len(n.Children) - 1, int(maxH)})
258 }
259 for _, v := range rows {
260 for i := v[0]; i < v[1]; i++ {
261 vState := s[n.Children[i].Properties.Id]
262 vState.Height = float32(v[2])
263 (*state)[n.Children[i].Properties.Id] = vState
264 }
265 }
266 }
267
268 // Shift to the right if reversed
269 if flexReversed {
270 last := s[n.Children[len(n.Children)-1].Properties.Id]
271 offset := (self.X + self.Width - self.Padding.Right) - (last.X + last.Width + last.Margin.Right + last.Border.Width)
272 for i, v := range n.Children {
273 vState := s[v.Properties.Id]
274 propagateOffsets(&n.Children[i], vState.X, vState.Y, vState.X+offset, vState.Y, state)
275 vState.X += offset
276
277 (*state)[v.Properties.Id] = vState
278 }
279 }
280
281 }
282
283 // Column doesn't really need a lot done bc it is basically block styling rn
284 if flexDirection == "column" && flexReversed {
285 flexReverse(n, state)
286 }
287 if n.Style["height"] == "" {
288 _, h := getInnerSize(n, state)
289 self.Height = h
290 }
291 (*state)[n.Properties.Id] = self
292 },
293 }
294}
295
296func applyBlock(n *element.Node, state *map[string]element.State) {
297 accum := float32(0)
298 inlineOffset := float32(0)
299 s := *state
300 lastHeight := float32(0)
301 baseY := s[n.Children[0].Properties.Id].Y
302 for i := 0; i < len(n.Children); i++ {
303 v := &n.Children[i]
304 vState := s[v.Properties.Id]
305
306 if v.Style["display"] != "block" {
307 vState.Y += inlineOffset
308 accum = (vState.Y - baseY)
309 lastHeight = vState.Height
310 } else if v.Style["position"] != "absolute" {
311 vState.Y += accum
312 inlineOffset += (vState.Height + (vState.Border.Width * 2) + vState.Margin.Top + vState.Margin.Bottom + vState.Padding.Top + vState.Padding.Bottom) + lastHeight
313 }
314 (*state)[v.Properties.Id] = vState
315 }
316}
317
318func deInline(n *element.Node, state *map[string]element.State) {
319 s := *state
320 // self := s[n.Properties.Id]
321 baseX := float32(-1)
322 baseY := float32(-1)
323 for _, v := range n.Children {
324 vState := s[v.Properties.Id]
325
326 if v.Style["display"] == "inline" {
327 if baseX < 0 && baseY < 0 {
328 baseX = vState.X
329 baseY = vState.Y
330 } else {
331 vState.X = baseX
332 vState.Y = baseY
333 (*state)[v.Properties.Id] = vState
334
335 }
336 } else {
337 baseX = float32(-1)
338 baseY = float32(-1)
339 }
340
341 if len(v.Children) > 0 {
342 deInline(&v, state)
343 }
344 }
345
346}
347
348func applyInline(n *element.Node, state *map[string]element.State) {
349 pl := inline.Init()
350 for i := 0; i < len(n.Children); i++ {
351 v := &n.Children[i]
352
353 if len(v.Children) > 0 {
354 applyInline(v, state)
355 }
356
357 if pl.Selector(v) {
358 pl.Handler(v, state)
359 }
360 }
361}
362
363func propagateOffsets(n *element.Node, prevx, prevy, newx, newy float32, state *map[string]element.State) {
364 s := *state
365 for _, v := range n.Children {
366 vState := s[v.Properties.Id]
367 xStore := (vState.X - prevx) + newx
368 yStore := (vState.Y - prevy) + newy
369
370 if len(v.Children) > 0 {
371 propagateOffsets(&v, vState.X, vState.Y, xStore, yStore, state)
372 }
373 vState.X = xStore
374 vState.Y = yStore
375 (*state)[v.Properties.Id] = vState
376 }
377
378}
379
380func countText(n element.Node) int {
381 count := 0
382 groups := []int{}
383 for _, v := range n.Children {
384 if v.TagName == "notaspan" {
385 count += 1
386 }
387 if v.Style["display"] == "block" {
388 groups = append(groups, count)
389 count = 0
390 }
391 if len(v.Children) > 0 {
392 count += countText(v)
393 }
394 }
395 groups = append(groups, count)
396
397 sort.Slice(groups, func(i, j int) bool {
398 return groups[i] > groups[j]
399 })
400 return groups[0]
401}
402
403func getMinWidth(n *element.Node, state *map[string]element.State) float32 {
404 s := *state
405 self := s[n.Properties.Id]
406 selfWidth := float32(0)
407
408 if len(n.Children) > 0 {
409 for _, v := range n.Children {
410 selfWidth = utils.Max(selfWidth, getNodeWidth(&v, state))
411 }
412 } else {
413 selfWidth = self.Width
414 }
415
416 selfWidth += self.Padding.Left + self.Padding.Right
417 return selfWidth
418}
419func getMaxWidth(n *element.Node, state *map[string]element.State) float32 {
420 s := *state
421 self := s[n.Properties.Id]
422 selfWidth := float32(0)
423
424 if len(n.Children) > 0 {
425 for _, v := range n.Children {
426 selfWidth += getNodeWidth(&v, state)
427 }
428 } else {
429 selfWidth = self.Width
430 }
431
432 selfWidth += self.Padding.Left + self.Padding.Right
433 return selfWidth
434}
435
436func getNodeWidth(n *element.Node, state *map[string]element.State) float32 {
437 s := *state
438 self := s[n.Properties.Id]
439 w := float32(0)
440 w += self.Padding.Left
441 w += self.Padding.Right
442
443 w += self.Margin.Left
444 w += self.Margin.Right
445
446 w += self.Width
447
448 w += self.Border.Width * 2
449
450 for _, v := range n.Children {
451 w = utils.Max(w, getNodeWidth(&v, state))
452 }
453
454 return w
455}
456
457func getInnerSize(n *element.Node, state *map[string]element.State) (float32, float32) {
458 s := *state
459 self := s[n.Properties.Id]
460
461 minx := float32(10e10)
462 maxw := float32(0)
463 miny := float32(10e10)
464 maxh := float32(0)
465 for _, v := range n.Children {
466 vState := s[v.Properties.Id]
467 minx = utils.Min(vState.X, minx)
468 miny = utils.Min(vState.Y, miny)
469 hOffset := (vState.Border.Width * 2) + vState.Margin.Top + vState.Margin.Bottom
470 wOffset := (vState.Border.Width * 2) + vState.Margin.Left + vState.Margin.Right
471 maxw = utils.Max(vState.X+vState.Width+wOffset, maxw)
472 maxh = utils.Max(vState.Y+vState.Height+hOffset, maxh)
473 }
474 w := maxw - minx
475 h := maxh - miny
476
477 // !ISSUE: this is a hack to get things moving adding 13 is random
478 w += self.Padding.Left + self.Padding.Right + 13
479 h += self.Padding.Top + self.Padding.Bottom
480 return w, h
481}
482
483func add2d(arr [][]float32, index int) float32 {
484 var sum float32
485 if len(arr) == 0 {
486 return sum
487 }
488
489 for i := 0; i < len(arr); i++ {
490 if len(arr[i]) <= index {
491 return sum
492 }
493 sum += arr[i][index]
494 }
495
496 return sum
497}
498
499func flexReverse(n *element.Node, state *map[string]element.State) {
500 s := *state
501 tempNodes := []element.Node{}
502 tempStates := []element.State{}
503 for i := len(n.Children) - 1; i >= 0; i-- {
504 tempNodes = append(tempNodes, n.Children[i])
505 tempStates = append(tempStates, s[n.Children[i].Properties.Id])
506 }
507
508 for i := 0; i < len(tempStates); i++ {
509 vState := s[n.Children[i].Properties.Id]
510 propagateOffsets(&n.Children[i], vState.X, vState.Y, vState.X, tempStates[i].Y, state)
511 vState.Y = tempStates[i].Y
512 (*state)[n.Children[i].Properties.Id] = vState
513 }
514
515 n.Children = tempNodes
516}