From cd5a78f3d8900db8d50d425eed775ad6be4a30b7 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 2 Oct 2021 10:36:45 +0200 Subject: [PATCH] widget: make Fit.scale a pure function Most importantly, return dimensions and transformation instead of adding operations. Makes the function easier to test, and supports scoped transform and clip stacks. Signed-off-by: Elias Naur --- widget/fit.go | 35 ++++++++++++----------------------- widget/fit_test.go | 35 ++++++----------------------------- widget/image.go | 7 +++++-- 3 files changed, 23 insertions(+), 54 deletions(-) diff --git a/widget/fit.go b/widget/fit.go index d45259e1..366704be 100644 --- a/widget/fit.go +++ b/widget/fit.go @@ -7,8 +7,6 @@ import ( "gioui.org/f32" "gioui.org/layout" - "gioui.org/op" - "gioui.org/op/clip" ) // Fit scales a widget to fit and clip to the constraints. @@ -32,25 +30,21 @@ const ( Fill ) -// scale adds clip and scale operations to fit dims to the constraints. -// It positions the widget to the appropriate position. -// It returns dimensions modified accordingly. -func (fit Fit) scale(gtx layout.Context, pos layout.Direction, dims layout.Dimensions) layout.Dimensions { +// scale computes the new dimensions and transformation required to fit dims to cs, given the position. +func (fit Fit) scale(cs layout.Constraints, pos layout.Direction, dims layout.Dimensions) (layout.Dimensions, f32.Affine2D) { widgetSize := dims.Size if fit == Unscaled || dims.Size.X == 0 || dims.Size.Y == 0 { - dims.Size = gtx.Constraints.Constrain(dims.Size) - clip.Rect{Max: dims.Size}.Add(gtx.Ops) + dims.Size = cs.Constrain(dims.Size) offset := pos.Position(widgetSize, dims.Size) - op.Offset(layout.FPt(offset)).Add(gtx.Ops) dims.Baseline += offset.Y - return dims + return dims, f32.Affine2D{}.Offset(layout.FPt(offset)) } scale := f32.Point{ - X: float32(gtx.Constraints.Max.X) / float32(dims.Size.X), - Y: float32(gtx.Constraints.Max.Y) / float32(dims.Size.Y), + X: float32(cs.Max.X) / float32(dims.Size.X), + Y: float32(cs.Max.Y) / float32(dims.Size.Y), } switch fit { @@ -75,13 +69,11 @@ func (fit Fit) scale(gtx layout.Context, pos layout.Direction, dims layout.Dimen // The widget would need to be scaled up, no change needed. if scale.X >= 1 { - dims.Size = gtx.Constraints.Constrain(dims.Size) - clip.Rect{Max: dims.Size}.Add(gtx.Ops) + dims.Size = cs.Constrain(dims.Size) offset := pos.Position(widgetSize, dims.Size) - op.Offset(layout.FPt(offset)).Add(gtx.Ops) dims.Baseline += offset.Y - return dims + return dims, f32.Affine2D{}.Offset(layout.FPt(offset)) } case Fill: } @@ -89,18 +81,15 @@ func (fit Fit) scale(gtx layout.Context, pos layout.Direction, dims layout.Dimen var scaledSize image.Point scaledSize.X = int(float32(widgetSize.X) * scale.X) scaledSize.Y = int(float32(widgetSize.Y) * scale.Y) - dims.Size = gtx.Constraints.Constrain(scaledSize) + dims.Size = cs.Constrain(scaledSize) dims.Baseline = int(float32(dims.Baseline) * scale.Y) - clip.Rect{Max: dims.Size}.Add(gtx.Ops) - offset := pos.Position(scaledSize, dims.Size) - op.Affine(f32.Affine2D{}. + trans := f32.Affine2D{}. Scale(f32.Point{}, scale). - Offset(layout.FPt(offset)), - ).Add(gtx.Ops) + Offset(layout.FPt(offset)) dims.Baseline += offset.Y - return dims + return dims, trans } diff --git a/widget/fit_test.go b/widget/fit_test.go index 9d516fe3..d5d78dc6 100644 --- a/widget/fit_test.go +++ b/widget/fit_test.go @@ -3,15 +3,11 @@ package widget import ( - "bytes" - "encoding/binary" "image" - "math" "testing" "gioui.org/f32" "gioui.org/layout" - "gioui.org/op" ) func TestFit(t *testing.T) { @@ -81,26 +77,13 @@ func TestFit(t *testing.T) { for fit, tests := range fittests { fit := Fit(fit) for i, test := range tests { - ops := new(op.Ops) - gtx := layout.Context{ - Ops: ops, - Constraints: layout.Constraints{ - Max: image.Point{X: 100, Y: 100}, - }, + cs := layout.Constraints{ + Max: image.Point{X: 100, Y: 100}, } - - result := fit.scale(gtx, layout.NW, layout.Dimensions{Size: test.Dims}) - - if test.Scale.X != 1 || test.Scale.Y != 1 { - opsdata := gtx.Ops.Data() - scaleX := float32Bytes(test.Scale.X) - scaleY := float32Bytes(test.Scale.Y) - if !bytes.Contains(opsdata, scaleX) { - t.Errorf("did not find scale.X:%v (%x) in ops: %x", test.Scale.X, scaleX, opsdata) - } - if !bytes.Contains(opsdata, scaleY) { - t.Errorf("did not find scale.Y:%v (%x) in ops: %x", test.Scale.Y, scaleY, opsdata) - } + result, trans := fit.scale(cs, layout.NW, layout.Dimensions{Size: test.Dims}) + sx, _, _, _, sy, _ := trans.Elems() + if scale := f32.Pt(sx, sy); scale != test.Scale { + t.Errorf("got scale %v expected %v", scale, test.Scale) } if result.Size != test.Result { @@ -109,9 +92,3 @@ func TestFit(t *testing.T) { } } } - -func float32Bytes(v float32) []byte { - var dst [4]byte - binary.LittleEndian.PutUint32(dst[:], math.Float32bits(v)) - return dst[:] -} diff --git a/widget/image.go b/widget/image.go index 48a1c879..77bacef2 100644 --- a/widget/image.go +++ b/widget/image.go @@ -8,6 +8,7 @@ import ( "gioui.org/f32" "gioui.org/layout" "gioui.org/op" + "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" ) @@ -42,10 +43,12 @@ func (im Image) Layout(gtx layout.Context) layout.Dimensions { wf, hf := float32(size.X), float32(size.Y) w, h := gtx.Px(unit.Dp(wf*scale)), gtx.Px(unit.Dp(hf*scale)) - dims := im.Fit.scale(gtx, im.Position, layout.Dimensions{Size: image.Pt(w, h)}) + dims, trans := im.Fit.scale(gtx.Constraints, im.Position, layout.Dimensions{Size: image.Pt(w, h)}) + clip.Rect{Max: dims.Size}.Add(gtx.Ops) pixelScale := scale * gtx.Metric.PxPerDp - op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(pixelScale, pixelScale))).Add(gtx.Ops) + trans = trans.Mul(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(pixelScale, pixelScale))) + op.Affine(trans).Add(gtx.Ops) im.Src.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops)