diff --git a/gpu/gpu.go b/gpu/gpu.go index 64395eaf..db914552 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -869,6 +869,13 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) { return } +func (d *drawOps) save(id int, state drawState) { + if extra := id - len(d.states) + 1; extra > 0 { + d.states = append(d.states, make([]drawState, extra)...) + } + d.states[id] = state +} + func (d *drawOps) collectOps(r *ops.Reader, state drawState) { var ( quads quadsOp @@ -876,6 +883,7 @@ func (d *drawOps) collectOps(r *ops.Reader, state drawState) { dashes dashOp z int ) + d.save(opconst.InitialStateID, state) loop: for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { switch opconst.OpType(encOp.Data[0]) { @@ -1029,13 +1037,16 @@ loop: } case opconst.TypeSave: id := ops.DecodeSave(encOp.Data) - if extra := id - len(d.states) + 1; extra > 0 { - d.states = append(d.states, make([]drawState, extra)...) - } - d.states[id] = state + d.save(id, state) case opconst.TypeLoad: - id := ops.DecodeLoad(encOp.Data) - state = d.states[id] + id, mask := ops.DecodeLoad(encOp.Data) + s := d.states[id] + if mask&opconst.TransformState != 0 { + state.t = s.t + } + if mask&^opconst.TransformState != 0 { + state = s + } } } } diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index cff4b0d1..58904ee1 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -57,7 +57,7 @@ const ( TypeKeyFocusLen = 1 + 1 TypeKeySoftKeyboardLen = 1 + 1 TypeSaveLen = 1 + 4 - TypeLoadLen = 1 + 4 + TypeLoadLen = 1 + 1 + 4 TypeAuxLen = 1 TypeClipLen = 1 + 4*4 + 1 TypeProfileLen = 1 @@ -67,6 +67,20 @@ const ( TypeDashLen = 1 + 4 + 1 ) +// StateMask is a bitmask of state types a load operation +// should restore. +type StateMask uint8 + +const ( + TransformState StateMask = 1 << iota + + AllState = ^StateMask(0) +) + +// InitialStateID is the ID for saving and loading +// the initial operation state. +const InitialStateID = 0 + func (t OpType) Size() int { return [...]int{ TypeMacroLen, diff --git a/internal/ops/ops.go b/internal/ops/ops.go index aa5b571c..49f68d73 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -72,11 +72,11 @@ func DecodeSave(data []byte) int { return int(bo.Uint32(data[1:])) } -// DecodeLoad decodes the state id of a restore op. -func DecodeLoad(data []byte) int { +// DecodeLoad decodes the state id and mask of a load op. +func DecodeLoad(data []byte) (int, opconst.StateMask) { if opconst.OpType(data[0]) != opconst.TypeLoad { panic("invalid op") } bo := binary.LittleEndian - return int(bo.Uint32(data[1:])) + return int(bo.Uint32(data[2:])), opconst.StateMask(data[1]) } diff --git a/internal/rendertest/refs/TestDeferredPaint.png b/internal/rendertest/refs/TestDeferredPaint.png index 68f278d5..1446810f 100644 Binary files a/internal/rendertest/refs/TestDeferredPaint.png and b/internal/rendertest/refs/TestDeferredPaint.png differ diff --git a/internal/rendertest/render_test.go b/internal/rendertest/render_test.go index 33c1ff43..9cdb68ec 100644 --- a/internal/rendertest/render_test.go +++ b/internal/rendertest/render_test.go @@ -107,13 +107,17 @@ func TestDeferredPaint(t *testing.T) { run(t, func(o *op.Ops) { state := op.Save(o) clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) - paint.ColorOp{Color: color.NRGBA{A: 0xff, R: 0xff}}.Add(o) + paint.ColorOp{Color: color.NRGBA{A: 0xff, G: 0xff}}.Add(o) + paint.PaintOp{}.Add(o) + + op.Affine(f32.Affine2D{}.Offset(f32.Pt(20, 20))).Add(o) m := op.Record(o) + clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) + paint.ColorOp{Color: color.NRGBA{A: 0xff, R: 0xff, G: 0xff}}.Add(o) paint.PaintOp{}.Add(o) paintMacro := m.Stop() op.Defer(o, paintMacro) - paint.ColorOp{Color: color.NRGBA{A: 0xff, G: 0xff}}.Add(o) - paint.PaintOp{}.Add(o) + state.Load() op.Affine(f32.Affine2D{}.Offset(f32.Pt(10, 10))).Add(o) clip.Rect(image.Rect(0, 0, 80, 80)).Op().Add(o) diff --git a/io/router/key.go b/io/router/key.go index cb50d079..5f64824c 100644 --- a/io/router/key.go +++ b/io/router/key.go @@ -140,7 +140,7 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState { q.states[id] = state state = resolveState{} case opconst.TypeLoad: - id := ops.DecodeLoad(encOp.Data) + id, mask := ops.DecodeLoad(encOp.Data) restored := q.states[id] if state.keyboard > restored.keyboard { restored.keyboard = state.keyboard @@ -148,7 +148,9 @@ func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState { if state.pri.replaces(restored.pri) { restored.tag, restored.pri = state.tag, state.pri } - state = restored + if mask != 0 { + state = restored + } } } return state diff --git a/io/router/pointer.go b/io/router/pointer.go index f0115b66..0fb2b22e 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -85,22 +85,33 @@ const ( areaEllipse ) +func (q *pointerQueue) save(id int, state collectState) { + if extra := id - len(q.states) + 1; extra > 0 { + q.states = append(q.states, make([]collectState, extra)...) + } + q.states[id] = state +} + func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { state := collectState{ area: -1, node: -1, } + q.save(opconst.InitialStateID, state) for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { switch opconst.OpType(encOp.Data[0]) { case opconst.TypeSave: id := ops.DecodeSave(encOp.Data) - if extra := id - len(q.states) + 1; extra > 0 { - q.states = append(q.states, make([]collectState, extra)...) - } - q.states[id] = state + q.save(id, state) case opconst.TypeLoad: - id := ops.DecodeLoad(encOp.Data) - state = q.states[id] + id, mask := ops.DecodeLoad(encOp.Data) + s := q.states[id] + if mask&opconst.TransformState != 0 { + state.t = s.t + } + if mask&^opconst.TransformState != 0 { + state = s + } case opconst.TypePass: state.pass = encOp.Data[1] != 0 case opconst.TypeArea: diff --git a/op/op.go b/op/op.go index efe8cbc2..7dc0af02 100644 --- a/op/op.go +++ b/op/op.go @@ -144,8 +144,8 @@ type pc struct { // Defer executes c after all other operations have completed, // including previously deferred operations. -// Defer saves the operation state, which is then loaded prior -// to execution. +// Defer saves the current transformation and restores it prior +// to execution. All other operation state is reset. // // Note that deferred operations are executed in first-in-first-out // order, unlike the Go facility of the same name. @@ -156,8 +156,8 @@ func Defer(o *Ops, c CallOp) { state := Save(o) // Wrap c in a macro that loads the saved state before execution. m := Record(o) - // - state.load() + load(o, opconst.InitialStateID, opconst.AllState) + load(o, state.id, opconst.TransformState) c.Add(o) c = m.Stop() // A Defer is recorded as a TypeDefer followed by the @@ -175,11 +175,17 @@ func Save(o *Ops) StateOp { id: o.nextStateID, macroID: o.macroStack.currentID, } + save(o, s.id) + return s +} + +// save records a save of the operations state to +// id. +func save(o *Ops, id int) { bo := binary.LittleEndian data := o.Write(opconst.TypeSaveLen) data[0] = byte(opconst.TypeSave) - bo.PutUint32(data[1:], uint32(s.id)) - return s + bo.PutUint32(data[1:], uint32(id)) } // Load a previously saved operations state. @@ -187,18 +193,20 @@ func (s StateOp) Load() { if s.ops.macroStack.currentID != s.macroID { panic("load in a different macro than save") } - s.load() -} - -// load is like Load without the same-macro check. -func (s StateOp) load() { if s.id == 0 { panic("zero-value op") } + load(s.ops, s.id, opconst.AllState) +} + +// load a previously saved operations state given +// its ID. Only state included in mask is affected. +func load(o *Ops, id int, m opconst.StateMask) { bo := binary.LittleEndian - data := s.ops.Write(opconst.TypeLoadLen) + data := o.Write(opconst.TypeLoadLen) data[0] = byte(opconst.TypeLoad) - bo.PutUint32(data[1:], uint32(s.id)) + data[1] = byte(m) + bo.PutUint32(data[2:], uint32(id)) } // Reset the Ops, preparing it for re-use. Reset invalidates