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