github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/layout/layout.go (about)

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