gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/layout/layout.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package layout 4 5 import ( 6 "image" 7 8 "gioui.org/ui" 9 ) 10 11 // Constraints represent a set of acceptable ranges for 12 // a widget's width and height. 13 type Constraints struct { 14 Width Constraint 15 Height Constraint 16 } 17 18 // Constraint is a range of acceptable sizes in a single 19 // dimension. 20 type Constraint struct { 21 Min, Max int 22 } 23 24 // Dimensions are the resolved size and baseline for a widget. 25 type Dimensions struct { 26 Size image.Point 27 Baseline int 28 } 29 30 // Axis is the Horizontal or Vertical direction. 31 type Axis uint8 32 33 // Alignment is the mutual alignment of a list of widgets. 34 type Alignment uint8 35 36 // Direction is the alignment of widgets relative to a containing 37 // space. 38 type Direction uint8 39 40 // Widget is a function scope for drawing, processing events and 41 // computing dimensions for a user interface element. 42 type Widget func() 43 44 // Context carry the state needed by almost all layouts and widgets. 45 type Context struct { 46 // Constraints track the constraints for the active widget or 47 // layout. 48 Constraints Constraints 49 // Dimensions track the result of the most recent layout 50 // operation. 51 Dimensions Dimensions 52 53 ui.Config 54 ui.Queue 55 *ui.Ops 56 } 57 58 const ( 59 Start Alignment = iota 60 End 61 Middle 62 Baseline 63 ) 64 65 const ( 66 NW Direction = iota 67 N 68 NE 69 E 70 SE 71 S 72 SW 73 W 74 Center 75 ) 76 77 const ( 78 Horizontal Axis = iota 79 Vertical 80 ) 81 82 // Layout a widget with a set of constraints and return its 83 // dimensions. The previous constraints are restored after layout. 84 func (s *Context) Layout(cs Constraints, w Widget) Dimensions { 85 saved := s.Constraints 86 s.Constraints = cs 87 s.Dimensions = Dimensions{} 88 w() 89 s.Constraints = saved 90 return s.Dimensions 91 } 92 93 // Reset the context. 94 func (c *Context) Reset(cfg ui.Config, cs Constraints) { 95 c.Constraints = cs 96 c.Dimensions = Dimensions{} 97 c.Config = cfg 98 if c.Ops == nil { 99 c.Ops = new(ui.Ops) 100 } 101 c.Ops.Reset() 102 } 103 104 // Constrain a value to the range [Min; Max]. 105 func (c Constraint) Constrain(v int) int { 106 if v < c.Min { 107 return c.Min 108 } else if v > c.Max { 109 return c.Max 110 } 111 return v 112 } 113 114 // Constrain a size to the Width and Height ranges. 115 func (c Constraints) Constrain(size image.Point) image.Point { 116 return image.Point{X: c.Width.Constrain(size.X), Y: c.Height.Constrain(size.Y)} 117 } 118 119 // RigidConstraints returns the constraints that can only be 120 // satisfied by the given dimensions. 121 func RigidConstraints(size image.Point) Constraints { 122 return Constraints{ 123 Width: Constraint{Min: size.X, Max: size.X}, 124 Height: Constraint{Min: size.Y, Max: size.Y}, 125 } 126 } 127 128 // Inset adds space around a widget. 129 type Inset struct { 130 Top, Right, Bottom, Left ui.Value 131 } 132 133 // Align aligns a widget in the available space. 134 type Align Direction 135 136 // Layout a widget. 137 func (in Inset) Layout(gtx *Context, w Widget) { 138 top := gtx.Px(in.Top) 139 right := gtx.Px(in.Right) 140 bottom := gtx.Px(in.Bottom) 141 left := gtx.Px(in.Left) 142 mcs := gtx.Constraints 143 mcs.Width.Min -= left + right 144 mcs.Width.Max -= left + right 145 if mcs.Width.Min < 0 { 146 mcs.Width.Min = 0 147 } 148 if mcs.Width.Max < mcs.Width.Min { 149 mcs.Width.Max = mcs.Width.Min 150 } 151 mcs.Height.Min -= top + bottom 152 mcs.Height.Max -= top + bottom 153 if mcs.Height.Min < 0 { 154 mcs.Height.Min = 0 155 } 156 if mcs.Height.Max < mcs.Height.Min { 157 mcs.Height.Max = mcs.Height.Min 158 } 159 var stack ui.StackOp 160 stack.Push(gtx.Ops) 161 ui.TransformOp{}.Offset(toPointF(image.Point{X: left, Y: top})).Add(gtx.Ops) 162 dims := gtx.Layout(mcs, w) 163 stack.Pop() 164 gtx.Dimensions = Dimensions{ 165 Size: gtx.Constraints.Constrain(dims.Size.Add(image.Point{X: right + left, Y: top + bottom})), 166 Baseline: dims.Baseline + top, 167 } 168 } 169 170 // UniformInset returns an Inset with a single inset applied to all 171 // edges. 172 func UniformInset(v ui.Value) Inset { 173 return Inset{Top: v, Right: v, Bottom: v, Left: v} 174 } 175 176 // Layout a widget. 177 func (a Align) Layout(gtx *Context, w Widget) { 178 var macro ui.MacroOp 179 macro.Record(gtx.Ops) 180 cs := gtx.Constraints 181 mcs := cs 182 mcs.Width.Min = 0 183 mcs.Height.Min = 0 184 dims := gtx.Layout(mcs, w) 185 macro.Stop() 186 sz := dims.Size 187 if sz.X < cs.Width.Min { 188 sz.X = cs.Width.Min 189 } 190 if sz.Y < cs.Height.Min { 191 sz.Y = cs.Height.Min 192 } 193 var p image.Point 194 switch Direction(a) { 195 case N, S, Center: 196 p.X = (sz.X - dims.Size.X) / 2 197 case NE, SE, E: 198 p.X = sz.X - dims.Size.X 199 } 200 switch Direction(a) { 201 case W, Center, E: 202 p.Y = (sz.Y - dims.Size.Y) / 2 203 case SW, S, SE: 204 p.Y = sz.Y - dims.Size.Y 205 } 206 var stack ui.StackOp 207 stack.Push(gtx.Ops) 208 ui.TransformOp{}.Offset(toPointF(p)).Add(gtx.Ops) 209 macro.Add(gtx.Ops) 210 stack.Pop() 211 gtx.Dimensions = Dimensions{ 212 Size: sz, 213 Baseline: dims.Baseline, 214 } 215 } 216 217 func (a Alignment) String() string { 218 switch a { 219 case Start: 220 return "Start" 221 case End: 222 return "End" 223 case Middle: 224 return "Middle" 225 case Baseline: 226 return "Baseline" 227 default: 228 panic("unreachable") 229 } 230 } 231 232 func (a Axis) String() string { 233 switch a { 234 case Horizontal: 235 return "Horizontal" 236 case Vertical: 237 return "Vertical" 238 default: 239 panic("unreachable") 240 } 241 } 242 243 func (d Direction) String() string { 244 switch d { 245 case NW: 246 return "NW" 247 case N: 248 return "N" 249 case NE: 250 return "NE" 251 case E: 252 return "E" 253 case SE: 254 return "SE" 255 case S: 256 return "S" 257 case SW: 258 return "SW" 259 case W: 260 return "W" 261 case Center: 262 return "Center" 263 default: 264 panic("unreachable") 265 } 266 }