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