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  }