From df791f2e9b17979b4e6f94417aee5a0f2f440e19 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 3 Jun 2019 16:08:38 +0200 Subject: [PATCH] ui: remove ui.Ops parameters from layouts and path builder structs Layouts and path builders are transient and need an ops list for operation. However, instead of passing the ops list to every method, pass the list in an init method and store it for subsequent methods. Signed-off-by: Elias Naur --- ui/draw/path.go | 67 +++++++++++++++++++++++-------------------- ui/layout/flex.go | 32 +++++++++++---------- ui/layout/layout.go | 12 ++++++-- ui/layout/list.go | 31 ++++++++++---------- ui/layout/stack.go | 32 +++++++++++---------- ui/measure/measure.go | 13 +++++---- 6 files changed, 101 insertions(+), 86 deletions(-) diff --git a/ui/draw/path.go b/ui/draw/path.go index a3c1a8ed..27e67f63 100644 --- a/ui/draw/path.go +++ b/ui/draw/path.go @@ -14,6 +14,7 @@ import ( ) type PathBuilder struct { + ops *ui.Ops firstVert int nverts int maxy float32 @@ -38,17 +39,21 @@ func (p opClip) Add(o *ui.Ops) { o.Write(data) } +func (p *PathBuilder) Init(ops *ui.Ops) { + p.ops = ops +} + // MoveTo moves the pen to the given position. -func (p *PathBuilder) Move(ops *ui.Ops, to f32.Point) { - p.end(ops) +func (p *PathBuilder) Move(to f32.Point) { + p.end() to = to.Add(p.pen) p.maxy = to.Y p.pen = to } // end completes the current contour. -func (p *PathBuilder) end(ops *ui.Ops) { - aux := ops.Aux() +func (p *PathBuilder) end() { + aux := p.ops.Aux() bo := binary.LittleEndian // Fill in maximal Y coordinates of the NW and NE corners. for i := p.firstVert; i < p.nverts; i++ { @@ -59,25 +64,25 @@ func (p *PathBuilder) end(ops *ui.Ops) { } // Line records a line from the pen to end. -func (p *PathBuilder) Line(ops *ui.Ops, to f32.Point) { +func (p *PathBuilder) Line(to f32.Point) { to = to.Add(p.pen) - p.lineTo(ops, to) + p.lineTo(to) } -func (p *PathBuilder) lineTo(ops *ui.Ops, to f32.Point) { +func (p *PathBuilder) lineTo(to f32.Point) { // Model lines as degenerate quadratic beziers. - p.quadTo(ops, to.Add(p.pen).Mul(.5), to) + p.quadTo(to.Add(p.pen).Mul(.5), to) } // Quad records a quadratic bezier from the pen to end // with the control point ctrl. -func (p *PathBuilder) Quad(ops *ui.Ops, ctrl, to f32.Point) { +func (p *PathBuilder) Quad(ctrl, to f32.Point) { ctrl = ctrl.Add(p.pen) to = to.Add(p.pen) - p.quadTo(ops, ctrl, to) + p.quadTo(ctrl, to) } -func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) { +func (p *PathBuilder) quadTo(ctrl, to f32.Point) { // Zero width curves don't contribute to stenciling. if p.pen.X == to.X && p.pen.X == ctrl.X { p.pen = to @@ -104,8 +109,8 @@ func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) { ctrl0 := p.pen.Mul(1 - t).Add(ctrl.Mul(t)) ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t)) mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t)) - p.simpleQuadTo(ops, ctrl0, mid) - p.simpleQuadTo(ops, ctrl1, to) + p.simpleQuadTo(ctrl0, mid) + p.simpleQuadTo(ctrl1, to) if mid.X > bounds.Max.X { bounds.Max.X = mid.X } @@ -113,7 +118,7 @@ func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) { bounds.Min.X = mid.X } } else { - p.simpleQuadTo(ops, ctrl, to) + p.simpleQuadTo(ctrl, to) } // Find the y extremum, if any. d = v0.Y - v1.Y @@ -132,7 +137,7 @@ func (p *PathBuilder) quadTo(ops *ui.Ops, ctrl, to f32.Point) { // Cube records a cubic bezier from the pen through // two control points ending in to. -func (p *PathBuilder) Cube(ops *ui.Ops, ctrl0, ctrl1, to f32.Point) { +func (p *PathBuilder) Cube(ctrl0, ctrl1, to f32.Point) { ctrl0 = ctrl0.Add(p.pen) ctrl1 = ctrl1.Add(p.pen) to = to.Add(p.pen) @@ -146,12 +151,12 @@ func (p *PathBuilder) Cube(ops *ui.Ops, ctrl0, ctrl1, to f32.Point) { if h := hull.Dy(); h > l { l = h } - p.approxCubeTo(ops, 0, l*0.001, ctrl0, ctrl1, to) + p.approxCubeTo(0, l*0.001, ctrl0, ctrl1, to) } // approxCube approximates a cubic beziér by a series of quadratic // curves. -func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int { +func (p *PathBuilder) approxCubeTo(splits int, maxDist float32, ctrl0, ctrl1, to f32.Point) int { // The idea is from // https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html // where a quadratic approximates a cubic by eliminating its t³ term @@ -179,7 +184,7 @@ func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctr c := ctrl0.Mul(3).Sub(p.pen).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0) const maxSplits = 32 if splits >= maxSplits { - p.quadTo(ops, c, to) + p.quadTo(c, to) return splits } // The maximum distance between the cubic P and its approximation Q given t @@ -191,7 +196,7 @@ func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctr v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(p.pen) d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36) if d2 <= maxDist*maxDist { - p.quadTo(ops, c, to) + p.quadTo(c, to) return splits } // De Casteljau split the curve and approximate the halves. @@ -203,8 +208,8 @@ func (p *PathBuilder) approxCubeTo(ops *ui.Ops, splits int, maxDist float32, ctr c12 := c1.Add(c2.Sub(c1).Mul(t)) c0112 := c01.Add(c12.Sub(c01).Mul(t)) splits++ - splits = p.approxCubeTo(ops, splits, maxDist, c0, c01, c0112) - splits = p.approxCubeTo(ops, splits, maxDist, c12, c2, to) + splits = p.approxCubeTo(splits, maxDist, c0, c01, c0112) + splits = p.approxCubeTo(splits, maxDist, c12, c2, to) return splits } @@ -220,7 +225,7 @@ func (p *PathBuilder) expand(b f32.Rectangle) { p.bounds = p.bounds.Union(b) } -func (p *PathBuilder) vertex(o *ui.Ops, cornerx, cornery int16, ctrl, to f32.Point) { +func (p *PathBuilder) vertex(cornerx, cornery int16, ctrl, to f32.Point) { p.nverts++ v := path.Vertex{ CornerX: cornerx, @@ -246,10 +251,10 @@ func (p *PathBuilder) vertex(o *ui.Ops, cornerx, cornery int16, ctrl, to f32.Poi bo.PutUint32(data[21:], math.Float32bits(v.CtrlY)) bo.PutUint32(data[25:], math.Float32bits(v.ToX)) bo.PutUint32(data[29:], math.Float32bits(v.ToY)) - o.Write(data) + p.ops.Write(data) } -func (p *PathBuilder) simpleQuadTo(ops *ui.Ops, ctrl, to f32.Point) { +func (p *PathBuilder) simpleQuadTo(ctrl, to f32.Point) { if p.pen.Y > p.maxy { p.maxy = p.pen.Y } @@ -260,19 +265,19 @@ func (p *PathBuilder) simpleQuadTo(ops *ui.Ops, ctrl, to f32.Point) { p.maxy = to.Y } // NW. - p.vertex(ops, -1, 1, ctrl, to) + p.vertex(-1, 1, ctrl, to) // NE. - p.vertex(ops, 1, 1, ctrl, to) + p.vertex(1, 1, ctrl, to) // SW. - p.vertex(ops, -1, -1, ctrl, to) + p.vertex(-1, -1, ctrl, to) // SE. - p.vertex(ops, 1, -1, ctrl, to) + p.vertex(1, -1, ctrl, to) p.pen = to } -func (p *PathBuilder) End(ops *ui.Ops) { - p.end(ops) +func (p *PathBuilder) End() { + p.end() opClip{ bounds: p.bounds, - }.Add(ops) + }.Add(p.ops) } diff --git a/ui/layout/flex.go b/ui/layout/flex.go index 7f716449..3c944a76 100644 --- a/ui/layout/flex.go +++ b/ui/layout/flex.go @@ -15,6 +15,7 @@ type Flex struct { CrossAxisAlignment CrossAxisAlignment MainAxisSize MainAxisSize + ops *ui.Ops constrained bool cs Constraints begun bool @@ -57,10 +58,11 @@ const ( Stretch ) -func (f *Flex) Init(cs Constraints) { +func (f *Flex) Init(ops *ui.Ops, cs Constraints) { if f.constrained { panic("Constrain must be called exactly once") } + f.ops = ops f.constrained = true f.cs = cs f.taken = 0 @@ -68,7 +70,7 @@ func (f *Flex) Init(cs Constraints) { f.maxBaseline = 0 } -func (f *Flex) begin(ops *ui.Ops) { +func (f *Flex) begin() { if !f.constrained { panic("must Constrain before adding a child") } @@ -76,12 +78,12 @@ func (f *Flex) begin(ops *ui.Ops) { panic("must End before adding a child") } f.begun = true - ops.Begin() - ui.OpLayer{}.Add(ops) + f.ops.Begin() + ui.OpLayer{}.Add(f.ops) } -func (f *Flex) Rigid(ops *ui.Ops) Constraints { - f.begin(ops) +func (f *Flex) Rigid() Constraints { + f.begin() mainc := axisMainConstraint(f.Axis, f.cs) mainMax := mainc.Max if mainc.Max != ui.Inf { @@ -90,8 +92,8 @@ func (f *Flex) Rigid(ops *ui.Ops) Constraints { return axisConstraints(f.Axis, Constraint{Max: mainMax}, f.crossConstraintChild(f.cs)) } -func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode) Constraints { - f.begin(ops) +func (f *Flex) Flexible(flex float32, mode FlexMode) Constraints { + f.begin() mainc := axisMainConstraint(f.Axis, f.cs) var flexSize int if mainc.Max != ui.Inf && mainc.Max > f.taken { @@ -104,12 +106,12 @@ func (f *Flex) Flexible(ops *ui.Ops, flex float32, mode FlexMode) Constraints { return axisConstraints(f.Axis, submainc, f.crossConstraintChild(f.cs)) } -func (f *Flex) End(ops *ui.Ops, dims Dimens) FlexChild { +func (f *Flex) End(dims Dimens) FlexChild { if !f.begun { panic("End called without an active child") } f.begun = false - block := ops.End() + block := f.ops.End() f.taken += axisMain(f.Axis, dims.Size) if c := axisCross(f.Axis, dims.Size); c > f.maxCross { f.maxCross = c @@ -120,7 +122,7 @@ func (f *Flex) End(ops *ui.Ops, dims Dimens) FlexChild { return FlexChild{block, dims} } -func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens { +func (f *Flex) Layout(children ...FlexChild) Dimens { mainc := axisMainConstraint(f.Axis, f.cs) crossSize := axisCrossConstraint(f.Axis, f.cs).Constrain(f.maxCross) var space int @@ -157,12 +159,12 @@ func (f *Flex) Layout(ops *ui.Ops, children ...FlexChild) Dimens { cross = f.maxBaseline - b } } - ui.OpPush{}.Add(ops) + ui.OpPush{}.Add(f.ops) ui.OpTransform{ Transform: ui.Offset(toPointF(axisPoint(f.Axis, mainSize, cross))), - }.Add(ops) - child.block.Add(ops) - ui.OpPop{}.Add(ops) + }.Add(f.ops) + child.block.Add(f.ops) + ui.OpPop{}.Add(f.ops) mainSize += axisMain(f.Axis, dims.Size) switch f.MainAxisAlignment { case SpaceEvenly: diff --git a/ui/layout/layout.go b/ui/layout/layout.go index 61a25375..98e1886e 100644 --- a/ui/layout/layout.go +++ b/ui/layout/layout.go @@ -70,10 +70,12 @@ func ExactConstraints(size image.Point) Constraints { type Insets struct { Top, Right, Bottom, Left float32 - cs Constraints + ops *ui.Ops + cs Constraints } func (in *Insets) Begin(ops *ui.Ops, cs Constraints) Constraints { + in.ops = ops 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))) @@ -102,7 +104,8 @@ func (in *Insets) Begin(ops *ui.Ops, cs Constraints) Constraints { return mcs } -func (in *Insets) End(ops *ui.Ops, dims Dimens) Dimens { +func (in *Insets) End(dims Dimens) Dimens { + ops := in.ops ui.OpPop{}.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{ @@ -144,17 +147,20 @@ func (s Sized) Constrain(cs Constraints) Constraints { } type Align struct { + ops *ui.Ops Alignment Direction cs Constraints } func (a *Align) Begin(ops *ui.Ops, cs Constraints) Constraints { + a.ops = ops a.cs = cs ops.Begin() return cs.Loose() } -func (a *Align) End(ops *ui.Ops, dims Dimens) Dimens { +func (a *Align) End(dims Dimens) Dimens { + ops := a.ops block := ops.End() sz := dims.Size if a.cs.Width.Max != ui.Inf { diff --git a/ui/layout/list.go b/ui/layout/list.go index c3395d02..7e9987e6 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -24,6 +24,7 @@ type List struct { // The distance scrolled since last call to Init. Distance int + ops *ui.Ops scroll gesture.Scroll scrollDir int @@ -46,7 +47,8 @@ const ( iterateBackward ) -func (l *List) Init(cs Constraints, len int) { +func (l *List) Init(ops *ui.Ops, cs Constraints, len int) { + l.ops = ops l.dir = iterateNone l.maxSize = 0 l.children = l.children[:0] @@ -55,6 +57,7 @@ func (l *List) Init(cs Constraints, len int) { if l.first > len { l.first = len } + ops.Begin() } func (l *List) Dragging() bool { @@ -69,19 +72,16 @@ func (l *List) Update(c *ui.Config, q pointer.Events) { l.offset += d } -func (l *List) Next(ops *ui.Ops) (int, Constraints, bool) { +func (l *List) Next() (int, Constraints, bool) { if l.dir != iterateNone { panic("a previous Next was not finished with Elem") } i, ok := l.next() var cs Constraints if ok { - if len(l.children) == 0 { - ops.Begin() - } cs = axisConstraints(l.Axis, Constraint{Max: ui.Inf}, l.crossConstraintChild(l.cs)) - ops.Begin() - ui.OpLayer{}.Add(ops) + l.ops.Begin() + ui.OpLayer{}.Add(l.ops) } return i, cs, ok } @@ -110,8 +110,8 @@ func (l *List) next() (int, bool) { return 0, false } -func (l *List) End(ops *ui.Ops, dims Dimens) { - block := ops.End() +func (l *List) End(dims Dimens) { + block := l.ops.End() child := scrollChild{dims.Size, block} switch l.dir { case iterateForward: @@ -130,7 +130,7 @@ func (l *List) End(ops *ui.Ops, dims Dimens) { l.dir = iterateNone } -func (l *List) Layout(ops *ui.Ops) Dimens { +func (l *List) Layout() Dimens { mainc := axisMainConstraint(l.Axis, l.cs) for len(l.children) > 0 { sz := l.children[0].size @@ -155,6 +155,7 @@ func (l *List) Layout(ops *ui.Ops) Dimens { break } } + ops := l.ops pos := -l.offset for _, child := range l.children { sz := child.size @@ -192,12 +193,10 @@ func (l *List) Layout(ops *ui.Ops) Dimens { l.scroll.Stop() } dims := axisPoint(l.Axis, mainc.Constrain(pos), maxCross) - if len(l.children) > 0 { - block := ops.End() - pointer.AreaRect(dims).Add(ops) - l.scroll.Add(ops) - block.Add(ops) - } + block := ops.End() + pointer.AreaRect(dims).Add(ops) + l.scroll.Add(ops) + block.Add(ops) return Dimens{Size: dims} } diff --git a/ui/layout/stack.go b/ui/layout/stack.go index 1af42404..3895e109 100644 --- a/ui/layout/stack.go +++ b/ui/layout/stack.go @@ -11,6 +11,7 @@ import ( type Stack struct { Alignment Direction + ops *ui.Ops constrained bool cs Constraints begun bool @@ -36,14 +37,15 @@ const ( W ) -func (s *Stack) Init(cs Constraints) { +func (s *Stack) Init(ops *ui.Ops, cs Constraints) { + s.ops = ops s.cs = cs s.constrained = true s.maxSZ = image.Point{} s.baseline = 0 } -func (s *Stack) begin(ops *ui.Ops) { +func (s *Stack) begin() { if !s.constrained { panic("must Constrain before adding a child") } @@ -51,25 +53,25 @@ func (s *Stack) begin(ops *ui.Ops) { panic("must End before adding a child") } s.begun = true - ops.Begin() - ui.OpLayer{}.Add(ops) + s.ops.Begin() + ui.OpLayer{}.Add(s.ops) } -func (s *Stack) Rigid(ops *ui.Ops) Constraints { - s.begin(ops) +func (s *Stack) Rigid() Constraints { + s.begin() return s.cs } -func (s *Stack) Expand(ops *ui.Ops) Constraints { - s.begin(ops) +func (s *Stack) Expand() Constraints { + s.begin() return Constraints{ Width: Constraint{Min: s.maxSZ.X, Max: s.maxSZ.X}, Height: Constraint{Min: s.maxSZ.Y, Max: s.maxSZ.Y}, } } -func (s *Stack) End(ops *ui.Ops, dims Dimens) StackChild { - b := ops.End() +func (s *Stack) End(dims Dimens) StackChild { + b := s.ops.End() s.begun = false if w := dims.Size.X; w > s.maxSZ.X { s.maxSZ.X = w @@ -85,7 +87,7 @@ func (s *Stack) End(ops *ui.Ops, dims Dimens) StackChild { return StackChild{b, dims} } -func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens { +func (s *Stack) Layout(children ...StackChild) Dimens { for _, ch := range children { sz := ch.dims.Size var p image.Point @@ -101,10 +103,10 @@ func (s *Stack) Layout(ops *ui.Ops, children ...StackChild) Dimens { case SW, S, SE: p.Y = s.maxSZ.Y - sz.Y } - ui.OpPush{}.Add(ops) - ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(ops) - ch.block.Add(ops) - ui.OpPop{}.Add(ops) + ui.OpPush{}.Add(s.ops) + ui.OpTransform{Transform: ui.Offset(toPointF(p))}.Add(s.ops) + ch.block.Add(s.ops) + ui.OpPop{}.Add(s.ops) } b := s.baseline if b == 0 { diff --git a/ui/measure/measure.go b/ui/measure/measure.go index 24da13ec..7e1ea088 100644 --- a/ui/measure/measure.go +++ b/ui/measure/measure.go @@ -233,6 +233,7 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock { var lastPos f32.Point var builder draw.PathBuilder ops := new(ui.Ops) + builder.Init(ops) var x fixed.Int26_6 var advIdx int ops.Begin() @@ -246,7 +247,7 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock { pos := f32.Point{ X: float32(x) / 64, } - builder.Move(ops, pos.Sub(lastPos)) + builder.Move(pos.Sub(lastPos)) lastPos = pos var lastArg f32.Point // Convert sfnt.Segments to relative segments. @@ -271,13 +272,13 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock { } switch fseg.Op { case sfnt.SegmentOpMoveTo: - builder.Move(ops, args[0]) + builder.Move(args[0]) case sfnt.SegmentOpLineTo: - builder.Line(ops, args[0]) + builder.Line(args[0]) case sfnt.SegmentOpQuadTo: - builder.Quad(ops, args[0], args[1]) + builder.Quad(args[0], args[1]) case sfnt.SegmentOpCubeTo: - builder.Cube(ops, args[0], args[1], args[2]) + builder.Cube(args[0], args[1], args[2]) default: panic("unsupported segment op") } @@ -287,6 +288,6 @@ func textPath(ppem fixed.Int26_6, f *opentype, str text.String) ui.OpBlock { x += str.Advances[advIdx] advIdx++ } - builder.End(ops) + builder.End() return ops.End() }