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() }