~eliasnaur/gio

d331dd2de80ded08a2defde8f27e3c68cce744a3 — Elias Naur 6 months ago ae2c74e
op: rename StackOp/Push/Pop to StateOp/Save/Load

The semantics were relaxed in a previous commit; this change renames
to operations accordingly.

API change. Use gofmt to adjust your code accordingly:

gofmt -r 'op.Push(a).Pop() -> op.Save(a).Load()'
gofmt -r 'op.Push(a) -> op.Save(a)'
gofmt -r 'v.Pop() -> v.Load()'
gofmt -r 'op.StackOp -> op.StateOp'

Signed-off-by: Elias Naur <mail@eliasnaur.com>
M internal/rendertest/bench_test.go => internal/rendertest/bench_test.go +16 -16
@@ 82,13 82,13 @@ func BenchmarkDrawUI(b *testing.B) {
	for i := 0; i < b.N; i++ {
		resetOps(gtx)

		p := op.Push(gtx.Ops)
		p := op.Save(gtx.Ops)
		off := float32(math.Mod(float64(i)/10, 10))
		op.Offset(f32.Pt(off, off)).Add(gtx.Ops)

		drawCore(gtx, th)

		p.Pop()
		p.Load()
		w.Frame(gtx.Ops)
	}
	finishBenchmark(b, w)


@@ 104,14 104,14 @@ func BenchmarkDrawUITransformed(b *testing.B) {
	for i := 0; i < b.N; i++ {
		resetOps(gtx)

		p := op.Push(gtx.Ops)
		p := op.Save(gtx.Ops)
		angle := float32(math.Mod(float64(i)/1000, 0.05))
		a := f32.Affine2D{}.Shear(f32.Point{}, angle, angle).Rotate(f32.Point{}, angle)
		op.Affine(a).Add(gtx.Ops)

		drawCore(gtx, th)

		p.Pop()
		p.Load()
		w.Frame(gtx.Ops)
	}
	finishBenchmark(b, w)


@@ 153,7 153,7 @@ func Benchmark1000CirclesInstanced(b *testing.B) {
func draw1000Circles(gtx layout.Context) {
	ops := gtx.Ops
	for x := 0; x < 100; x++ {
		p := op.Push(ops)
		p := op.Save(ops)
		op.Offset(f32.Pt(float32(x*10), 0)).Add(ops)
		for y := 0; y < 10; y++ {
			paint.FillShape(ops,


@@ 162,7 162,7 @@ func draw1000Circles(gtx layout.Context) {
			)
			op.Offset(f32.Pt(0, float32(100))).Add(ops)
		}
		p.Pop()
		p.Load()
	}
}



@@ 175,16 175,16 @@ func draw1000CirclesInstanced(gtx layout.Context) {
	c := r.Stop()

	for x := 0; x < 100; x++ {
		p := op.Push(ops)
		p := op.Save(ops)
		op.Offset(f32.Pt(float32(x*10), 0)).Add(ops)
		for y := 0; y < 10; y++ {
			pi := op.Push(ops)
			pi := op.Save(ops)
			paint.ColorOp{Color: color.NRGBA{R: 100 + uint8(x), G: 100 + uint8(y), B: 100, A: 120}}.Add(ops)
			c.Add(ops)
			pi.Pop()
			pi.Load()
			op.Offset(f32.Pt(0, float32(100))).Add(ops)
		}
		p.Pop()
		p.Load()
	}
}



@@ 204,7 204,7 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
		ops := &op1
		c := op.Record(ops)
		for x := 0; x < 9; x++ {
			p := op.Push(ops)
			p := op.Save(ops)
			op.Offset(f32.Pt(float32(x*50), 0)).Add(ops)
			for y := 0; y < 9; y++ {
				paint.FillShape(ops,


@@ 213,7 213,7 @@ func drawIndividualShapes(gtx layout.Context, th *material.Theme) chan op.CallOp
				)
				op.Offset(f32.Pt(0, float32(50))).Add(ops)
			}
			p.Pop()
			p.Load()
		}
		c1 <- c.Stop()
	}()


@@ 235,10 235,10 @@ func drawShapeInstances(gtx layout.Context, th *material.Theme) chan op.CallOp {
		rad := float32(0)
		for x := 0; x < 20; x++ {
			for y := 0; y < 20; y++ {
				p := op.Push(ops)
				p := op.Save(ops)
				op.Offset(f32.Pt(float32(x*50+25), float32(y*50+25))).Add(ops)
				c.Add(ops)
				p.Pop()
				p.Load()
				rad += math.Pi * 2 / 400
			}
		}


@@ 256,11 256,11 @@ func drawText(gtx layout.Context, th *material.Theme) chan op.CallOp {
		txt := material.H6(th, "")
		for x := 0; x < 40; x++ {
			txt.Text = textRows[x]
			p := op.Push(ops)
			p := op.Save(ops)
			op.Offset(f32.Pt(float32(0), float32(24*x))).Add(ops)
			gtx.Ops = ops
			txt.Layout(gtx)
			p.Pop()
			p.Load()
		}
		c3 <- c.Stop()
	}()

M internal/rendertest/clip_test.go => internal/rendertest/clip_test.go +28 -28
@@ 229,7 229,7 @@ func TestStrokedPathRoundRound(t *testing.T) {
func TestStrokedPathFlatMiter(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			clip.Stroke{
				Path: p,


@@ 241,10 241,10 @@ func TestStrokedPathFlatMiter(t *testing.T) {
				},
			}.Op().Add(o)
			paint.Fill(o, red)
			stk.Pop()
			stk.Load()
		}
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			clip.Stroke{
				Path: p,


@@ 253,7 253,7 @@ func TestStrokedPathFlatMiter(t *testing.T) {
				},
			}.Op().Add(o)
			paint.Fill(o, black)
			stk.Pop()
			stk.Load()
		}

	}, func(r result) {


@@ 266,7 266,7 @@ func TestStrokedPathFlatMiter(t *testing.T) {
func TestStrokedPathFlatMiterInf(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			clip.Stroke{
				Path: p,


@@ 278,10 278,10 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
				},
			}.Op().Add(o)
			paint.Fill(o, red)
			stk.Pop()
			stk.Load()
		}
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			clip.Stroke{
				Path: p,


@@ 290,7 290,7 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
				},
			}.Op().Add(o)
			paint.Fill(o, black)
			stk.Pop()
			stk.Load()
		}

	}, func(r result) {


@@ 303,7 303,7 @@ func TestStrokedPathFlatMiterInf(t *testing.T) {
func TestStrokedPathZeroWidth(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := new(clip.Path)
			p.Begin(o)
			p.Move(f32.Pt(10, 50))


@@ 316,11 316,11 @@ func TestStrokedPathZeroWidth(t *testing.T) {
			}.Op().Add(o)

			paint.Fill(o, black)
			stk.Pop()
			stk.Load()
		}

		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := new(clip.Path)
			p.Begin(o)
			p.Move(f32.Pt(10, 50))


@@ 330,7 330,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
			}.Op().Add(o) // width=0, disable stroke

			paint.Fill(o, red)
			stk.Pop()
			stk.Load()
		}

	}, func(r result) {


@@ 344,7 344,7 @@ func TestStrokedPathZeroWidth(t *testing.T) {
func TestDashedPathFlatCapEllipse(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newEllipsePath(o)

			var dash clip.Dash


@@ 367,10 367,10 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
				o,
				red,
			)
			stk.Pop()
			stk.Load()
		}
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newEllipsePath(o)
			clip.Stroke{
				Path: p,


@@ 383,7 383,7 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
				o,
				black,
			)
			stk.Pop()
			stk.Load()
		}

	}, func(r result) {


@@ 396,7 396,7 @@ func TestDashedPathFlatCapEllipse(t *testing.T) {
func TestDashedPathFlatCapZ(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			var dash clip.Dash
			dash.Begin(o)


@@ 414,18 414,18 @@ func TestDashedPathFlatCapZ(t *testing.T) {
				Dashes: dash.End(),
			}.Op().Add(o)
			paint.Fill(o, red)
			stk.Pop()
			stk.Load()
		}

		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			clip.Stroke{
				Path:  p,
				Style: clip.StrokeStyle{Width: 2},
			}.Op().Add(o)
			paint.Fill(o, black)
			stk.Pop()
			stk.Load()
		}
	}, func(r result) {
		r.expect(0, 0, colornames.White)


@@ 438,7 438,7 @@ func TestDashedPathFlatCapZ(t *testing.T) {
func TestDashedPathFlatCapZNoDash(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			var dash clip.Dash
			dash.Begin(o)


@@ 455,16 455,16 @@ func TestDashedPathFlatCapZNoDash(t *testing.T) {
				Dashes: dash.End(),
			}.Op().Add(o)
			paint.Fill(o, red)
			stk.Pop()
			stk.Load()
		}
		{
			stk := op.Push(o)
			stk := op.Save(o)
			clip.Stroke{
				Path:  newZigZagPath(o),
				Style: clip.StrokeStyle{Width: 2},
			}.Op().Add(o)
			paint.Fill(o, black)
			stk.Pop()
			stk.Load()
		}
	}, func(r result) {
		r.expect(0, 0, colornames.White)


@@ 477,7 477,7 @@ func TestDashedPathFlatCapZNoDash(t *testing.T) {
func TestDashedPathFlatCapZNoPath(t *testing.T) {
	run(t, func(o *op.Ops) {
		{
			stk := op.Push(o)
			stk := op.Save(o)
			var dash clip.Dash
			dash.Begin(o)
			dash.Dash(0)


@@ 492,17 492,17 @@ func TestDashedPathFlatCapZNoPath(t *testing.T) {
				Dashes: dash.End(),
			}.Op().Add(o)
			paint.Fill(o, red)
			stk.Pop()
			stk.Load()
		}
		{
			stk := op.Push(o)
			stk := op.Save(o)
			p := newZigZagPath(o)
			clip.Stroke{
				Path:  p,
				Style: clip.StrokeStyle{Width: 2},
			}.Op().Add(o)
			paint.Fill(o, black)
			stk.Pop()
			stk.Load()
		}
	}, func(r result) {
		r.expect(0, 0, colornames.White)

M internal/rendertest/render_test.go => internal/rendertest/render_test.go +22 -22
@@ 33,26 33,26 @@ func TestTransformMacro(t *testing.T) {
		m2 := op.Record(o)
		paint.ColorOp{Color: red}.Add(o)
		// Simulate a draw text call
		stack := op.Push(o)
		stack := op.Save(o)
		op.Offset(f32.Pt(0, 10)).Add(o)

		// Apply the clip-path.
		c.Add(o)

		paint.PaintOp{}.Add(o)
		stack.Pop()
		stack.Load()

		c2 := m2.Stop()

		// Call each of them in a transform
		s1 := op.Push(o)
		s1 := op.Save(o)
		op.Offset(f32.Pt(0, 0)).Add(o)
		c1.Add(o)
		s1.Pop()
		s2 := op.Push(o)
		s1.Load()
		s2 := op.Save(o)
		op.Offset(f32.Pt(0, 0)).Add(o)
		c2.Add(o)
		s2.Pop()
		s2.Load()
	}, func(r result) {
		r.expect(5, 15, colornames.Red)
		r.expect(15, 15, colornames.Black)


@@ 140,14 140,14 @@ func TestReuseStencil(t *testing.T) {
		c2 := drawChild(ops, txt)

		// lay out the children
		stack1 := op.Push(ops)
		stack1 := op.Save(ops)
		c1.Add(ops)
		stack1.Pop()
		stack1.Load()

		stack2 := op.Push(ops)
		stack2 := op.Save(ops)
		op.Offset(f32.Pt(0, 50)).Add(ops)
		c2.Add(ops)
		stack2.Pop()
		stack2.Load()
	}, func(r result) {
		r.expect(5, 5, colornames.Black)
		r.expect(5, 55, colornames.Black)


@@ 161,11 161,11 @@ func TestBuildOffscreen(t *testing.T) {

	txt := constSqCirc()
	draw := func(off float32, o *op.Ops) {
		s := op.Push(o)
		s := op.Save(o)
		op.Offset(f32.Pt(0, off)).Add(o)
		txt.Add(o)
		paint.PaintOp{}.Add(o)
		s.Pop()
		s.Load()
	}

	multiRun(t,


@@ 230,12 230,12 @@ func TestLinearGradient(t *testing.T) {
				Stop2:  f32.Pt(gr.Max.X, gr.Min.Y),
				Color2: g.To,
			}.Add(ops)
			st := op.Push(ops)
			st := op.Save(ops)
			clip.RRect{Rect: gr}.Add(ops)
			op.Affine(f32.Affine2D{}.Offset(pixelAligned.Min)).Add(ops)
			scale(pixelAligned.Dx()/128, 1).Add(ops)
			paint.PaintOp{}.Add(ops)
			st.Pop()
			st.Load()
			gr = gr.Add(f32.Pt(0, gradienth))
		}
	}, func(r result) {


@@ 260,10 260,10 @@ func TestLinearGradientAngled(t *testing.T) {
			Stop2:  f32.Pt(0, 0),
			Color2: red,
		}.Add(ops)
		st := op.Push(ops)
		st := op.Save(ops)
		clip.Rect(image.Rect(0, 0, 64, 64)).Add(ops)
		paint.PaintOp{}.Add(ops)
		st.Pop()
		st.Load()

		paint.LinearGradientOp{
			Stop1:  f32.Pt(64, 64),


@@ 271,10 271,10 @@ func TestLinearGradientAngled(t *testing.T) {
			Stop2:  f32.Pt(128, 0),
			Color2: green,
		}.Add(ops)
		st = op.Push(ops)
		st = op.Save(ops)
		clip.Rect(image.Rect(64, 0, 128, 64)).Add(ops)
		paint.PaintOp{}.Add(ops)
		st.Pop()
		st.Load()

		paint.LinearGradientOp{
			Stop1:  f32.Pt(64, 64),


@@ 282,10 282,10 @@ func TestLinearGradientAngled(t *testing.T) {
			Stop2:  f32.Pt(128, 128),
			Color2: blue,
		}.Add(ops)
		st = op.Push(ops)
		st = op.Save(ops)
		clip.Rect(image.Rect(64, 64, 128, 128)).Add(ops)
		paint.PaintOp{}.Add(ops)
		st.Pop()
		st.Load()

		paint.LinearGradientOp{
			Stop1:  f32.Pt(64, 64),


@@ 293,10 293,10 @@ func TestLinearGradientAngled(t *testing.T) {
			Stop2:  f32.Pt(0, 128),
			Color2: magenta,
		}.Add(ops)
		st = op.Push(ops)
		st = op.Save(ops)
		clip.Rect(image.Rect(0, 64, 64, 128)).Add(ops)
		paint.PaintOp{}.Add(ops)
		st.Pop()
		st.Load()
	}, func(r result) {})
}


M internal/rendertest/transform_test.go => internal/rendertest/transform_test.go +1 -1
@@ 130,7 130,7 @@ func TestOffsetScaleTexture(t *testing.T) {

func TestRotateTexture(t *testing.T) {
	run(t, func(o *op.Ops) {
		defer op.Push(o).Pop()
		defer op.Save(o).Load()
		squares.Add(o)
		a := f32.Affine2D{}.Offset(f32.Pt(30, 30)).Rotate(f32.Pt(40, 40), math.Pi/4)
		op.Affine(a).Add(o)

M io/pointer/doc.go => io/pointer/doc.go +4 -4
@@ 51,13 51,13 @@ For example:
	var stack op.StackOp
	var h1, h2 *Handler

	stack := op.Push(ops)
	state := op.Save(ops)
	pointer.InputOp{Tag: h1}.Add(Ops)
	stack.Pop()
	state.Load()

	stack = op.Push(ops)
	state = op.Save(ops)
	pointer.InputOp{Tag: h2}.Add(ops)
	stack.Pop()
	state.Load()

implies a tree of two inner nodes, each with one pointer handler.


M io/router/key_test.go => io/router/key_test.go +38 -38
@@ 38,30 38,30 @@ func TestKeyStacked(t *testing.T) {
	ops := new(op.Ops)
	r := new(Router)

	s := op.Push(ops)
	s := op.Save(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	// FocusOp must not overwrite the
	// FocusOp{Focus: true}.
	key.FocusOp{Focus: false}.Add(ops)
	s.Pop()
	s = op.Push(ops)
	s.Load()
	s = op.Save(ops)
	key.SoftKeyboardOp{Show: false}.Add(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	key.FocusOp{Focus: true}.Add(ops)
	s.Pop()
	s = op.Push(ops)
	s.Load()
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[2]}.Add(ops)
	// SoftwareKeyboardOp will open the keyboard,
	// overwriting `SoftKeyboardOp{Show: false}`.
	key.SoftKeyboardOp{Show: true}.Add(ops)
	s.Pop()
	s = op.Push(ops)
	s.Load()
	s = op.Save(ops)
	key.SoftKeyboardOp{Show: false}.Add(ops)
	key.InputOp{Tag: &handlers[3]}.Add(ops)
	// FocusOp must not overwrite the
	// FocusOp{Focus: true}.
	key.FocusOp{Focus: false}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 93,16 93,16 @@ func TestKeyRemoveFocus(t *testing.T) {
	r := new(Router)

	// New InputOp with Focus and Keyboard:
	s := op.Push(ops)
	s := op.Save(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	key.FocusOp{Focus: true}.Add(ops)
	key.SoftKeyboardOp{Show: true}.Add(ops)
	s.Pop()
	s.Load()

	// New InputOp without any focus:
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 118,19 118,19 @@ func TestKeyRemoveFocus(t *testing.T) {
	ops.Reset()

	// Will get the focus removed:
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	s.Pop()
	s.Load()

	// Unchanged:
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	s.Pop()
	s.Load()

	// Removing any Focus:
	s = op.Push(ops)
	s = op.Save(ops)
	key.FocusOp{Focus: false}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 141,18 141,18 @@ func TestKeyRemoveFocus(t *testing.T) {

	ops.Reset()

	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	s.Pop()
	s.Load()

	// Setting Focus without InputOp:
	s = op.Push(ops)
	s = op.Save(ops)
	key.FocusOp{Focus: true}.Add(ops)
	s.Pop()
	s.Load()

	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 165,18 165,18 @@ func TestKeyRemoveFocus(t *testing.T) {

	// Set focus to InputOp which already
	// exists in the previous frame:
	s = op.Push(ops)
	s = op.Save(ops)
	key.FocusOp{Focus: true}.Add(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	key.SoftKeyboardOp{Show: true}.Add(ops)
	s.Pop()
	s.Load()

	// Tries to remove focus:
	// It must not overwrite the previous `FocusOp`.
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	key.FocusOp{Focus: false}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 192,16 192,16 @@ func TestKeyFocusedInvisible(t *testing.T) {
	r := new(Router)

	// Set new InputOp with focus:
	s := op.Push(ops)
	s := op.Save(ops)
	key.FocusOp{Focus: true}.Add(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	key.SoftKeyboardOp{Show: true}.Add(ops)
	s.Pop()
	s.Load()

	// Set new InputOp without focus:
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 217,9 217,9 @@ func TestKeyFocusedInvisible(t *testing.T) {
	//

	// Unchanged:
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)



@@ 232,14 232,14 @@ func TestKeyFocusedInvisible(t *testing.T) {

	// Respawn the first element:
	// It must receive one `Event{Focus: false}`.
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[0]}.Add(ops)
	s.Pop()
	s.Load()

	// Unchanged
	s = op.Push(ops)
	s = op.Save(ops)
	key.InputOp{Tag: &handlers[1]}.Add(ops)
	s.Pop()
	s.Load()

	r.Frame(ops)


M io/router/pointer_test.go => io/router/pointer_test.go +3 -3
@@ 243,12 243,12 @@ func TestMultipleAreas(t *testing.T) {
	var ops op.Ops

	addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
	st := op.Push(&ops)
	st := op.Save(&ops)
	pointer.Rect(image.Rect(50, 50, 200, 200)).Add(&ops)
	// Second area has no Types set, yet should receive events because
	// Types for the same handles are or-ed together.
	pointer.InputOp{Tag: handler}.Add(&ops)
	st.Pop()
	st.Load()

	var r Router
	r.Frame(&ops)


@@ 468,7 468,7 @@ func TestCursorNameOp(t *testing.T) {
// addPointerHandler adds a pointer.InputOp for the tag in a
// rectangular area.
func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
	defer op.Push(ops).Pop()
	defer op.Save(ops).Load()
	pointer.Rect(area).Add(ops)
	pointer.InputOp{
		Tag:   tag,

M layout/flex.go => layout/flex.go +2 -2
@@ 181,10 181,10 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
				cross = maxBaseline - b
			}
		}
		stack := op.Push(gtx.Ops)
		stack := op.Save(gtx.Ops)
		op.Offset(FPt(f.Axis.point(mainSize, cross))).Add(gtx.Ops)
		child.call.Add(gtx.Ops)
		stack.Pop()
		stack.Load()
		mainSize += f.Axis.Main(dims.Size)
		if i < len(children)-1 {
			switch f.Spacing {

M layout/layout.go => layout/layout.go +4 -4
@@ 141,11 141,11 @@ func (in Inset) Layout(gtx Context, w Widget) Dimensions {
	if mcs.Min.Y > mcs.Max.Y {
		mcs.Min.Y = mcs.Max.Y
	}
	stack := op.Push(gtx.Ops)
	stack := op.Save(gtx.Ops)
	op.Offset(FPt(image.Point{X: left, Y: top})).Add(gtx.Ops)
	gtx.Constraints = mcs
	dims := w(gtx)
	stack.Pop()
	stack.Load()
	return Dimensions{
		Size:     dims.Size.Add(image.Point{X: right + left, Y: top + bottom}),
		Baseline: dims.Baseline + bottom,


@@ 186,10 186,10 @@ func (d Direction) Layout(gtx Context, w Widget) Dimensions {
	case SW, S, SE:
		p.Y = sz.Y - dims.Size.Y
	}
	stack := op.Push(gtx.Ops)
	stack := op.Save(gtx.Ops)
	op.Offset(FPt(p)).Add(gtx.Ops)
	call.Add(gtx.Ops)
	stack.Pop()
	stack.Load()
	return Dimensions{
		Size:     sz,
		Baseline: dims.Baseline + sz.Y - dims.Size.Y - p.Y,

M layout/list.go => layout/list.go +3 -3
@@ 253,11 253,11 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
			Min: l.Axis.point(min, -inf),
			Max: l.Axis.point(max, inf),
		}
		stack := op.Push(ops)
		stack := op.Save(ops)
		clip.Rect(r).Add(ops)
		op.Offset(FPt(l.Axis.point(pos, cross))).Add(ops)
		child.call.Add(ops)
		stack.Pop()
		stack.Load()
		pos += childSize
	}
	atStart := l.Position.First == 0 && l.Position.Offset <= 0


@@ 274,7 274,7 @@ func (l *List) layout(ops *op.Ops, macro op.MacroOp) Dimensions {
	}
	dims := l.Axis.point(pos, maxCross)
	call := macro.Stop()
	defer op.Push(ops).Pop()
	defer op.Save(ops).Load()
	pointer.Rect(image.Rectangle{Max: dims}).Add(ops)
	l.scroll.Add(ops)
	call.Add(ops)

M layout/stack.go => layout/stack.go +2 -2
@@ 104,10 104,10 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
		case SW, S, SE:
			p.Y = maxSZ.Y - sz.Y
		}
		stack := op.Push(gtx.Ops)
		stack := op.Save(gtx.Ops)
		op.Offset(FPt(p)).Add(gtx.Ops)
		ch.call.Add(gtx.Ops)
		stack.Pop()
		stack.Load()
		if baseline == 0 {
			if b := ch.dims.Baseline; b != 0 {
				baseline = b + maxSZ.Y - sz.Y - p.Y

M op/op.go => op/op.go +15 -15
@@ 33,21 33,21 @@ State
An Ops list can be viewed as a very simple virtual machine: it has an implicit
mutable state stack and execution flow can be controlled with macros.

The StackOp saves the current state to the state stack and restores it later:
The Save function saves the current state for later restoring:

	ops := new(op.Ops)
	// Save the current state, in particular the transform.
	stack := op.Push(ops)
	state := op.Save(ops)
	// Apply a transform to subsequent operations.
	op.Offset(...).Add(ops)
	...
	// Restore the previous transform.
	stack.Pop()
	state.Load()

You can also use this one-line to save the current state and restore it at the
end of a function :

  defer op.Push(ops).Pop()
  defer op.Save(ops).Load()

The MacroOp records a list of operations to be executed later:



@@ 85,15 85,15 @@ type Ops struct {
	// refs hold external references for operations.
	refs []interface{}
	// nextStateID is the id allocated for the next
	// StackOp.
	// StateOp.
	nextStateID int

	macroStack stack
}

// StackOp saves and restores the operation state
// in a stack-like manner.
type StackOp struct {
// StateOp represents a saved operation snapshop to be restored
// later.
type StateOp struct {
	id      int
	macroID int
	ops     *Ops


@@ 125,8 125,8 @@ type TransformOp struct {
	t f32.Affine2D
}

// stack tracks the integer identities of StackOp and MacroOp
// operations to ensure correct pairing of Push/Pop and Record/End.
// stack tracks the integer identities of MacroOp
// operations to ensure correct pairing of Record/End.
type stack struct {
	currentID int
	nextID    int


@@ 142,10 142,10 @@ type pc struct {
	refs int
}

// Push (save) the current operations state.
func Push(o *Ops) StackOp {
// Save the current operations state.
func Save(o *Ops) StateOp {
	o.nextStateID++
	s := StackOp{
	s := StateOp{
		ops:     o,
		id:      o.nextStateID,
		macroID: o.macroStack.currentID,


@@ 157,8 157,8 @@ func Push(o *Ops) StackOp {
	return s
}

// Pop (restore) a previously Pushed operations state.
func (s StackOp) Pop() {
// Load a previously saved operations state.
func (s StateOp) Load() {
	if s.id == 0 {
		panic("zero-value op")
	}

M op/paint/paint.go => op/paint/paint.go +2 -2
@@ 139,7 139,7 @@ func (d PaintOp) Add(o *op.Ops) {

// FillShape fills the clip shape with a color.
func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
	defer op.Push(ops).Pop()
	defer op.Save(ops).Load()
	shape.Add(ops)
	Fill(ops, c)
}


@@ 149,7 149,7 @@ func FillShape(ops *op.Ops, c color.NRGBA, shape clip.Op) {
// the painted area. Use FillShape unless you need to paint several
// times within the same clip.Op.
func Fill(ops *op.Ops, c color.NRGBA) {
	defer op.Push(ops).Pop()
	defer op.Save(ops).Load()
	ColorOp{Color: c}.Add(ops)
	PaintOp{}.Add(ops)
}

M widget/border.go => widget/border.go +2 -2
@@ 24,7 24,7 @@ func (b Border) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
	dims := w(gtx)
	sz := dims.Size
	rr := float32(gtx.Px(b.CornerRadius))
	st := op.Push(gtx.Ops)
	st := op.Save(gtx.Ops)
	width := gtx.Px(b.Width)
	clip.Border{
		Rect: f32.Rectangle{


@@ 35,6 35,6 @@ func (b Border) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
	}.Add(gtx.Ops)
	paint.ColorOp{Color: b.Color}.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	st.Pop()
	st.Load()
	return dims
}

M widget/button.go => widget/button.go +2 -2
@@ 74,10 74,10 @@ func (b *Clickable) History() []Press {

func (b *Clickable) Layout(gtx layout.Context) layout.Dimensions {
	b.update(gtx)
	stack := op.Push(gtx.Ops)
	stack := op.Save(gtx.Ops)
	pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
	b.click.Add(gtx.Ops)
	stack.Pop()
	stack.Load()
	for len(b.history) > 0 {
		c := b.history[0]
		if c.End.IsZero() || gtx.Now.Sub(c.End) < 1*time.Second {

M widget/editor.go => widget/editor.go +5 -5
@@ 451,12 451,12 @@ func (e *Editor) PaintText(gtx layout.Context) {
	cl := textPadding(e.lines)
	cl.Max = cl.Max.Add(e.viewSize)
	for _, shape := range e.shapes {
		stack := op.Push(gtx.Ops)
		stack := op.Save(gtx.Ops)
		op.Offset(layout.FPt(shape.offset)).Add(gtx.Ops)
		shape.clip.Add(gtx.Ops)
		clip.Rect(cl.Sub(shape.offset)).Add(gtx.Ops)
		paint.PaintOp{}.Add(gtx.Ops)
		stack.Pop()
		stack.Load()
	}
}



@@ 469,7 469,7 @@ func (e *Editor) PaintCaret(gtx layout.Context) {
	carX := e.caret.x
	carY := e.caret.y

	defer op.Push(gtx.Ops).Pop()
	defer op.Save(gtx.Ops).Load()
	carX -= carWidth / 2
	carAsc, carDesc := -e.lines[e.caret.line].Bounds.Min.Y, e.lines[e.caret.line].Bounds.Max.Y
	carRect := image.Rectangle{


@@ 492,10 492,10 @@ func (e *Editor) PaintCaret(gtx layout.Context) {
	cl.Max = cl.Max.Add(e.viewSize)
	carRect = cl.Intersect(carRect)
	if !carRect.Empty() {
		st := op.Push(gtx.Ops)
		st := op.Save(gtx.Ops)
		clip.Rect(carRect).Add(gtx.Ops)
		paint.PaintOp{}.Add(gtx.Ops)
		st.Pop()
		st.Load()
	}
}


M widget/enum.go => widget/enum.go +1 -1
@@ 37,7 37,7 @@ func (e *Enum) Changed() bool {

// Layout adds the event handler for key.
func (e *Enum) Layout(gtx layout.Context, key string) layout.Dimensions {
	defer op.Push(gtx.Ops).Pop()
	defer op.Save(gtx.Ops).Load()
	pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)

	if index(e.values, key) == -1 {

M widget/float.go => widget/float.go +1 -1
@@ 52,7 52,7 @@ func (f *Float) Layout(gtx layout.Context, pointerMargin int, min, max float32) 
		f.pos = 1
	}

	defer op.Push(gtx.Ops).Pop()
	defer op.Save(gtx.Ops).Load()
	rect := image.Rectangle{Max: size}
	rect.Min.X -= pointerMargin
	rect.Max.X += pointerMargin

M widget/image.go => widget/image.go +2 -2
@@ 32,10 32,10 @@ func (im Image) Layout(gtx layout.Context) layout.Dimensions {
	w, h := gtx.Px(unit.Dp(wf*scale)), gtx.Px(unit.Dp(hf*scale))
	cs := gtx.Constraints
	d := cs.Constrain(image.Pt(w, h))
	stack := op.Push(gtx.Ops)
	stack := op.Save(gtx.Ops)
	clip.Rect(image.Rectangle{Max: d}).Add(gtx.Ops)
	im.Src.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	stack.Pop()
	stack.Load()
	return layout.Dimensions{Size: d}
}

M widget/label.go => widget/label.go +2 -2
@@ 110,12 110,12 @@ func (l Label) Layout(gtx layout.Context, s text.Shaper, font text.Font, size un
		if !ok {
			break
		}
		stack := op.Push(gtx.Ops)
		stack := op.Save(gtx.Ops)
		op.Offset(layout.FPt(off)).Add(gtx.Ops)
		s.Shape(font, textSize, l).Add(gtx.Ops)
		clip.Rect(cl.Sub(off)).Add(gtx.Ops)
		paint.PaintOp{}.Add(gtx.Ops)
		stack.Pop()
		stack.Load()
	}
	return dims
}

M widget/material/button.go => widget/material/button.go +1 -1
@@ 271,7 271,7 @@ func drawInk(gtx layout.Context, c widget.Press) {
	alpha := 0.7 * alphaBezier
	const col = 0.8
	ba, bc := byte(alpha*0xff), byte(col*0xff)
	defer op.Push(gtx.Ops).Pop()
	defer op.Save(gtx.Ops).Load()
	rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
	ink := paint.ColorOp{Color: rgba}
	ink.Add(gtx.Ops)

M widget/material/editor.go => widget/material/editor.go +1 -1
@@ 40,7 40,7 @@ func Editor(th *Theme, editor *widget.Editor, hint string) EditorStyle {
}

func (e EditorStyle) Layout(gtx layout.Context) layout.Dimensions {
	defer op.Push(gtx.Ops).Pop()
	defer op.Save(gtx.Ops).Load()
	macro := op.Record(gtx.Ops)
	paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops)
	var maxlines int

M widget/material/loader.go => widget/material/loader.go +1 -1
@@ 36,7 36,7 @@ func (l LoaderStyle) Layout(gtx layout.Context) layout.Dimensions {
	}
	sz := gtx.Constraints.Constrain(image.Pt(diam, diam))
	radius := float64(sz.X) * .5
	defer op.Push(gtx.Ops).Pop()
	defer op.Save(gtx.Ops).Load()
	op.Offset(f32.Pt(float32(radius), float32(radius))).Add(gtx.Ops)

	dt := (time.Duration(gtx.Now.UnixNano()) % (time.Second)).Seconds()

M widget/material/slider.go => widget/material/slider.go +8 -8
@@ 58,13 58,13 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
		size.Y = 2 * (touchSizePx / 2)
	}

	st := op.Push(gtx.Ops)
	st := op.Save(gtx.Ops)
	op.Offset(f32.Pt(thumbRadius, 0)).Add(gtx.Ops)
	gtx.Constraints.Min = image.Pt(size.X-2*thumbRadiusInt, size.Y)
	s.Float.Layout(gtx, thumbRadiusInt, s.Min, s.Max)
	gtx.Constraints.Min.Y = size.Y
	thumbPos := thumbRadius + s.Float.Pos()
	st.Pop()
	st.Load()

	color := s.Color
	if gtx.Queue == nil {


@@ 72,7 72,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
	}

	// Draw track before thumb.
	st = op.Push(gtx.Ops)
	st = op.Save(gtx.Ops)
	track := f32.Rectangle{
		Min: f32.Point{
			X: thumbRadius,


@@ 86,19 86,19 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
	clip.RRect{Rect: track}.Add(gtx.Ops)
	paint.ColorOp{Color: color}.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	st.Pop()
	st.Load()

	// Draw track after thumb.
	st = op.Push(gtx.Ops)
	st = op.Save(gtx.Ops)
	track.Min.X = thumbPos
	track.Max.X = float32(size.X) - thumbRadius
	clip.RRect{Rect: track}.Add(gtx.Ops)
	paint.ColorOp{Color: f32color.MulAlpha(color, 96)}.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	st.Pop()
	st.Load()

	// Draw thumb.
	st = op.Push(gtx.Ops)
	st = op.Save(gtx.Ops)
	thumb := f32.Rectangle{
		Min: f32.Point{
			X: thumbPos - thumbRadius,


@@ 116,7 116,7 @@ func (s SliderStyle) Layout(gtx layout.Context) layout.Dimensions {
	}.Add(gtx.Ops)
	paint.ColorOp{Color: color}.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	st.Pop()
	st.Load()

	return layout.Dimensions{Size: size}
}

M widget/material/switch.go => widget/material/switch.go +11 -11
@@ 45,7 45,7 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
	trackOff := float32(thumbSize-trackHeight) * .5

	// Draw track.
	stack := op.Push(gtx.Ops)
	stack := op.Save(gtx.Ops)
	trackCorner := float32(trackHeight) / 2
	trackRect := f32.Rectangle{Max: f32.Point{
		X: float32(trackWidth),


@@ 66,10 66,10 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
	}.Add(gtx.Ops)
	paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	stack.Pop()
	stack.Load()

	// Draw thumb ink.
	stack = op.Push(gtx.Ops)
	stack = op.Save(gtx.Ops)
	inkSize := gtx.Px(unit.Dp(44))
	rr := float32(inkSize) * .5
	inkOff := f32.Point{


@@ 87,10 87,10 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
	for _, p := range s.Switch.History() {
		drawInk(gtx, p)
	}
	stack.Pop()
	stack.Load()

	// Compute thumb offset and color.
	stack = op.Push(gtx.Ops)
	stack = op.Save(gtx.Ops)
	if s.Switch.Value {
		off := trackWidth - thumbSize
		op.Offset(f32.Point{X: float32(off)}).Add(gtx.Ops)


@@ 98,19 98,19 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {

	// Draw thumb shadow, a translucent disc slightly larger than the
	// thumb itself.
	shadowStack := op.Push(gtx.Ops)
	shadowStack := op.Save(gtx.Ops)
	shadowSize := float32(2)
	// Center shadow horizontally and slightly adjust its Y.
	op.Offset(f32.Point{X: -shadowSize / 2, Y: -.75}).Add(gtx.Ops)
	drawDisc(gtx.Ops, float32(thumbSize)+shadowSize, argb(0x55000000))
	shadowStack.Pop()
	shadowStack.Load()

	// Draw thumb.
	drawDisc(gtx.Ops, float32(thumbSize), col)
	stack.Pop()
	stack.Load()

	// Set up click area.
	stack = op.Push(gtx.Ops)
	stack = op.Save(gtx.Ops)
	clickSize := gtx.Px(unit.Dp(40))
	clickOff := f32.Point{
		X: (float32(trackWidth) - float32(clickSize)) * .5,


@@ 121,14 121,14 @@ func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
	pointer.Ellipse(image.Rectangle{Max: sz}).Add(gtx.Ops)
	gtx.Constraints.Min = sz
	s.Switch.Layout(gtx)
	stack.Pop()
	stack.Load()

	dims := image.Point{X: trackWidth, Y: thumbSize}
	return layout.Dimensions{Size: dims}
}

func drawDisc(ops *op.Ops, sz float32, col color.NRGBA) {
	defer op.Push(ops).Pop()
	defer op.Save(ops).Load()
	rr := sz / 2
	r := f32.Rectangle{Max: f32.Point{X: sz, Y: sz}}
	clip.RRect{