github.com/utopiagio/gio@v0.0.8/layout/flex.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package layout 4 5 import ( 6 "image" 7 8 "github.com/utopiagio/gio/op" 9 ) 10 11 // Flex lays out child elements along an axis, 12 // according to alignment and weights. 13 type Flex struct { 14 // Axis is the main axis, either Horizontal or Vertical. 15 Axis Axis 16 // Spacing controls the distribution of space left after 17 // layout. 18 Spacing Spacing 19 // Alignment is the alignment in the cross axis. 20 Alignment Alignment 21 // WeightSum is the sum of weights used for the weighted 22 // size of Flexed children. If WeightSum is zero, the sum 23 // of all Flexed weights is used. 24 WeightSum float32 25 } 26 27 // RNW Modified 28 // hflex horizontal flex style. hflex=true->Flexed(hflex child) : hflex=false->Rigid(hflex child) 29 // vflex vertical flex style. vflex=true->Flexed(vflex child) : vflex=false->Rigid(vflex child) 30 // ******************************************** 31 // FlexChild is the descriptor for a Flex child. 32 type FlexChild struct { 33 //flex bool 34 hflex bool 35 vflex bool 36 weight float32 37 38 widget Widget 39 40 // Scratch space. 41 call op.CallOp 42 dims Dimensions 43 } 44 45 // Spacing determine the spacing mode for a Flex. 46 type Spacing uint8 47 48 const ( 49 // SpaceEnd leaves space at the end. 50 SpaceEnd Spacing = iota 51 // SpaceStart leaves space at the start. 52 SpaceStart 53 // SpaceSides shares space between the start and end. 54 SpaceSides 55 // SpaceAround distributes space evenly between children, 56 // with half as much space at the start and end. 57 SpaceAround 58 // SpaceBetween distributes space evenly between children, 59 // leaving no space at the start and end. 60 SpaceBetween 61 // SpaceEvenly distributes space evenly between children and 62 // at the start and end. 63 SpaceEvenly 64 ) 65 66 // RNW Modified 67 // hflex horizontal flex style 68 // vflex vertical flex style 69 // ********************************************* 70 // FlexControl returns a Flex child with a combination of flex or rigid axis 71 func FlexControl(hflex bool, vflex bool, weight float32, widget Widget) FlexChild { 72 return FlexChild{ 73 hflex: hflex, 74 vflex: vflex, 75 weight: weight, 76 widget: widget, 77 } 78 } 79 80 // RNW Modified 81 // hflex horizontal flex style defaults to false 82 // vflex vertical flex style defaults to false 83 // ********************************************* 84 // Rigid returns a Flex child and a maximal constraint of the 85 // remaining space. 86 func Rigid(widget Widget) FlexChild { 87 return FlexChild{ 88 hflex: false, 89 vflex: false, 90 weight: 0, 91 widget: widget, 92 } 93 } 94 95 // RNW Modified 96 // hflex horizontal flex style defaults to true 97 // vflex vertical flex style defaults to true 98 // ******************************************** 99 // Flexed returns a Flex child forced to take up weight fraction of the 100 // space left over from Rigid children. The fraction is weight 101 // divided by either the weight sum of all Flexed children or the Flex 102 // WeightSum if non zero. 103 func Flexed(weight float32, widget Widget) FlexChild { 104 return FlexChild{ 105 //flex: true, 106 hflex: true, 107 vflex: true, 108 weight: weight, 109 widget: widget, 110 } 111 } 112 113 // RNW Modified to enable fixed or expanded contexts in both axis 114 // hflex horizontal flex style 115 // vflex vertical flex style 116 // if vflex == false {crossMin = 0} 117 // ********************************************** 118 // Layout a list of children. The position of the children are 119 // determined by the specified order, but Rigid children are laid out 120 // before Flexed children. 121 func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions { 122 var crossMinRigid int 123 size := 0 124 cs := gtx.Constraints 125 mainMin, mainMax := f.Axis.mainConstraint(cs) 126 crossMin, crossMax := f.Axis.crossConstraint(cs) 127 128 remaining := mainMax 129 var totalWeight float32 130 cgtx := gtx 131 // Lay out Rigid children. 132 for i, child := range children { 133 if child.hflex { 134 totalWeight += child.weight 135 continue 136 } 137 //log.Println("rigid child.idx =", i) 138 //log.Println("rigid child.hflex =", child.hflex) 139 macro := op.Record(gtx.Ops) 140 if child.vflex == false { 141 crossMinRigid = 0 142 } else { 143 crossMinRigid = crossMin 144 } 145 cgtx.Constraints = f.Axis.constraints(0, remaining, crossMinRigid, crossMax) 146 dims := child.widget(cgtx) 147 c := macro.Stop() 148 sz := f.Axis.Convert(dims.Size).X 149 size += sz 150 remaining -= sz 151 if remaining < 0 { 152 remaining = 0 153 } 154 children[i].call = c 155 children[i].dims = dims 156 } 157 if w := f.WeightSum; w != 0 { 158 totalWeight = w 159 } 160 // fraction is the rounding error from a Flex weighting. 161 var fraction float32 162 flexTotal := remaining 163 //log.Println("layout Flex......") 164 // Lay out Flexed children. 165 for i, child := range children { 166 if !child.hflex { 167 continue 168 } 169 var flexSize int 170 if remaining > 0 && totalWeight > 0 { 171 // Apply weight and add any leftover fraction from a 172 // previous Flexed. 173 childSize := float32(flexTotal) * child.weight / totalWeight 174 flexSize = int(childSize + fraction + .5) 175 fraction = childSize - float32(flexSize) 176 if flexSize > remaining { 177 flexSize = remaining 178 } 179 } 180 macro := op.Record(gtx.Ops) 181 if child.vflex == false { 182 crossMin = 0 183 } 184 cgtx.Constraints = f.Axis.constraints(flexSize, flexSize, crossMin, crossMax) 185 dims := child.widget(cgtx) 186 c := macro.Stop() 187 sz := f.Axis.Convert(dims.Size).X 188 size += sz 189 remaining -= sz 190 if remaining < 0 { 191 remaining = 0 192 } 193 children[i].call = c 194 children[i].dims = dims 195 } 196 maxCross := crossMin 197 var maxBaseline int 198 for _, child := range children { 199 if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross { 200 maxCross = c 201 } 202 if b := child.dims.Size.Y - child.dims.Baseline; b > maxBaseline { 203 maxBaseline = b 204 } 205 } 206 var space int 207 if mainMin > size { 208 space = mainMin - size 209 } 210 var mainSize int 211 switch f.Spacing { 212 case SpaceSides: 213 mainSize += space / 2 214 case SpaceStart: 215 mainSize += space 216 case SpaceEvenly: 217 mainSize += space / (1 + len(children)) 218 case SpaceAround: 219 if len(children) > 0 { 220 mainSize += space / (len(children) * 2) 221 } 222 } 223 for i, child := range children { 224 dims := child.dims 225 b := dims.Size.Y - dims.Baseline 226 var cross int 227 switch f.Alignment { 228 case End: 229 cross = maxCross - f.Axis.Convert(dims.Size).Y 230 case Middle: 231 cross = (maxCross - f.Axis.Convert(dims.Size).Y) / 2 232 case Baseline: 233 if f.Axis == Horizontal { 234 cross = maxBaseline - b 235 } 236 } 237 pt := f.Axis.Convert(image.Pt(mainSize, cross)) 238 trans := op.Offset(pt).Push(gtx.Ops) 239 child.call.Add(gtx.Ops) 240 trans.Pop() 241 mainSize += f.Axis.Convert(dims.Size).X 242 if i < len(children)-1 { 243 switch f.Spacing { 244 case SpaceEvenly: 245 mainSize += space / (1 + len(children)) 246 case SpaceAround: 247 if len(children) > 0 { 248 mainSize += space / len(children) 249 } 250 case SpaceBetween: 251 if len(children) > 1 { 252 mainSize += space / (len(children) - 1) 253 } 254 } 255 } 256 } 257 switch f.Spacing { 258 case SpaceSides: 259 mainSize += space / 2 260 case SpaceEnd: 261 mainSize += space 262 case SpaceEvenly: 263 mainSize += space / (1 + len(children)) 264 case SpaceAround: 265 if len(children) > 0 { 266 mainSize += space / (len(children) * 2) 267 } 268 } 269 sz := f.Axis.Convert(image.Pt(mainSize, maxCross)) 270 sz = cs.Constrain(sz) 271 return Dimensions{Size: sz, Baseline: sz.Y - maxBaseline} 272 } 273 274 func (s Spacing) String() string { 275 switch s { 276 case SpaceEnd: 277 return "SpaceEnd" 278 case SpaceStart: 279 return "SpaceStart" 280 case SpaceSides: 281 return "SpaceSides" 282 case SpaceAround: 283 return "SpaceAround" 284 case SpaceBetween: 285 return "SpaceAround" 286 case SpaceEvenly: 287 return "SpaceEvenly" 288 default: 289 panic("unreachable") 290 } 291 }