github.com/jmigpin/editor@v1.6.0/util/uiutil/widget/boxlayout.go (about) 1 package widget 2 3 import ( 4 "image" 5 ) 6 7 type BoxLayout struct { 8 ENode 9 YAxis bool 10 11 flex map[Node]XYAxisBoolPair // used in measure+layout, has priority over fill 12 fill map[Node]XYAxisBoolPair // used only in layout 13 } 14 15 func NewBoxLayout() *BoxLayout { 16 bl := &BoxLayout{ 17 flex: make(map[Node]XYAxisBoolPair), 18 fill: make(map[Node]XYAxisBoolPair), 19 } 20 return bl 21 } 22 23 func (bl *BoxLayout) Measure(hint image.Point) image.Point { 24 bounds := bl.childsBounds(hint, true) 25 xya := &XYAxis{bl.YAxis} 26 var max image.Point 27 for _, b := range bounds { 28 s := b.Size() 29 size := xya.Point(&s) 30 max.X += size.X 31 if size.Y > max.Y { 32 max.Y = size.Y 33 } 34 } 35 return xya.Point(&max) 36 } 37 38 func (bl *BoxLayout) Layout() { 39 bounds := bl.childsBounds(bl.Bounds.Size(), false) 40 41 // set bounds 42 bl.IterateWrappers2(func(child Node) { 43 b := bounds[child] 44 r := b.Add(bl.Bounds.Min) 45 r3 := r.Intersect(bl.Bounds) 46 child.Embed().Bounds = r3 47 }) 48 } 49 50 func (bl *BoxLayout) childsBounds(max image.Point, measure bool) map[Node]image.Rectangle { 51 xya := &XYAxis{bl.YAxis} 52 max2 := xya.Point(&max) 53 sizes := make(map[Node]image.Point, bl.ChildsLen()) 54 55 // count flex/fill 56 nFlexX := 0 57 nFillX := 0 58 var lastFlexXNode, lastFillXNode Node 59 bl.IterateWrappers2(func(child Node) { 60 bp := xya.BoolPair(bl.flex[child]) 61 if bp.X { 62 nFlexX++ 63 lastFlexXNode = child 64 } 65 if !measure { 66 bp := xya.BoolPair(bl.fill[child]) 67 if bp.X { 68 nFillX++ 69 lastFillXNode = child 70 } 71 } 72 }) 73 74 // x fills are only considered if there are no x flexes (priority for flex) 75 flexingX := nFlexX > 0 76 nX := nFlexX 77 fillingX := false 78 if !flexingX && !measure && nFillX > 0 { 79 fillingX = true 80 nX = nFillX 81 } 82 83 // measure non-flexible childs first to get remaining space 84 available := max2 85 bl.IterateWrappers2(func(child Node) { 86 bp := xya.BoolPair(bl.flex[child]) 87 bp2 := xya.BoolPair(bl.fill[child]) 88 if (!flexingX && !fillingX) || (flexingX && !bp.X) || (fillingX && !bp2.X) { 89 // flex: -X-Y 90 m0 := child.Measure(xya.Point(&available)) 91 m := xya.Point(&m0) 92 93 // flex: -X+Y 94 if bp.Y || (!measure && bp2.Y) { 95 m.Y = available.Y 96 } 97 98 sizes[child] = m 99 available.X -= m.X 100 if available.X < 0 { 101 available.X = 0 102 } 103 } 104 }) 105 106 // x flex childs 107 { 108 // divide remaining space among the flexible childs 109 share := available 110 if nX > 0 { 111 share.X = available.X / nX 112 } 113 114 // measure flexible childs 115 bl.IterateWrappers2(func(child Node) { 116 bp := xya.BoolPair(bl.flex[child]) 117 bp2 := xya.BoolPair(bl.fill[child]) 118 if (flexingX && bp.X) || (fillingX && bp2.X) { 119 var m image.Point 120 121 // flex: +X+Y 122 if bp.Y || (!measure && bp2.Y) { 123 m = share 124 } else { 125 // flex: +X-Y 126 m0 := child.Measure(xya.Point(&share)) 127 m = xya.Point(&m0) 128 m.X = share.X 129 } 130 131 // correct rounding errors on last node 132 if child == lastFlexXNode || child == lastFillXNode { 133 m.X = available.X - (share.X * (nX - 1)) 134 } 135 136 sizes[child] = m 137 } 138 }) 139 } 140 141 // setup bounds 142 bounds := make(map[Node]image.Rectangle, bl.ChildsLen()) 143 x := 0 144 bl.IterateWrappers2(func(child Node) { 145 size := sizes[child] 146 r := image.Rect(x, 0, x+size.X, size.Y) 147 bounds[child] = xya.Rectangle(&r) 148 x += size.X 149 }) 150 return bounds 151 } 152 153 func (bl *BoxLayout) SetChildFlex(node Node, x, y bool) { 154 bl.flex[node] = XYAxisBoolPair{x, y} 155 } 156 157 func (bl *BoxLayout) SetChildFill(node Node, x, y bool) { 158 bl.fill[node] = XYAxisBoolPair{x, y} 159 }