gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/layout/stack.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package layout
     4  
     5  import (
     6  	"image"
     7  
     8  	"gioui.org/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  		trans := op.Offset(p).Push(gtx.Ops)
   108  		ch.call.Add(gtx.Ops)
   109  		trans.Pop()
   110  		if baseline == 0 {
   111  			if b := ch.dims.Baseline; b != 0 {
   112  				baseline = b + maxSZ.Y - sz.Y - p.Y
   113  			}
   114  		}
   115  	}
   116  	return Dimensions{
   117  		Size:     maxSZ,
   118  		Baseline: baseline,
   119  	}
   120  }
   121  
   122  // Background lays out single child widget on top of a background,
   123  // centering, if necessary.
   124  type Background struct{}
   125  
   126  // Layout a widget and then add a background to it.
   127  func (Background) Layout(gtx Context, background, widget Widget) Dimensions {
   128  	macro := op.Record(gtx.Ops)
   129  	wdims := widget(gtx)
   130  	baseline := wdims.Baseline
   131  	call := macro.Stop()
   132  
   133  	cgtx := gtx
   134  	cgtx.Constraints.Min = gtx.Constraints.Constrain(wdims.Size)
   135  	bdims := background(cgtx)
   136  
   137  	if bdims.Size != wdims.Size {
   138  		p := image.Point{
   139  			X: (bdims.Size.X - wdims.Size.X) / 2,
   140  			Y: (bdims.Size.Y - wdims.Size.Y) / 2,
   141  		}
   142  		baseline += (bdims.Size.Y - wdims.Size.Y) / 2
   143  		trans := op.Offset(p).Push(gtx.Ops)
   144  		defer trans.Pop()
   145  	}
   146  
   147  	call.Add(gtx.Ops)
   148  
   149  	return Dimensions{
   150  		Size:     bdims.Size,
   151  		Baseline: baseline,
   152  	}
   153  }