stack.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package layout
   4  
   5  import (
   6  	"image"
   7  
   8  	"github.com/p9c/p9/pkg/gel/gio/op"
   9  )
  10  
  11  // Stack lays out child elements on top of each other,
  12  // according to an alignment direction.
  13  type Stack struct {
  14  	// Alignment is the direction to align children
  15  	// smaller than the available space.
  16  	Alignment Direction
  17  }
  18  
  19  // StackChild represents a child for a Stack layout.
  20  type StackChild struct {
  21  	expanded bool
  22  	widget   Widget
  23  
  24  	// Scratch space.
  25  	call op.CallOp
  26  	dims Dimensions
  27  }
  28  
  29  // Stacked returns a Stack child that is laid out with no minimum
  30  // constraints and the maximum constraints passed to Stack.Layout.
  31  func Stacked(w Widget) StackChild {
  32  	return StackChild{
  33  		widget: w,
  34  	}
  35  }
  36  
  37  // Expanded returns a Stack child with the minimum constraints set
  38  // to the largest Stacked child. The maximum constraints are set to
  39  // the same as passed to Stack.Layout.
  40  func Expanded(w Widget) StackChild {
  41  	return StackChild{
  42  		expanded: true,
  43  		widget:   w,
  44  	}
  45  }
  46  
  47  // Layout a stack of children. The position of the children are
  48  // determined by the specified order, but Stacked children are laid out
  49  // before Expanded children.
  50  func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
  51  	var maxSZ image.Point
  52  	// First lay out Stacked children.
  53  	cgtx := gtx
  54  	cgtx.Constraints.Min = image.Point{}
  55  	for i, w := range children {
  56  		if w.expanded {
  57  			continue
  58  		}
  59  		macro := op.Record(gtx.Ops)
  60  		dims := w.widget(cgtx)
  61  		call := macro.Stop()
  62  		if w := dims.Size.X; w > maxSZ.X {
  63  			maxSZ.X = w
  64  		}
  65  		if h := dims.Size.Y; h > maxSZ.Y {
  66  			maxSZ.Y = h
  67  		}
  68  		children[i].call = call
  69  		children[i].dims = dims
  70  	}
  71  	// Then lay out Expanded children.
  72  	for i, w := range children {
  73  		if !w.expanded {
  74  			continue
  75  		}
  76  		macro := op.Record(gtx.Ops)
  77  		cgtx.Constraints.Min = maxSZ
  78  		dims := w.widget(cgtx)
  79  		call := macro.Stop()
  80  		if w := dims.Size.X; w > maxSZ.X {
  81  			maxSZ.X = w
  82  		}
  83  		if h := dims.Size.Y; h > maxSZ.Y {
  84  			maxSZ.Y = h
  85  		}
  86  		children[i].call = call
  87  		children[i].dims = dims
  88  	}
  89  
  90  	maxSZ = gtx.Constraints.Constrain(maxSZ)
  91  	var baseline int
  92  	for _, ch := range children {
  93  		sz := ch.dims.Size
  94  		var p image.Point
  95  		switch s.Alignment {
  96  		case N, S, Center:
  97  			p.X = (maxSZ.X - sz.X) / 2
  98  		case NE, SE, E:
  99  			p.X = maxSZ.X - sz.X
 100  		}
 101  		switch s.Alignment {
 102  		case W, Center, E:
 103  			p.Y = (maxSZ.Y - sz.Y) / 2
 104  		case SW, S, SE:
 105  			p.Y = maxSZ.Y - sz.Y
 106  		}
 107  		stack := op.Save(gtx.Ops)
 108  		op.Offset(FPt(p)).Add(gtx.Ops)
 109  		ch.call.Add(gtx.Ops)
 110  		stack.Load()
 111  		if baseline == 0 {
 112  			if b := ch.dims.Baseline; b != 0 {
 113  				baseline = b + maxSZ.Y - sz.Y - p.Y
 114  			}
 115  		}
 116  	}
 117  	return Dimensions{
 118  		Size:     maxSZ,
 119  		Baseline: baseline,
 120  	}
 121  }
 122