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 } else {
172 // Flex Wrapped
173 sum := innerSizes[0][0]
174 shifted := false
175 for i := 0; i < len(n.Children); i++ {
176 v := n.Children[i]
177 vState := s[v.Properties.Id]
178
179 // if the next plus current will break then
180 w := innerSizes[i][0]
181 if i > 0 {
182 sib := s[n.Children[i-1].Properties.Id]
183 if w+sum > selfWidth {
184 if maxWidths[i] > selfWidth {
185 w = selfWidth - vState.Margin.Left - vState.Margin.Right - (vState.Border.Width * 2)
186 }
187 sum = 0
188 shifted = true
189 } else {
190 if !shifted {
191 propagateOffsets(&v, vState.X, vState.Y, vState.X, sib.Y, state)
192
193 vState.Y = sib.Y
194 (*state)[v.Properties.Id] = vState
195 } else {
196 shifted = false
197 }
198 sum += w
199 }
200 }
201
202 widths = append(widths, w)
203 }
204
205 // Move the elements into the correct position
206 rows := [][]int{}
207 start := 0
208 maxH := float32(0)
209 var prevOffset float32
210 for i := 0; i < len(n.Children); i++ {
211 v := n.Children[i]
212 vState := s[v.Properties.Id]
213
214 vState.Width = widths[i]
215 xStore := vState.X
216 yStore := vState.Y
217
218 if i > 0 {
219 sib := s[n.Children[i-1].Properties.Id]
220 if vState.Y+prevOffset == sib.Y {
221 vState.Y += prevOffset
222
223 if vState.Height < sib.Height {
224 vState.Height = sib.Height
225 }
226 // Shift right if on a row with sibling
227 xStore = sib.X + sib.Width + sib.Margin.Right + sib.Border.Width + vState.Margin.Left + vState.Border.Width
228 } else {
229 // Shift under sibling
230 yStore = sib.Y + sib.Height + sib.Margin.Top + sib.Margin.Bottom + sib.Border.Width*2
231 prevOffset = yStore - vState.Y
232 rows = append(rows, []int{start, i, int(maxH)})
233 start = i
234 maxH = 0
235 }
236 propagateOffsets(&v, vState.X, vState.Y, xStore, yStore, state)
237 }
238 vState.X = xStore
239 vState.Y = yStore
240
241 (*state)[v.Properties.Id] = vState
242 deInline(&v, state)
243 applyInline(&v, state)
244 applyBlock(&v, state)
245 _, h := getInnerSize(&v, state)
246 h = utils.Max(h, vState.Height)
247 maxH = utils.Max(maxH, h)
248 vState.Height = h
249 (*state)[v.Properties.Id] = vState
250 }
251 if start < len(n.Children)-1 {
252 rows = append(rows, []int{start, len(n.Children) - 1, int(maxH)})
253 }
254 for _, v := range rows {
255 for i := v[0]; i < v[1]; i++ {
256 vState := s[n.Children[i].Properties.Id]
257 vState.Height = float32(v[2])
258 (*state)[n.Children[i].Properties.Id] = vState
259 }
260 }
261 }
262
263 // Shift to the right if reversed
264 if flexReversed {
265 last := s[n.Children[len(n.Children)-1].Properties.Id]
266 offset := (self.X + self.Width - self.Padding.Right) - (last.X + last.Width + last.Margin.Right + last.Border.Width)
267 for i, v := range n.Children {
268 vState := s[v.Properties.Id]
269 propagateOffsets(&n.Children[i], vState.X, vState.Y, vState.X+offset, vState.Y, state)
270 vState.X += offset
271
272 (*state)[v.Properties.Id] = vState
273 }
274 }
275
276 }
277
278 // Column doesn't really need a lot done bc it is basically block styling rn
279 if flexDirection == "column" && flexReversed {
280 flexReverse(n, state)
281 }
282 if n.Style["height"] == "" {
283 _, h := getInnerSize(n, state)
284 self.Height = h
285 }
286 (*state)[n.Properties.Id] = self
287 },
288 }
289}
290
291func applyBlock(n *element.Node, state *map[string]element.State) {
292 accum := float32(0)
293 inlineOffset := float32(0)
294 s := *state
295 lastHeight := float32(0)
296 baseY := s[n.Children[0].Properties.Id].Y
297 for i := 0; i < len(n.Children); i++ {
298 v := &n.Children[i]
299 vState := s[v.Properties.Id]
300
301 if v.Style["display"] != "block" {
302 vState.Y += inlineOffset
303 accum = (vState.Y - baseY)
304 lastHeight = vState.Height
305 } else if v.Style["position"] != "absolute" {
306 vState.Y += accum
307 inlineOffset += (vState.Height + (vState.Border.Width * 2) + vState.Margin.Top + vState.Margin.Bottom + vState.Padding.Top + vState.Padding.Bottom) + lastHeight
308 }
309 (*state)[v.Properties.Id] = vState
310 }
311}
312
313func deInline(n *element.Node, state *map[string]element.State) {
314 s := *state
315 // self := s[n.Properties.Id]
316 baseX := float32(-1)
317 baseY := float32(-1)
318 for _, v := range n.Children {
319 vState := s[v.Properties.Id]
320
321 if v.Style["display"] == "inline" {
322 if baseX < 0 && baseY < 0 {
323 baseX = vState.X
324 baseY = vState.Y
325 } else {
326 vState.X = baseX
327 vState.Y = baseY
328 (*state)[v.Properties.Id] = vState
329
330 }
331 } else {
332 baseX = float32(-1)
333 baseY = float32(-1)
334 }
335
336 if len(v.Children) > 0 {
337 deInline(&v, state)
338 }
339 }
340
341}
342
343func applyInline(n *element.Node, state *map[string]element.State) {
344 pl := inline.Init()
345 for i := 0; i < len(n.Children); i++ {
346 v := &n.Children[i]
347
348 if len(v.Children) > 0 {
349 applyInline(v, state)
350 }
351
352 if pl.Selector(v) {
353 pl.Handler(v, state)
354 }
355 }
356}
357
358func propagateOffsets(n *element.Node, prevx, prevy, newx, newy float32, state *map[string]element.State) {
359 s := *state
360 for _, v := range n.Children {
361 vState := s[v.Properties.Id]
362 xStore := (vState.X - prevx) + newx
363 yStore := (vState.Y - prevy) + newy
364
365 if len(v.Children) > 0 {
366 propagateOffsets(&v, vState.X, vState.Y, xStore, yStore, state)
367 }
368 vState.X = xStore
369 vState.Y = yStore
370 (*state)[v.Properties.Id] = vState
371 }
372
373}
374
375func countText(n element.Node) int {
376 count := 0
377 groups := []int{}
378 for _, v := range n.Children {
379 if v.TagName == "notaspan" {
380 count += 1
381 }
382 if v.Style["display"] == "block" {
383 groups = append(groups, count)
384 count = 0
385 }
386 if len(v.Children) > 0 {
387 count += countText(v)
388 }
389 }
390 groups = append(groups, count)
391
392 sort.Slice(groups, func(i, j int) bool {
393 return groups[i] > groups[j]
394 })
395 return groups[0]
396}
397
398func getMinWidth(n *element.Node, state *map[string]element.State) float32 {
399 s := *state
400 self := s[n.Properties.Id]
401 selfWidth := float32(0)
402
403 if len(n.Children) > 0 {
404 for _, v := range n.Children {
405 selfWidth = utils.Max(selfWidth, getNodeWidth(&v, state))
406 }
407 } else {
408 selfWidth = self.Width
409 }
410
411 selfWidth += self.Padding.Left + self.Padding.Right
412 return selfWidth
413}
414func getMaxWidth(n *element.Node, state *map[string]element.State) float32 {
415 s := *state
416 self := s[n.Properties.Id]
417 selfWidth := float32(0)
418
419 if len(n.Children) > 0 {
420 for _, v := range n.Children {
421 selfWidth += getNodeWidth(&v, state)
422 }
423 } else {
424 selfWidth = self.Width
425 }
426
427 selfWidth += self.Padding.Left + self.Padding.Right
428 return selfWidth
429}
430
431func getNodeWidth(n *element.Node, state *map[string]element.State) float32 {
432 s := *state
433 self := s[n.Properties.Id]
434 w := float32(0)
435 w += self.Padding.Left
436 w += self.Padding.Right
437
438 w += self.Margin.Left
439 w += self.Margin.Right
440
441 w += self.Width
442
443 w += self.Border.Width * 2
444
445 for _, v := range n.Children {
446 w = utils.Max(w, getNodeWidth(&v, state))
447 }
448
449 return w
450}
451
452func getInnerSize(n *element.Node, state *map[string]element.State) (float32, float32) {
453 s := *state
454 self := s[n.Properties.Id]
455
456 minx := float32(10e10)
457 maxw := float32(0)
458 miny := float32(10e10)
459 maxh := float32(0)
460 for _, v := range n.Children {
461 vState := s[v.Properties.Id]
462 minx = utils.Min(vState.X, minx)
463 miny = utils.Min(vState.Y, miny)
464 hOffset := (vState.Border.Width * 2) + vState.Margin.Top + vState.Margin.Bottom
465 wOffset := (vState.Border.Width * 2) + vState.Margin.Left + vState.Margin.Right
466 maxw = utils.Max(vState.X+vState.Width+wOffset, maxw)
467 maxh = utils.Max(vState.Y+vState.Height+hOffset, maxh)
468 }
469 w := maxw - minx
470 h := maxh - miny
471
472 // !ISSUE: this is a hack to get things moving adding 13 is random
473 w += self.Padding.Left + self.Padding.Right + 13
474 h += self.Padding.Top + self.Padding.Bottom
475 return w, h
476}
477
478func add2d(arr [][]float32, index int) float32 {
479 var sum float32
480 if len(arr) == 0 {
481 return sum
482 }
483
484 for i := 0; i < len(arr); i++ {
485 if len(arr[i]) <= index {
486 return sum
487 }
488 sum += arr[i][index]
489 }
490
491 return sum
492}
493
494func flexReverse(n *element.Node, state *map[string]element.State) {
495 s := *state
496 tempNodes := []element.Node{}
497 tempStates := []element.State{}
498 for i := len(n.Children) - 1; i >= 0; i-- {
499 tempNodes = append(tempNodes, n.Children[i])
500 tempStates = append(tempStates, s[n.Children[i].Properties.Id])
501 }
502
503 for i := 0; i < len(tempStates); i++ {
504 vState := s[n.Children[i].Properties.Id]
505 propagateOffsets(&n.Children[i], vState.X, vState.Y, vState.X, tempStates[i].Y, state)
506 vState.Y = tempStates[i].Y
507 (*state)[n.Children[i].Properties.Id] = vState
508 }
509
510 n.Children = tempNodes
511}