diff --git a/gpu/gpu.go b/gpu/gpu.go index 0079dfd1..64395eaf 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -61,6 +61,7 @@ type renderer struct { type drawOps struct { profile bool reader ops.Reader + states []drawState cache *resourceCache vertCache []byte viewport image.Point @@ -87,7 +88,6 @@ type drawState struct { t f32.Affine2D cpath *pathOp rect bool - z int matType materialType // Current paint.ImageOp @@ -869,11 +869,12 @@ func splitTransform(t f32.Affine2D) (srs f32.Affine2D, offset f32.Point) { return } -func (d *drawOps) collectOps(r *ops.Reader, state drawState) int { +func (d *drawOps) collectOps(r *ops.Reader, state drawState) { var ( quads quadsOp stroke clip.StrokeStyle dashes dashOp + z int ) loop: for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { @@ -995,19 +996,19 @@ loop: d.allImageOps = d.allImageOps[:0] d.zimageOps = d.zimageOps[:0] d.imageOps = d.imageOps[:0] - state.z = 0 + z = 0 d.clearColor = mat.color.Opaque() continue } - state.z++ - if state.z != int(uint16(state.z)) { + z++ + if z != int(uint16(z)) { // TODO(eliasnaur) gioui.org/issue/127. panic("more than 65k paint objects not supported") } // Assume 16-bit depth buffer. const zdepth = 1 << 16 // Convert z to window-space, assuming depth range [0;1]. - zf := float32(state.z)*2/zdepth - 1.0 + zf := float32(z)*2/zdepth - 1.0 img := imageOp{ z: zf, path: state.cpath, @@ -1026,13 +1027,17 @@ loop: state.cpath = state.cpath.parent state.rect = wasrect } - case opconst.TypePush: - state.z = d.collectOps(r, state) - case opconst.TypePop: - break 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 + case opconst.TypeLoad: + id := ops.DecodeLoad(encOp.Data) + state = d.states[id] } } - return state.z } func expandPathOp(p *pathOp, clip image.Rectangle) { diff --git a/internal/opconst/ops.go b/internal/opconst/ops.go index 361abda1..ccb9884e 100644 --- a/internal/opconst/ops.go +++ b/internal/opconst/ops.go @@ -25,8 +25,8 @@ const ( TypeKeyInput TypeKeyFocus TypeKeySoftKeyboard - TypePush - TypePop + TypeSave + TypeLoad TypeAux TypeClip TypeProfile @@ -54,8 +54,8 @@ const ( TypeKeyInputLen = 1 TypeKeyFocusLen = 1 + 1 TypeKeySoftKeyboardLen = 1 + 1 - TypePushLen = 1 - TypePopLen = 1 + TypeSaveLen = 1 + 4 + TypeLoadLen = 1 + 4 TypeAuxLen = 1 TypeClipLen = 1 + 4*4 + 1 TypeProfileLen = 1 @@ -84,8 +84,8 @@ func (t OpType) Size() int { TypeKeyInputLen, TypeKeyFocusLen, TypeKeySoftKeyboardLen, - TypePushLen, - TypePopLen, + TypeSaveLen, + TypeLoadLen, TypeAuxLen, TypeClipLen, TypeProfileLen, diff --git a/internal/ops/ops.go b/internal/ops/ops.go index da389d9e..aa5b571c 100644 --- a/internal/ops/ops.go +++ b/internal/ops/ops.go @@ -62,3 +62,21 @@ func DecodeTransform(data []byte) (t f32.Affine2D) { f := math.Float32frombits(bo.Uint32(data[4*5:])) return f32.NewAffine2D(a, b, c, d, e, f) } + +// DecodeSave decodes the state id of a save op. +func DecodeSave(data []byte) int { + if opconst.OpType(data[0]) != opconst.TypeSave { + panic("invalid op") + } + bo := binary.LittleEndian + return int(bo.Uint32(data[1:])) +} + +// DecodeLoad decodes the state id of a restore op. +func DecodeLoad(data []byte) int { + if opconst.OpType(data[0]) != opconst.TypeLoad { + panic("invalid op") + } + bo := binary.LittleEndian + return int(bo.Uint32(data[1:])) +} diff --git a/io/router/key.go b/io/router/key.go index acfa3982..cb50d079 100644 --- a/io/router/key.go +++ b/io/router/key.go @@ -17,6 +17,14 @@ type keyQueue struct { handlers map[event.Tag]*keyHandler reader ops.Reader state TextInputState + // states store states during resolveFocus + states []resolveState +} + +type resolveState struct { + tag event.Tag + pri listenerPriority + keyboard TextInputState } type keyHandler struct { @@ -56,9 +64,9 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) { } q.reader.Reset(root) - focus, pri, keyboard := q.resolveFocus(events) - if pri == priNone { - focus = nil + state := q.resolveFocus(events) + if state.pri == priNone { + state.tag = nil } for k, h := range q.handlers { if !h.visible { @@ -66,26 +74,26 @@ func (q *keyQueue) Frame(root *op.Ops, events *handlerEvents) { if q.focus == k { // Remove the focus from the handler that is no longer visible. q.focus = nil - keyboard = TextInputClose + state.keyboard = TextInputClose } } - if h.new && k != focus { + if h.new && k != state.tag { // Reset the handler on (each) first appearance. events.Add(k, key.FocusEvent{Focus: false}) } } - if focus != q.focus { + if state.tag != q.focus { if q.focus != nil { events.Add(q.focus, key.FocusEvent{Focus: false}) } - q.focus = focus + q.focus = state.tag if q.focus != nil { events.Add(q.focus, key.FocusEvent{Focus: true}) } else { - keyboard = TextInputClose + state.keyboard = TextInputClose } } - q.state = keyboard + q.state = state.keyboard } func (q *keyQueue) Push(e event.Event, events *handlerEvents) { @@ -94,28 +102,28 @@ func (q *keyQueue) Push(e event.Event, events *handlerEvents) { } } -func (q *keyQueue) resolveFocus(events *handlerEvents) (tag event.Tag, pri listenerPriority, keyboard TextInputState) { -loop: +func (q *keyQueue) resolveFocus(events *handlerEvents) resolveState { + var state resolveState for encOp, ok := q.reader.Decode(); ok; encOp, ok = q.reader.Decode() { switch opconst.OpType(encOp.Data[0]) { case opconst.TypeKeyFocus: op := decodeFocusOp(encOp.Data, encOp.Refs) if op.Focus { - pri = priNewFocus + state.pri = priNewFocus } else { - pri, keyboard = priNone, TextInputClose + state.pri, state.keyboard = priNone, TextInputClose } case opconst.TypeKeySoftKeyboard: op := decodeSoftKeyboardOp(encOp.Data, encOp.Refs) if op.Show { - keyboard = TextInputOpen + state.keyboard = TextInputOpen } else { - keyboard = TextInputClose + state.keyboard = TextInputClose } case opconst.TypeKeyInput: op := decodeKeyInputOp(encOp.Data, encOp.Refs) - if op.Tag == q.focus && pri < priCurrentFocus { - pri = priCurrentFocus + if op.Tag == q.focus && state.pri < priCurrentFocus { + state.pri = priCurrentFocus } h, ok := q.handlers[op.Tag] if !ok { @@ -123,20 +131,27 @@ loop: q.handlers[op.Tag] = h } h.visible = true - tag = op.Tag - case opconst.TypePush: - newK, newPri, newKeyboard := q.resolveFocus(events) - if newKeyboard > keyboard { - keyboard = newKeyboard + state.tag = op.Tag + case opconst.TypeSave: + id := ops.DecodeSave(encOp.Data) + if extra := id - len(q.states) + 1; extra > 0 { + q.states = append(q.states, make([]resolveState, extra)...) } - if newPri.replaces(pri) { - tag, pri = newK, newPri + q.states[id] = state + state = resolveState{} + case opconst.TypeLoad: + id := ops.DecodeLoad(encOp.Data) + restored := q.states[id] + if state.keyboard > restored.keyboard { + restored.keyboard = state.keyboard } - case opconst.TypePop: - break loop + if state.pri.replaces(restored.pri) { + restored.tag, restored.pri = state.tag, state.pri + } + state = restored } } - return tag, pri, keyboard + return state } func (p listenerPriority) replaces(p2 listenerPriority) bool { diff --git a/io/router/pointer.go b/io/router/pointer.go index ea70dc14..f0115b66 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -23,6 +23,8 @@ type pointerQueue struct { pointers []pointerInfo reader ops.Reader + // states holds the storage for save/restore ops. + states []collectState scratch []event.Tag } @@ -70,34 +72,51 @@ type areaNode struct { type areaKind uint8 +// collectState represents the state for collectHandlers +type collectState struct { + t f32.Affine2D + area int + node int + pass bool +} + const ( areaRect areaKind = iota areaEllipse ) -func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f32.Affine2D, area, node int, pass bool) { +func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents) { + state := collectState{ + area: -1, + node: -1, + } for encOp, ok := r.Decode(); ok; encOp, ok = r.Decode() { switch opconst.OpType(encOp.Data[0]) { - case opconst.TypePush: - q.collectHandlers(r, events, t, area, node, pass) - case opconst.TypePop: - return + 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 + case opconst.TypeLoad: + id := ops.DecodeLoad(encOp.Data) + state = q.states[id] case opconst.TypePass: - pass = encOp.Data[1] != 0 + state.pass = encOp.Data[1] != 0 case opconst.TypeArea: var op areaOp op.Decode(encOp.Data) - q.areas = append(q.areas, areaNode{trans: t, next: area, area: op}) - area = len(q.areas) - 1 + q.areas = append(q.areas, areaNode{trans: state.t, next: state.area, area: op}) + state.area = len(q.areas) - 1 q.hitTree = append(q.hitTree, hitNode{ - next: node, - area: area, - pass: pass, + next: state.node, + area: state.area, + pass: state.pass, }) - node = len(q.hitTree) - 1 + state.node = len(q.hitTree) - 1 case opconst.TypeTransform: dop := ops.DecodeTransform(encOp.Data) - t = t.Mul(dop) + state.t = state.t.Mul(dop) case opconst.TypePointerInput: op := pointer.InputOp{ Tag: encOp.Refs[0].(event.Tag), @@ -105,12 +124,12 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f Types: pointer.Type(encOp.Data[2]), } q.hitTree = append(q.hitTree, hitNode{ - next: node, - area: area, - pass: pass, + next: state.node, + area: state.area, + pass: state.pass, tag: op.Tag, }) - node = len(q.hitTree) - 1 + state.node = len(q.hitTree) - 1 h, ok := q.handlers[op.Tag] if !ok { h = new(pointerHandler) @@ -118,7 +137,7 @@ func (q *pointerQueue) collectHandlers(r *ops.Reader, events *handlerEvents, t f events.Add(op.Tag, pointer.Event{Type: pointer.Cancel}) } h.active = true - h.area = area + h.area = state.area h.wantsGrab = h.wantsGrab || op.Grab h.types = h.types | op.Types case opconst.TypeCursor: @@ -192,7 +211,7 @@ func (q *pointerQueue) Frame(root *op.Ops, events *handlerEvents) { q.areas = q.areas[:0] q.cursors = q.cursors[:0] q.reader.Reset(root) - q.collectHandlers(&q.reader, events, f32.Affine2D{}, -1, -1, false) + q.collectHandlers(&q.reader, events) for k, h := range q.handlers { if !h.active { q.dropHandlers(events, k) diff --git a/op/op.go b/op/op.go index 35dbc0b9..8cf0279b 100644 --- a/op/op.go +++ b/op/op.go @@ -82,8 +82,11 @@ type Ops struct { version int // data contains the serialized operations. data []byte - // External references for operations. + // refs hold external references for operations. refs []interface{} + // nextStateID is the id allocated for the next + // StackOp. + nextStateID int stackStack stack macroStack stack @@ -92,7 +95,8 @@ type Ops struct { // StackOp saves and restores the operation state // in a stack-like manner. type StackOp struct { - id stackID + id int + stackID stackID macroID int ops *Ops } @@ -142,13 +146,17 @@ type pc struct { // Push (save) the current operations state. func Push(o *Ops) StackOp { + o.nextStateID++ s := StackOp{ ops: o, - id: o.stackStack.push(), + id: o.nextStateID, + stackID: o.stackStack.push(), macroID: o.macroStack.currentID, } - data := o.Write(opconst.TypePushLen) - data[0] = byte(opconst.TypePush) + bo := binary.LittleEndian + data := o.Write(opconst.TypeSaveLen) + data[0] = byte(opconst.TypeSave) + bo.PutUint32(data[1:], uint32(s.id)) return s } @@ -157,9 +165,11 @@ func (s StackOp) Pop() { if s.ops.macroStack.currentID != s.macroID { panic("pop in a different macro than push") } - s.ops.stackStack.pop(s.id) - data := s.ops.Write(opconst.TypePopLen) - data[0] = byte(opconst.TypePop) + s.ops.stackStack.pop(s.stackID) + bo := binary.LittleEndian + data := s.ops.Write(opconst.TypeLoadLen) + data[0] = byte(opconst.TypeLoad) + bo.PutUint32(data[1:], uint32(s.id)) } // Reset the Ops, preparing it for re-use. Reset invalidates @@ -173,6 +183,7 @@ func (o *Ops) Reset() { } o.data = o.data[:0] o.refs = o.refs[:0] + o.nextStateID = 0 o.version++ }