diff --git a/ui/layout/flex.go b/ui/layout/flex.go index ba8ede47..fc39771f 100644 --- a/ui/layout/flex.go +++ b/ui/layout/flex.go @@ -10,13 +10,14 @@ import ( ) type Flex struct { - Constraints Constraints - Axis Axis MainAxisAlignment MainAxisAlignment CrossAxisAlignment CrossAxisAlignment MainAxisSize MainAxisSize + constrained bool + cs Constraints + begun bool taken int maxCross int maxBaseline int @@ -56,29 +57,42 @@ const ( Stretch ) -func (f *Flex) Rigid(ops *ui.Ops, w Widget) FlexChild { - mainc := axisMainConstraint(f.Axis, f.Constraints) +func (f *Flex) Init(cs Constraints) { + if f.constrained { + panic("Constrain must be called exactly once") + } + f.constrained = true + f.cs = cs + f.taken = 0 + f.maxCross = 0 + f.maxBaseline = 0 +} + +func (f *Flex) begin(ops *ui.Ops) { + if !f.constrained { + panic("must Constrain before adding a child") + } + if f.begun { + panic("must End before adding a child") + } + f.begun = true + ops.Begin() + ui.OpLayer{}.Add(ops) +} + +func (f *Flex) Rigid(ops *ui.Ops) Constraints { + f.begin(ops) + mainc := axisMainConstraint(f.Axis, f.cs) mainMax := mainc.Max if mainc.Max != ui.Inf { mainMax -= f.taken } - cs := axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.Constraints)) - ops.Begin() - ui.OpLayer{}.Add(ops) - dims := w(ops, cs) - block := ops.End() - f.taken += axisMain(f.Axis, dims.Size) - if c := axisCross(f.Axis, dims.Size); c > f.maxCross { - f.maxCross = c - } - if b := dims.Baseline; b > f.maxBaseline { - f.maxBaseline = b - } - return FlexChild{block, dims} + return axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs)) } -func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) FlexChild { - mainc := axisMainConstraint(f.Axis, f.Constraints) +func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode) Constraints { + f.begin(ops) + mainc := axisMainConstraint(f.Axis, f.cs) var flexSize int if mainc.Max != ui.Inf && mainc.Max > f.taken { flexSize = mainc.Max - f.taken @@ -87,10 +101,14 @@ func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) Flex if mode == Fit { submainc.Min = submainc.Max } - cs := axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.Constraints)) - ops.Begin() - ui.OpLayer{}.Add(ops) - dims := w(ops, cs) + return axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs)) +} + +func (f *Flex) End(ops *ui.Ops, dims Dimens) FlexChild { + if !f.begun { + panic("End called without an active child") + } + f.begun = false block := ops.End() f.taken += axisMain(f.Axis, dims.Size) if c := axisCross(f.Axis, dims.Size); c > f.maxCross { @@ -103,8 +121,8 @@ func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode, w Widget) Flex } func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens { - mainc := axisMainConstraint(f.Axis, f.Constraints) - crossSize := axisCrossConstraint(f.Axis, f.Constraints).Constrain(f.maxCross) + mainc := axisMainConstraint(f.Axis, f.cs) + crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross) var space int if mainc.Max != ui.Inf && f.MainAxisSize == Max { if mainc.Max > f.taken { diff --git a/ui/layout/layout.go b/ui/layout/layout.go index 96dbaec3..973d43f8 100644 --- a/ui/layout/layout.go +++ b/ui/layout/layout.go @@ -9,8 +9,6 @@ import ( "gioui.org/ui" ) -type Widget func(ops *ui.Ops, cs Constraints) Dimens - type Constraints struct { Width Constraint Height Constraint @@ -70,12 +68,13 @@ func ExactConstraints(size image.Point) Constraints { } type Insets struct { - C Widget - Top, Right, Bottom, Left float32 + + cs Constraints } -func (in Insets) W(ops *ui.Ops, cs Constraints) Dimens { +func (in *Insets) Begin(ops *ui.Ops, cs Constraints) Constraints { + in.cs = cs mcs := cs t, r, b, l := int(math.Round(float64(in.Top))), int(math.Round(float64(in.Right))), int(math.Round(float64(in.Bottom))), int(math.Round(float64(in.Left))) if mcs.Width.Max != ui.Inf { @@ -100,16 +99,20 @@ func (in Insets) W(ops *ui.Ops, cs Constraints) Dimens { } ops.Begin() ui.OpTransform{Transform: ui.Offset(toPointF(image.Point{X: l, Y: t}))}.Add(ops) - dims := in.C(ops, mcs) + return mcs +} + +func (in *Insets) End(ops *ui.Ops, dims Dimens) Dimens { ops.End().Add(ops) + t, r, b, l := int(math.Round(float64(in.Top))), int(math.Round(float64(in.Right))), int(math.Round(float64(in.Bottom))), int(math.Round(float64(in.Left))) return Dimens{ - Size: cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})), + Size: in.cs.Constrain(dims.Size.Add(image.Point{X: r + l, Y: t + b})), Baseline: dims.Baseline + t, } } -func EqualInsets(v float32, w Widget) Insets { - return Insets{C: w, Top: v, Right: v, Bottom: v, Left: v} +func EqualInsets(v float32) Insets { + return Insets{Top: v, Right: v, Bottom: v, Left: v} } func isInf(v ui.Value) bool { @@ -117,11 +120,10 @@ func isInf(v ui.Value) bool { } type Sized struct { - C Widget Width, Height float32 } -func (s Sized) W(ops *ui.Ops, cs Constraints) Dimens { +func (s Sized) Constrain(cs Constraints) Constraints { if h := int(s.Height + 0.5); h != 0 { if cs.Height.Min < h { cs.Height.Min = h @@ -138,24 +140,28 @@ func (s Sized) W(ops *ui.Ops, cs Constraints) Dimens { cs.Width.Max = w } } - return s.C(ops, cs) + return cs } type Align struct { - C Widget Alignment Direction + cs Constraints } -func (a Align) W(ops *ui.Ops, cs Constraints) Dimens { +func (a *Align) Begin(ops *ui.Ops, cs Constraints) Constraints { + a.cs = cs ops.Begin() - dims := a.C(ops, cs.Loose()) + return cs.Loose() +} + +func (a *Align) End(ops *ui.Ops, dims Dimens) Dimens { block := ops.End() sz := dims.Size - if cs.Width.Max != ui.Inf { - sz.X = cs.Width.Max + if a.cs.Width.Max != ui.Inf { + sz.X = a.cs.Width.Max } - if cs.Height.Max != ui.Inf { - sz.Y = cs.Height.Max + if a.cs.Height.Max != ui.Inf { + sz.Y = a.cs.Height.Max } var p image.Point switch a.Alignment { diff --git a/ui/layout/list.go b/ui/layout/list.go index 1093ea7b..d3673271 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -31,31 +31,31 @@ type List struct { offset int first int - ops *ui.Ops cs Constraints len int - maxSize int - children []scrollChild - elemForward bool - - size image.Point + maxSize int + children []scrollChild + dir iterationDir } -func (l *List) Init(ops *ui.Ops, cs Constraints, len int) (int, bool) { +type iterationDir uint8 + +const ( + iterateNone iterationDir = iota + iterateForward + iterateBackward +) + +func (l *List) Init(cs Constraints, len int) { + l.dir = iterateNone l.maxSize = 0 l.children = l.children[:0] - l.ops = ops l.cs = cs l.len = len if l.first > len { l.first = len } - if len == 0 { - return 0, false - } - l.scroll.Op(ops, &l.area) - return l.Index() } func (l *List) Dragging() bool { @@ -70,24 +70,28 @@ func (l *List) Scroll(c *ui.Config, q pointer.Events) { l.offset += d } -func (l *List) Index() (int, bool) { - i, ok := l.next() - if !ok { - l.draw() +func (l *List) Next(ops *ui.Ops) (int, Constraints, bool) { + if l.dir != iterateNone { + panic("a previous Next was not finished with Elem") } - return i, ok -} - -func (l *List) Layout() Dimens { - l.area.Size = l.size - return Dimens{Size: l.size} + i, ok := l.next() + var cs Constraints + if ok { + if len(l.children) == 0 { + l.scroll.Op(ops, &l.area) + } + cs = axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) + ops.Begin() + ui.OpLayer{}.Add(ops) + } + return i, cs, ok } func (l *List) next() (int, bool) { mainc := axisMainConstraint(l.Axis, l.cs) if l.offset <= 0 { if l.first > 0 { - l.elemForward = false + l.dir = iterateBackward return l.first - 1, true } l.offset = 0 @@ -95,7 +99,7 @@ func (l *List) next() (int, bool) { if l.maxSize-l.offset < mainc.Max { i := l.first + len(l.children) if i < l.len { - l.elemForward = true + l.dir = iterateForward return i, true } missing := mainc.Max - (l.maxSize - l.offset) @@ -107,31 +111,27 @@ func (l *List) next() (int, bool) { return 0, false } -func (l *List) Elem(w Widget) { - child := l.add(w) - if l.elemForward { +func (l *List) End(ops *ui.Ops, dims Dimens) { + block := ops.End() + child := scrollChild{dims.Size, block} + switch l.dir { + case iterateForward: mainSize := axisMain(l.Axis, child.size) l.maxSize += mainSize l.children = append(l.children, child) - } else { + case iterateBackward: l.first-- mainSize := axisMain(l.Axis, child.size) l.offset += mainSize l.maxSize += mainSize l.children = append([]scrollChild{child}, l.children...) + default: + panic("call Next before End") } + l.dir = iterateNone } -func (l *List) add(w Widget) scrollChild { - subcs := axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) - l.ops.Begin() - ui.OpLayer{}.Add(l.ops) - dims := w(l.ops, subcs) - block := l.ops.End() - return scrollChild{dims.Size, block} -} - -func (l *List) draw() { +func (l *List) Layout(ops *ui.Ops) Dimens { mainc := axisMainConstraint(l.Axis, l.cs) for len(l.children) > 0 { sz := l.children[0].size @@ -178,13 +178,13 @@ func (l *List) draw() { Min: axisPoint(l.Axis, min, -ui.Inf), Max: axisPoint(l.Axis, max, ui.Inf), } - l.ops.Begin() - draw.OpClip{Path: draw.RectPath(r)}.Add(l.ops) + ops.Begin() + draw.OpClip{Path: draw.RectPath(r)}.Add(ops) ui.OpTransform{ Transform: ui.Offset(toPointF(axisPoint(l.Axis, pos, cross))), - }.Add(l.ops) - child.block.Add(l.ops) - l.ops.End().Add(l.ops) + }.Add(ops) + child.block.Add(ops) + ops.End().Add(ops) pos += axisMain(l.Axis, sz) } atStart := l.first == 0 && l.offset <= 0 @@ -192,7 +192,9 @@ func (l *List) draw() { if atStart && l.scrollDir < 0 || atEnd && l.scrollDir > 0 { l.scroll.Stop() } - l.size = axisPoint(l.Axis, mainc.Constrain(pos), maxCross) + dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross) + l.area.Size = dims + return Dimens{Size: dims} } func (l *List) crossConstraintChild(cs Constraints) Constraint { diff --git a/ui/layout/stack.go b/ui/layout/stack.go index 13775e4a..1b6de4a3 100644 --- a/ui/layout/stack.go +++ b/ui/layout/stack.go @@ -9,11 +9,13 @@ import ( ) type Stack struct { - Alignment Direction - Constraints Constraints + Alignment Direction - maxSZ image.Point - baseline int + constrained bool + cs Constraints + begun bool + maxSZ image.Point + baseline int } type StackChild struct { @@ -34,10 +36,42 @@ const ( W ) -func (s *Stack) Rigid(ops *ui.Ops, w Widget) StackChild { +func (s *Stack) Init(cs Constraints) { + s.cs = cs + s.constrained = true + s.maxSZ = image.Point{} + s.baseline = 0 +} + +func (s *Stack) begin(ops *ui.Ops) { + if !s.constrained { + panic("must Constrain before adding a child") + } + if s.begun { + panic("must End before adding a child") + } + s.begun = true ops.Begin() ui.OpLayer{}.Add(ops) - dims := w(ops, s.Constraints) +} + +func (s *Stack) Rigid(ops *ui.Ops) Constraints { + ops.Begin() + ui.OpLayer{}.Add(ops) + return s.cs +} + +func (s *Stack) Expand(ops *ui.Ops) Constraints { + cs := Constraints{ + Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X}, + Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y}, + } + ops.Begin() + ui.OpLayer{}.Add(ops) + return cs +} + +func (s *Stack) End(ops *ui.Ops, dims Dimens) StackChild { b := ops.End() if w := dims.Size.X; w > s.maxSZ.X { s.maxSZ.X = w @@ -45,29 +79,12 @@ func (s *Stack) Rigid(ops *ui.Ops, w Widget) StackChild { if h := dims.Size.Y; h > s.maxSZ.Y { s.maxSZ.Y = h } - s.addjustBaseline(dims) - return StackChild{b, dims} -} - -func (s *Stack) Expand(ops *ui.Ops, w Widget) StackChild { - cs := Constraints{ - Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X}, - Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y}, - } - ops.Begin() - ui.OpLayer{}.Add(ops) - dims := w(ops, cs) - b := ops.End() - s.addjustBaseline(dims) - return StackChild{b, dims} -} - -func (s *Stack) addjustBaseline(dims Dimens) { if s.baseline == 0 { if b := dims.Baseline; b != dims.Size.Y { s.baseline = b } } + return StackChild{b, dims} } func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens { diff --git a/ui/text/editor.go b/ui/text/editor.go index 1ca18be5..fe9d310d 100644 --- a/ui/text/editor.go +++ b/ui/text/editor.go @@ -128,7 +128,7 @@ func (e *Editor) caretWidth() fixed.Int26_6 { return fixed.Int26_6(oneDp * 64) } -func (e *Editor) W(ops *ui.Ops, cs layout.Constraints) layout.Dimens { +func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { twoDp := int(e.cfg.Val(ui.Dp(2)) + 0.5) e.padLeft, e.padRight = twoDp, twoDp maxWidth := cs.Width.Max diff --git a/ui/text/label.go b/ui/text/label.go index a1d16d0f..ac02f4d2 100644 --- a/ui/text/label.go +++ b/ui/text/label.go @@ -80,7 +80,7 @@ func (l *lineIterator) Next() (String, f32.Point, bool) { return String{}, f32.Point{}, false } -func (l Label) W(ops *ui.Ops, cs layout.Constraints) layout.Dimens { +func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { textLayout := l.Face.Layout(l.Text, false, cs.Width.Max) lines := textLayout.Lines dims := linesDimens(lines) diff --git a/ui/widget/image.go b/ui/widget/image.go index c133c0e0..3eaef624 100644 --- a/ui/widget/image.go +++ b/ui/widget/image.go @@ -16,7 +16,7 @@ type Image struct { Rect image.Rectangle } -func (im Image) W(ops *ui.Ops, cs layout.Constraints) layout.Dimens { +func (im Image) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens { d := image.Point{X: cs.Width.Max, Y: cs.Height.Max} if d.X == ui.Inf { d.X = cs.Width.Min