io/input: merge per-handler state

We're about to need per-handler state related to neither pointer nor
key input. This change merges the pointer and key handler state into one
state struct, tracked in the Router.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-20 18:02:35 -06:00
parent 9dfada745c
commit 4d8caba6c9
3 changed files with 263 additions and 256 deletions
+65 -81
View File
@@ -26,7 +26,6 @@ type TextInputState uint8
type keyQueue struct { type keyQueue struct {
order []event.Tag order []event.Tag
dirOrder []dirFocusEntry dirOrder []dirFocusEntry
handlers map[event.Tag]*keyHandler
hint key.InputHint hint key.InputHint
} }
@@ -43,14 +42,21 @@ type keyHandler struct {
visible bool visible bool
// reset tracks whether the handler has seen a // reset tracks whether the handler has seen a
// focus reset. // focus reset.
reset bool reset bool
hint key.InputHint
orderPlusOne int
dirOrder int
// filter are the key filters accumulated in the previous frame,
// used for routing events in the current frame.
filter keyFilter
// nextFilter is the filter accumulator for the current frame.
nextFilter keyFilter
trans f32.Affine2D
}
type keyFilter struct {
focusable bool focusable bool
active bool
hint key.InputHint
order int
dirOrder int
filters []key.Filter filters []key.Filter
trans f32.Affine2D
} }
type dirFocusEntry struct { type dirFocusEntry struct {
@@ -66,9 +72,8 @@ const (
TextInputOpen TextInputOpen
) )
func (q *keyQueue) inputHint(op key.InputHintOp) { func (k *keyHandler) inputHint(hint key.InputHint) {
h := q.handlerFor(op.Tag) k.hint = hint
h.hint = op.Hint
} }
// InputState returns the input state and returns a state // InputState returns the input state and returns a state
@@ -81,52 +86,46 @@ func (s keyState) InputState() (keyState, TextInputState) {
// InputHint returns the input hint from the focused handler and whether it was // InputHint returns the input hint from the focused handler and whether it was
// changed since the last call. // changed since the last call.
func (q *keyQueue) InputHint(state keyState) (key.InputHint, bool) { func (q *keyQueue) InputHint(handlers map[event.Tag]*handler, state keyState) (key.InputHint, bool) {
focused, ok := q.handlers[state.focus] focused, ok := handlers[state.focus]
if !ok { if !ok {
return q.hint, false return q.hint, false
} }
old := q.hint old := q.hint
q.hint = focused.hint q.hint = focused.key.hint
return q.hint, old != q.hint return q.hint, old != q.hint
} }
func (k *keyHandler) Reset() {
k.filter, k.nextFilter = k.nextFilter, k.filter
k.nextFilter = keyFilter{}
k.visible = false
k.orderPlusOne = 0
k.hint = key.HintAny
}
func (q *keyQueue) Reset() { func (q *keyQueue) Reset() {
for _, h := range q.handlers {
h.order = -1
h.hint = key.HintAny
}
q.order = q.order[:0] q.order = q.order[:0]
q.dirOrder = q.dirOrder[:0] q.dirOrder = q.dirOrder[:0]
} }
func (q *keyQueue) ResetEvent(k event.Tag) (event.Event, bool) { func (k *keyHandler) ResetEvent() (event.Event, bool) {
h, ok := q.handlers[k] if k.reset {
if !ok || h.reset {
return nil, false return nil, false
} }
h.reset = true k.reset = true
return key.FocusEvent{Focus: false}, true return key.FocusEvent{Focus: false}, true
} }
func (q *keyQueue) Frame(state keyState) keyState { func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState {
for k, h := range q.handlers { if state.focus != nil {
if !h.visible || !h.focusable { if h, ok := handlers[state.focus]; !ok || !h.key.isFocusable() {
if state.focus == k { // Remove focus from the handler that is no longer focusable.
// Remove focus from the handler that is no longer focusable. state.focus = nil
state.focus = nil state.state = TextInputClose
state.state = TextInputClose
}
if !h.visible && !h.focusable {
delete(q.handlers, k)
continue
}
} }
h.visible = false
h.focusable = false
h.active = false
} }
q.updateFocusLayout() q.updateFocusLayout(handlers)
return state return state
} }
@@ -137,7 +136,7 @@ func (q *keyQueue) Frame(state keyState) keyState {
// containing it. Then, extend the handler bounds to a horizontal beam // containing it. Then, extend the handler bounds to a horizontal beam
// and add to the row every handler whose center intersect it. Repeat // and add to the row every handler whose center intersect it. Repeat
// until no handlers remain. // until no handlers remain.
func (q *keyQueue) updateFocusLayout() { func (q *keyQueue) updateFocusLayout(handlers map[event.Tag]*handler) {
order := q.dirOrder order := q.dirOrder
// Sort by ascending y position. // Sort by ascending y position.
sort.SliceStable(order, func(i, j int) bool { sort.SliceStable(order, func(i, j int) bool {
@@ -165,18 +164,18 @@ func (q *keyQueue) updateFocusLayout() {
row++ row++
} }
for i, o := range q.dirOrder { for i, o := range q.dirOrder {
q.handlers[o.tag].dirOrder = i handlers[o.tag].key.dirOrder = i
} }
} }
// MoveFocus attempts to move the focus in the direction of dir. // MoveFocus attempts to move the focus in the direction of dir.
func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState, []taggedEvent) { func (q *keyQueue) MoveFocus(handlers map[event.Tag]*handler, state keyState, dir key.FocusDirection) (keyState, []taggedEvent) {
if len(q.dirOrder) == 0 { if len(q.dirOrder) == 0 {
return state, nil return state, nil
} }
order := 0 order := 0
if state.focus != nil { if state.focus != nil {
order = q.handlers[state.focus].dirOrder order = handlers[state.focus].key.dirOrder
} }
focus := q.dirOrder[order] focus := q.dirOrder[order]
switch dir { switch dir {
@@ -189,7 +188,7 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState,
order = -1 order = -1
} }
if state.focus != nil { if state.focus != nil {
order = q.handlers[state.focus].order order = handlers[state.focus].key.orderPlusOne - 1
if dir == key.FocusForward { if dir == key.FocusForward {
order++ order++
} else { } else {
@@ -197,7 +196,7 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState,
} }
} }
order = (order + len(q.order)) % len(q.order) order = (order + len(q.order)) % len(q.order)
return q.Focus(state, q.order[order]) return q.Focus(handlers, state, q.order[order])
case key.FocusRight, key.FocusLeft: case key.FocusRight, key.FocusLeft:
next := order next := order
if state.focus != nil { if state.focus != nil {
@@ -209,7 +208,7 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState,
if 0 <= next && next < len(q.dirOrder) { if 0 <= next && next < len(q.dirOrder) {
newFocus := q.dirOrder[next] newFocus := q.dirOrder[next]
if newFocus.row == focus.row { if newFocus.row == focus.row {
return q.Focus(state, newFocus.tag) return q.Focus(handlers, state, newFocus.tag)
} }
} }
case key.FocusUp, key.FocusDown: case key.FocusUp, key.FocusDown:
@@ -245,24 +244,24 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState,
order += delta order += delta
} }
if closest != nil { if closest != nil {
return q.Focus(state, closest) return q.Focus(handlers, state, closest)
} }
} }
return state, nil return state, nil
} }
func (q *keyQueue) BoundsFor(t event.Tag) image.Rectangle { func (q *keyQueue) BoundsFor(k *keyHandler) image.Rectangle {
order := q.handlers[t].dirOrder order := k.dirOrder
return q.dirOrder[order].bounds return q.dirOrder[order].bounds
} }
func (q *keyQueue) AreaFor(t event.Tag) int { func (q *keyQueue) AreaFor(k *keyHandler) int {
order := q.handlers[t].dirOrder order := k.dirOrder
return q.dirOrder[order].area return q.dirOrder[order].area
} }
func (q *keyQueue) Accepts(t event.Tag, e key.Event) bool { func (k *keyHandler) Accepts(e key.Event) bool {
for _, f := range q.handlers[t].filters { for _, f := range k.filter.filters {
if keyFilterMatch(f, e) { if keyFilterMatch(f, e) {
return true return true
} }
@@ -283,9 +282,9 @@ func keyFilterMatch(f key.Filter, e key.Event) bool {
return true return true
} }
func (q *keyQueue) Focus(state keyState, focus event.Tag) (keyState, []taggedEvent) { func (q *keyQueue) Focus(handlers map[event.Tag]*handler, state keyState, focus event.Tag) (keyState, []taggedEvent) {
if focus != nil { if focus != nil {
if _, exists := q.handlers[focus]; !exists { if h, exists := handlers[focus]; !exists || !h.key.isFocusable() {
focus = nil focus = nil
} }
} }
@@ -316,41 +315,26 @@ func (s keyState) softKeyboard(show bool) keyState {
return s return s
} }
func (q *keyQueue) filter(tag event.Tag, f key.Filter) { func (k *keyHandler) Filter(f key.Filter) {
h := q.handlerFor(tag) k.nextFilter.filters = append(k.nextFilter.filters, f)
if !h.active {
h.active = true
h.filters = h.filters[:0]
}
h.filters = append(h.filters, f)
} }
func (q *keyQueue) focusable(tag event.Tag) { func (k *keyHandler) isFocusable() bool {
h := q.handlerFor(tag) return k.filter.focusable && k.visible
h.focusable = true
} }
func (q *keyQueue) handlerFor(tag event.Tag) *keyHandler { func (k *keyHandler) Focusable() {
h, ok := q.handlers[tag] k.nextFilter.focusable = true
if !ok {
h = &keyHandler{order: -1}
if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler)
}
q.handlers[tag] = h
}
return h
} }
func (q *keyQueue) inputOp(tag event.Tag, t f32.Affine2D, area int, bounds image.Rectangle) { func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) {
h := q.handlerFor(tag) state.visible = true
if h.order == -1 { if state.orderPlusOne == 0 {
h.order = len(q.order) state.orderPlusOne = len(q.order) + 1
q.order = append(q.order, tag) q.order = append(q.order, tag)
q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: tag, area: area, bounds: bounds}) q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: tag, area: area, bounds: bounds})
} }
h.visible = true state.trans = t
h.trans = t
} }
func (q *keyQueue) setSelection(state keyState, req key.SelectionCmd) keyState { func (q *keyQueue) setSelection(state keyState, req key.SelectionCmd) keyState {
@@ -362,10 +346,10 @@ func (q *keyQueue) setSelection(state keyState, req key.SelectionCmd) keyState {
return state return state
} }
func (q *keyQueue) editorState(state keyState) EditorState { func (q *keyQueue) editorState(handlers map[event.Tag]*handler, state keyState) EditorState {
s := state.content s := state.content
if f := state.focus; f != nil { if f := state.focus; f != nil {
s.Selection.Transform = q.handlers[f].trans s.Selection.Transform = handlers[f].key.trans
} }
return s return s
} }
+127 -145
View File
@@ -17,9 +17,8 @@ import (
) )
type pointerQueue struct { type pointerQueue struct {
hitTree []hitNode hitTree []hitNode
areas []areaNode areas []areaNode
handlers map[event.Tag]*pointerHandler
semantic struct { semantic struct {
idsAssigned bool idsAssigned bool
@@ -62,12 +61,22 @@ type pointerInfo struct {
} }
type pointerHandler struct { type pointerHandler struct {
area int // areaPlusOne is the index into the list of pointerQueue.areas, plus 1.
areaPlusOne int
// setup tracks whether the handler has received // setup tracks whether the handler has received
// the pointer.Cancel event that resets its state. // the pointer.Cancel event that resets its state.
setup bool setup bool
active bool // filter is the combined filter of every filter the handler has
types pointer.Kind // asked for through event handling in the previous frame. It is
// used for routing events in the current frame.
filter pointerFilter
// prevFilter is the filter being built in the current frame.
nextFilter pointerFilter
}
// pointerFilter represents the union of a set of pointer filters.
type pointerFilter struct {
kinds pointer.Kind
// min and max horizontal/vertical scroll // min and max horizontal/vertical scroll
scrollRange image.Rectangle scrollRange image.Rectangle
@@ -228,37 +237,20 @@ func (c *pointerCollector) addHitNode(n hitNode) {
} }
// newHandler returns the current handler or a new one for tag. // newHandler returns the current handler or a new one for tag.
func (c *pointerCollector) newHandler(tag event.Tag) *pointerHandler { func (c *pointerCollector) newHandler(tag event.Tag, state *pointerHandler) {
areaID := c.currentArea() areaID := c.currentArea()
c.addHitNode(hitNode{ c.addHitNode(hitNode{
area: areaID, area: areaID,
tag: tag, tag: tag,
pass: c.state.pass > 0, pass: c.state.pass > 0,
}) })
h := c.q.handlerFor(tag) state.areaPlusOne = areaID + 1
h.area = areaID
return h
} }
func (q *pointerQueue) handlerFor(tag event.Tag) *pointerHandler { func (s *pointerHandler) Reset() {
h, ok := q.handlers[tag] s.areaPlusOne = 0
if !ok { s.filter = s.nextFilter
h = &pointerHandler{ s.nextFilter = pointerFilter{}
area: -1,
}
if q.handlers == nil {
q.handlers = make(map[event.Tag]*pointerHandler)
}
q.handlers[tag] = h
}
if !h.active {
h.types = 0
h.scrollRange = image.Rectangle{}
h.sourceMimes = h.sourceMimes[:0]
h.targetMimes = h.targetMimes[:0]
}
h.active = true
return h
} }
func (c *pointerCollector) actionInputOp(act system.Action) { func (c *pointerCollector) actionInputOp(act system.Action) {
@@ -288,25 +280,23 @@ func (q *pointerQueue) grab(state pointerState, req pointer.GrabCmd) (pointerSta
return state, evts return state, evts
} }
func (c *pointerCollector) inputOp(tag event.Tag) { func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) {
areaID := c.currentArea() areaID := c.currentArea()
area := &c.q.areas[areaID] area := &c.q.areas[areaID]
area.semantic.content.tag = tag area.semantic.content.tag = tag
c.newHandler(tag) c.newHandler(tag, state)
} }
func (q *pointerQueue) filterTag(tag event.Tag, f pointer.Filter) { func (s *pointerHandler) Filter(tag event.Tag, f pointer.Filter) {
h := q.handlerFor(tag) s.nextFilter.kinds = s.nextFilter.kinds | f.Kinds
h.types = h.types | f.Kinds s.nextFilter.scrollRange = s.nextFilter.scrollRange.Union(f.ScrollBounds)
h.scrollRange = h.scrollRange.Union(f.ScrollBounds)
} }
func (q *pointerQueue) ResetEvent(tag event.Tag) (event.Event, bool) { func (s *pointerHandler) ResetEvent() (event.Event, bool) {
h, ok := q.handlers[tag] if s.setup {
if !ok || h.setup {
return nil, false return nil, false
} }
h.setup = true s.setup = true
return pointer.Event{Kind: pointer.Cancel}, true return pointer.Event{Kind: pointer.Cancel}, true
} }
@@ -351,17 +341,15 @@ func (c *pointerCollector) cursor(cursor pointer.Cursor) {
area.cursor = cursor area.cursor = cursor
} }
func (q *pointerQueue) sourceFilter(tag event.Tag, f transfer.SourceFilter) { func (s *pointerHandler) SourceFilter(tag event.Tag, f transfer.SourceFilter) {
h := q.handlerFor(tag) s.nextFilter.sourceMimes = append(s.nextFilter.sourceMimes, f.Type)
h.sourceMimes = append(h.sourceMimes, f.Type)
} }
func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter) { func (s *pointerHandler) TargetFilter(tag event.Tag, f transfer.TargetFilter) {
h := q.handlerFor(tag) s.nextFilter.targetMimes = append(s.nextFilter.targetMimes, f.Type)
h.targetMimes = append(h.targetMimes, f.Type)
} }
func (q *pointerQueue) offerData(state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) { func (q *pointerQueue) offerData(handlers map[event.Tag]*handler, state pointerState, req transfer.OfferCmd) (pointerState, []taggedEvent) {
var evts []taggedEvent var evts []taggedEvent
for i, p := range state.pointers { for i, p := range state.pointers {
if p.dataSource != req.Tag { if p.dataSource != req.Tag {
@@ -376,13 +364,13 @@ func (q *pointerQueue) offerData(state pointerState, req transfer.OfferCmd) (poi
}}) }})
} }
state.pointers = append([]pointerInfo{}, state.pointers...) state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i], evts = q.deliverTransferCancelEvent(p, evts) state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts)
break break
} }
return state, evts return state, evts
} }
func (c *pointerCollector) reset() { func (c *pointerCollector) Reset() {
c.q.reset() c.q.reset()
c.resetState() c.resetState()
c.ensureRoot() c.ensureRoot()
@@ -537,19 +525,6 @@ func (q *pointerQueue) hitTest(pos f32.Point, onNode func(*hitNode) bool) pointe
return cursor return cursor
} }
func (q *pointerQueue) opHit(pos f32.Point) ([]event.Tag, pointer.Cursor) {
var hits []event.Tag
cursor := q.hitTest(pos, func(n *hitNode) bool {
if n.tag != nil {
if _, exists := q.handlers[n.tag]; exists {
hits = addHandler(hits, n.tag)
}
}
return true
})
return hits, cursor
}
func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point { func (q *pointerQueue) invTransform(areaIdx int, p f32.Point) f32.Point {
if areaIdx == -1 { if areaIdx == -1 {
return p return p
@@ -574,10 +549,6 @@ func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, pointer.Cursor) {
} }
func (q *pointerQueue) reset() { func (q *pointerQueue) reset() {
for _, h := range q.handlers {
// Reset handler.
h.area = -1
}
q.hitTree = q.hitTree[:0] q.hitTree = q.hitTree[:0]
q.areas = q.areas[:0] q.areas = q.areas[:0]
q.semantic.idsAssigned = false q.semantic.idsAssigned = false
@@ -597,20 +568,14 @@ func (q *pointerQueue) reset() {
} }
} }
func (q *pointerQueue) Frame(state pointerState) (pointerState, []taggedEvent) { func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState) (pointerState, []taggedEvent) {
for k, h := range q.handlers { for _, h := range handlers {
if !h.active { if h.pointer.areaPlusOne != 0 {
state = dropHandler(state, k) area := &q.areas[h.pointer.areaPlusOne-1]
delete(q.handlers, k) if h.pointer.filter.kinds&(pointer.Press|pointer.Release) != 0 {
continue
}
h.active = false
if h.area != -1 {
area := &q.areas[h.area]
if h.types&(pointer.Press|pointer.Release) != 0 {
area.semantic.content.gestures |= ClickGesture area.semantic.content.gestures |= ClickGesture
} }
if h.types&pointer.Scroll != 0 { if h.pointer.filter.kinds&pointer.Scroll != 0 {
area.semantic.content.gestures |= ScrollGesture area.semantic.content.gestures |= ScrollGesture
} }
area.semantic.valid = area.semantic.content.gestures != 0 area.semantic.valid = area.semantic.content.gestures != 0
@@ -619,7 +584,7 @@ func (q *pointerQueue) Frame(state pointerState) (pointerState, []taggedEvent) {
var evts []taggedEvent var evts []taggedEvent
for i, p := range state.pointers { for i, p := range state.pointers {
changed := false changed := false
p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(state.cursor, p, evts, p.last) p, evts, state.cursor, changed = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, p.last)
if changed { if changed {
state.pointers = append([]pointerInfo{}, state.pointers...) state.pointers = append([]pointerInfo{}, state.pointers...)
state.pointers[i] = p state.pointers[i] = p
@@ -664,7 +629,7 @@ func (s pointerState) pointerOf(e pointer.Event) (pointerState, int) {
} }
// Deliver is like Push, but delivers an event to a particular area. // Deliver is like Push, but delivers an event to a particular area.
func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event) []taggedEvent { func (q *pointerQueue) Deliver(handlers map[event.Tag]*handler, areaIdx int, e pointer.Event) []taggedEvent {
var sx, sy = e.Scroll.X, e.Scroll.Y var sx, sy = e.Scroll.X, e.Scroll.Y
idx := len(q.hitTree) - 1 idx := len(q.hitTree) - 1
// Locate first potential receiver. // Locate first potential receiver.
@@ -679,23 +644,21 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event) []taggedEvent {
for idx != -1 { for idx != -1 {
n := &q.hitTree[idx] n := &q.hitTree[idx]
idx = n.next idx = n.next
if n.tag == nil { h, ok := handlers[n.tag]
continue if !ok || e.Kind&h.pointer.filter.kinds == 0 {
}
h := q.handlers[n.tag]
if h == nil || e.Kind&h.types == 0 {
continue continue
} }
f := h.pointer.filter
e := e e := e
if e.Kind == pointer.Scroll { if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 { if sx == 0 && sy == 0 {
break break
} }
// Distribute the scroll to the handler based on its ScrollRange. // Distribute the scroll to the handler based on its ScrollRange.
sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X) sx, e.Scroll.X = setScrollEvent(sx, f.scrollRange.Min.X, f.scrollRange.Max.X)
sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y) sy, e.Scroll.Y = setScrollEvent(sy, f.scrollRange.Min.Y, f.scrollRange.Max.Y)
} }
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{tag: n.tag, event: e}) evts = append(evts, taggedEvent{tag: n.tag, event: e})
if e.Kind != pointer.Scroll { if e.Kind != pointer.Scroll {
break break
@@ -717,10 +680,10 @@ func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) {
return semanticContent{}, -1 return semanticContent{}, -1
} }
func (q *pointerQueue) Push(state pointerState, e pointer.Event) (pointerState, []taggedEvent) { func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState, e pointer.Event) (pointerState, []taggedEvent) {
var evts []taggedEvent var evts []taggedEvent
if e.Kind == pointer.Cancel { if e.Kind == pointer.Cancel {
for k := range q.handlers { for k := range handlers {
evts = append(evts, taggedEvent{ evts = append(evts, taggedEvent{
event: pointer.Event{Kind: pointer.Cancel}, event: pointer.Event{Kind: pointer.Cancel},
tag: k, tag: k,
@@ -734,26 +697,26 @@ func (q *pointerQueue) Push(state pointerState, e pointer.Event) (pointerState,
switch e.Kind { switch e.Kind {
case pointer.Press: case pointer.Press:
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
p.pressed = true p.pressed = true
evts = q.deliverEvent(p, evts, e) evts = q.deliverEvent(handlers, p, evts, e)
case pointer.Move: case pointer.Move:
if p.pressed { if p.pressed {
e.Kind = pointer.Drag e.Kind = pointer.Drag
} }
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
evts = q.deliverEvent(p, evts, e) evts = q.deliverEvent(handlers, p, evts, e)
if p.pressed { if p.pressed {
p, evts = q.deliverDragEvent(p, evts) p, evts = q.deliverDragEvent(handlers, p, evts)
} }
case pointer.Release: case pointer.Release:
evts = q.deliverEvent(p, evts, e) evts = q.deliverEvent(handlers, p, evts, e)
p.pressed = false p.pressed = false
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
p, evts = q.deliverDropEvent(p, evts) p, evts = q.deliverDropEvent(handlers, p, evts)
case pointer.Scroll: case pointer.Scroll:
p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e)
evts = q.deliverEvent(p, evts, e) evts = q.deliverEvent(handlers, p, evts, e)
default: default:
panic("unsupported pointer event type") panic("unsupported pointer event type")
} }
@@ -770,7 +733,7 @@ func (q *pointerQueue) Push(state pointerState, e pointer.Event) (pointerState,
return state, evts return state, evts
} }
func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent { func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent {
foremost := true foremost := true
if p.pressed && len(p.handlers) == 1 { if p.pressed && len(p.handlers) == 1 {
e.Priority = pointer.Grabbed e.Priority = pointer.Grabbed
@@ -778,16 +741,20 @@ func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer
} }
var sx, sy = e.Scroll.X, e.Scroll.Y var sx, sy = e.Scroll.X, e.Scroll.Y
for _, k := range p.handlers { for _, k := range p.handlers {
h := q.handlers[k] h, ok := handlers[k]
if !ok {
continue
}
f := h.pointer.filter
if e.Kind == pointer.Scroll { if e.Kind == pointer.Scroll {
if sx == 0 && sy == 0 { if sx == 0 && sy == 0 {
return evts return evts
} }
// Distribute the scroll to the handler based on its ScrollRange. // Distribute the scroll to the handler based on its ScrollRange.
sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X) sx, e.Scroll.X = setScrollEvent(sx, f.scrollRange.Min.X, f.scrollRange.Max.X)
sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y) sy, e.Scroll.Y = setScrollEvent(sy, f.scrollRange.Min.Y, f.scrollRange.Max.Y)
} }
if e.Kind&h.types == 0 { if e.Kind&f.kinds == 0 {
continue continue
} }
e := e e := e
@@ -795,38 +762,47 @@ func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer
foremost = false foremost = false
e.Priority = pointer.Foremost e.Priority = pointer.Foremost
} }
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{event: e, tag: k}) evts = append(evts, taggedEvent{event: e, tag: k})
} }
return evts return evts
} }
func (q *pointerQueue) deliverEnterLeaveEvents(cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) { func (q *pointerQueue) deliverEnterLeaveEvents(handlers map[event.Tag]*handler, cursor pointer.Cursor, p pointerInfo, evts []taggedEvent, e pointer.Event) (pointerInfo, []taggedEvent, pointer.Cursor, bool) {
changed := false changed := false
var hits []event.Tag var hits []event.Tag
if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press { if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press {
// Consider non-mouse pointers leaving when they're released. // Consider non-mouse pointers leaving when they're released.
} else { } else {
hits, cursor = q.opHit(e.Position) var transSrc *pointerHandler
if p.pressed { if p.dataSource != nil {
// Filter out non-participating handlers, transSrc = &handlers[p.dataSource].pointer
// except potential transfer targets when a transfer has been initiated. }
var hitsHaveTarget bool cursor = q.hitTest(e.Position, func(n *hitNode) bool {
if p.dataSource != nil { h, ok := handlers[n.tag]
transferSource := q.handlers[p.dataSource] if !ok {
for _, hit := range hits { return true
if _, ok := firstMimeMatch(transferSource, q.handlers[hit]); ok { }
hitsHaveTarget = true add := true
break if p.pressed {
add = false
// Filter out non-participating handlers,
// except potential transfer targets when a transfer has been initiated.
if _, found := searchTag(p.handlers, n.tag); found {
add = true
}
if transSrc != nil {
if _, ok := firstMimeMatch(transSrc, &h.pointer); ok {
add = true
} }
} }
} }
for i := len(hits) - 1; i >= 0; i-- { if add {
if _, found := searchTag(p.handlers, hits[i]); !found && !hitsHaveTarget { hits = addHandler(hits, n.tag)
hits = append(hits[:i], hits[i+1:]...)
}
} }
} else { return true
})
if !p.pressed {
changed = true changed = true
p.handlers = hits p.handlers = hits
} }
@@ -836,28 +812,34 @@ func (q *pointerQueue) deliverEnterLeaveEvents(cursor pointer.Cursor, p pointerI
if _, found := searchTag(hits, k); found { if _, found := searchTag(hits, k); found {
continue continue
} }
h, ok := handlers[k]
if !ok {
continue
}
changed = true changed = true
h := q.handlers[k]
e := e e := e
e.Kind = pointer.Leave e.Kind = pointer.Leave
if e.Kind&h.types != 0 { if e.Kind&h.pointer.filter.kinds != 0 {
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{tag: k, event: e}) evts = append(evts, taggedEvent{tag: k, event: e})
} }
} }
// Deliver Enter events. // Deliver Enter events.
for _, k := range hits { for _, k := range hits {
h := q.handlers[k]
if _, found := searchTag(p.entered, k); found { if _, found := searchTag(p.entered, k); found {
continue continue
} }
h, ok := handlers[k]
if !ok {
continue
}
changed = true changed = true
e := e e := e
e.Kind = pointer.Enter e.Kind = pointer.Enter
if e.Kind&h.types != 0 { if e.Kind&h.pointer.filter.kinds != 0 {
e.Position = q.invTransform(h.area, e.Position) e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position)
evts = append(evts, taggedEvent{tag: k, event: e}) evts = append(evts, taggedEvent{tag: k, event: e})
} }
} }
@@ -865,21 +847,21 @@ func (q *pointerQueue) deliverEnterLeaveEvents(cursor pointer.Cursor, p pointerI
return p, evts, cursor, changed return p, evts, cursor, changed
} }
func (q *pointerQueue) deliverDragEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) { func (q *pointerQueue) deliverDragEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
if p.dataSource != nil { if p.dataSource != nil {
return p, evts return p, evts
} }
// Identify the data source. // Identify the data source.
for _, k := range p.entered { for _, k := range p.entered {
src := q.handlers[k] src := &handlers[k].pointer
if len(src.sourceMimes) == 0 { if len(src.filter.sourceMimes) == 0 {
continue continue
} }
// One data source handler per pointer. // One data source handler per pointer.
p.dataSource = k p.dataSource = k
// Notify all potential targets. // Notify all potential targets.
for k, tgt := range q.handlers { for k, tgt := range handlers {
if _, ok := firstMimeMatch(src, tgt); ok { if _, ok := firstMimeMatch(src, &tgt.pointer); ok {
evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}}) evts = append(evts, taggedEvent{tag: k, event: transfer.InitiateEvent{}})
} }
} }
@@ -888,30 +870,30 @@ func (q *pointerQueue) deliverDragEvent(p pointerInfo, evts []taggedEvent) (poin
return p, evts return p, evts
} }
func (q *pointerQueue) deliverDropEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) { func (q *pointerQueue) deliverDropEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
if p.dataSource == nil { if p.dataSource == nil {
return p, evts return p, evts
} }
// Request data from the source. // Request data from the source.
src := q.handlers[p.dataSource] src := &handlers[p.dataSource].pointer
for _, k := range p.entered { for _, k := range p.entered {
h := q.handlers[k] h := handlers[k]
if m, ok := firstMimeMatch(src, h); ok { if m, ok := firstMimeMatch(src, &h.pointer); ok {
p.dataTarget = k p.dataTarget = k
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}}) evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}})
return p, evts return p, evts
} }
} }
// No valid target found, abort. // No valid target found, abort.
return q.deliverTransferCancelEvent(p, evts) return q.deliverTransferCancelEvent(handlers, p, evts)
} }
func (q *pointerQueue) deliverTransferCancelEvent(p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) { func (q *pointerQueue) deliverTransferCancelEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent) (pointerInfo, []taggedEvent) {
evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}}) evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.CancelEvent{}})
// Cancel all potential targets. // Cancel all potential targets.
src := q.handlers[p.dataSource] src := &handlers[p.dataSource].pointer
for k, h := range q.handlers { for k, h := range handlers {
if _, ok := firstMimeMatch(src, h); ok { if _, ok := firstMimeMatch(src, &h.pointer); ok {
evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}}) evts = append(evts, taggedEvent{tag: k, event: transfer.CancelEvent{}})
} }
} }
@@ -953,8 +935,8 @@ func addHandler(tags []event.Tag, tag event.Tag) []event.Tag {
// firstMimeMatch returns the first type match between src and tgt. // firstMimeMatch returns the first type match between src and tgt.
func firstMimeMatch(src, tgt *pointerHandler) (first string, matched bool) { func firstMimeMatch(src, tgt *pointerHandler) (first string, matched bool) {
for _, m1 := range tgt.targetMimes { for _, m1 := range tgt.filter.targetMimes {
for _, m2 := range src.sourceMimes { for _, m2 := range src.filter.sourceMimes {
if m1 == m2 { if m1 == m2 {
return m1, true return m1, true
} }
+71 -30
View File
@@ -27,6 +27,7 @@ import (
type Router struct { type Router struct {
savedTrans []f32.Affine2D savedTrans []f32.Affine2D
transStack []f32.Affine2D transStack []f32.Affine2D
handlers map[event.Tag]*handler
pointer struct { pointer struct {
queue pointerQueue queue pointerQueue
collector pointerCollector collector pointerCollector
@@ -101,6 +102,15 @@ const (
// By convention, the zero value denotes the non-existent ID. // By convention, the zero value denotes the non-existent ID.
type SemanticID uint type SemanticID uint
// handler contains the per-handler state tracked by a [Router].
type handler struct {
// active tracks whether the handler was active in the current
// frame. Router deletes state belonging to inactive handlers during Frame.
active bool
pointer pointerHandler
key keyHandler
}
// stateChange represents the new state and outgoing events // stateChange represents the new state and outgoing events
// resulting from an incoming event. // resulting from an incoming event.
type stateChange struct { type stateChange struct {
@@ -153,25 +163,26 @@ func (s Source) Events(k event.Tag, filters ...event.Filter) []event.Event {
func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event { func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
var events []event.Event var events []event.Event
h := q.stateFor(k)
// Record handler filters and add reset events. // Record handler filters and add reset events.
for _, f := range filters { for _, f := range filters {
switch f := f.(type) { switch f := f.(type) {
case key.Filter: case key.Filter:
q.key.queue.filter(k, f) h.key.Filter(f)
case key.FocusFilter: case key.FocusFilter:
q.key.queue.focusable(k) h.key.Focusable()
if reset, ok := q.key.queue.ResetEvent(k); ok { if reset, ok := h.key.ResetEvent(); ok {
events = append(events, reset) events = append(events, reset)
} }
case pointer.Filter: case pointer.Filter:
q.pointer.queue.filterTag(k, f) h.pointer.Filter(k, f)
if reset, ok := q.pointer.queue.ResetEvent(k); ok { if reset, ok := h.pointer.ResetEvent(); ok {
events = append(events, reset) events = append(events, reset)
} }
case transfer.SourceFilter: case transfer.SourceFilter:
q.pointer.queue.sourceFilter(k, f) h.pointer.SourceFilter(k, f)
case transfer.TargetFilter: case transfer.TargetFilter:
q.pointer.queue.targetFilter(k, f) h.pointer.TargetFilter(k, f)
} }
} }
// Accumulate events from state changes until there are no more // Accumulate events from state changes until there are no more
@@ -225,15 +236,26 @@ func (q *Router) Frame(frame *op.Ops) {
state := q.changes[n-1].state state := q.changes[n-1].state
q.changes = append(q.changes[:0], stateChange{state: state}) q.changes = append(q.changes[:0], stateChange{state: state})
} }
for _, h := range q.handlers {
h.pointer.Reset()
h.key.Reset()
}
var ops *ops.Ops var ops *ops.Ops
if frame != nil { if frame != nil {
ops = &frame.Internal ops = &frame.Internal
} }
q.reader.Reset(ops) q.reader.Reset(ops)
q.collect() q.collect()
for k, h := range q.handlers {
if !h.active {
delete(q.handlers, k)
} else {
h.active = false
}
}
q.executeCommands() q.executeCommands()
q.changePointerState(q.pointer.queue.Frame(q.lastState().pointerState)) q.changePointerState(q.pointer.queue.Frame(q.handlers, q.lastState().pointerState))
kstate := q.key.queue.Frame(q.lastState().keyState) kstate := q.key.queue.Frame(q.handlers, q.lastState().keyState)
q.changeKeyState(kstate, nil) q.changeKeyState(kstate, nil)
// Collapse state and events. // Collapse state and events.
q.collapseState(len(q.changes) - 1) q.collapseState(len(q.changes) - 1)
@@ -258,7 +280,7 @@ func (q *Router) processEvent(e event.Event) bool {
state := q.lastState() state := q.lastState()
switch e := e.(type) { switch e := e.(type) {
case pointer.Event: case pointer.Event:
return q.changePointerState(q.pointer.queue.Push(state.pointerState, e)) return q.changePointerState(q.pointer.queue.Push(q.handlers, state.pointerState, e))
case key.Event: case key.Event:
return q.addEvents(q.queueKeyEvent(state.keyState, e)) return q.addEvents(q.queueKeyEvent(state.keyState, e))
case key.SnippetEvent: case key.SnippetEvent:
@@ -333,7 +355,7 @@ func (q *Router) executeCommands() {
kstate := q.key.queue.setSelection(state.keyState, req) kstate := q.key.queue.setSelection(state.keyState, req)
q.changeKeyState(kstate, nil) q.changeKeyState(kstate, nil)
case key.FocusCmd: case key.FocusCmd:
q.changeKeyState(q.key.queue.Focus(state.keyState, req.Tag)) q.changeKeyState(q.key.queue.Focus(q.handlers, state.keyState, req.Tag))
case key.SoftKeyboardCmd: case key.SoftKeyboardCmd:
kstate := state.keyState.softKeyboard(req.Show) kstate := state.keyState.softKeyboard(req.Show)
q.changeKeyState(kstate, nil) q.changeKeyState(kstate, nil)
@@ -341,7 +363,7 @@ func (q *Router) executeCommands() {
kstate := q.key.queue.setSnippet(state.keyState, req) kstate := q.key.queue.setSnippet(state.keyState, req)
q.changeKeyState(kstate, nil) q.changeKeyState(kstate, nil)
case transfer.OfferCmd: case transfer.OfferCmd:
q.changePointerState(q.pointer.queue.offerData(state.pointerState, req)) q.changePointerState(q.pointer.queue.offerData(q.handlers, state.pointerState, req))
case clipboard.WriteCmd: case clipboard.WriteCmd:
q.cqueue.ProcessWriteClipboard(req) q.cqueue.ProcessWriteClipboard(req)
case clipboard.ReadCmd: case clipboard.ReadCmd:
@@ -404,10 +426,9 @@ func rangeNorm(r key.Range) key.Range {
} }
func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent { func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
kq := &q.key.queue
f := state.focus f := state.focus
var evts []taggedEvent var evts []taggedEvent
if f != nil && kq.Accepts(f, e) { if f != nil && q.handlers[f].key.Accepts(e) {
evts = append(evts, taggedEvent{tag: f, event: e}) evts = append(evts, taggedEvent{tag: f, event: e})
return evts return evts
} }
@@ -430,7 +451,7 @@ func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
if n.tag == nil { if n.tag == nil {
continue continue
} }
if kq.Accepts(n.tag, e) { if q.handlers[n.tag].key.Accepts(e) {
evts = append(evts, taggedEvent{tag: n.tag, event: e}) evts = append(evts, taggedEvent{tag: n.tag, event: e})
break break
} }
@@ -439,7 +460,7 @@ func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
} }
func (q *Router) MoveFocus(dir key.FocusDirection) bool { func (q *Router) MoveFocus(dir key.FocusDirection) bool {
ks, evts := q.key.queue.MoveFocus(q.lastState().keyState, dir) ks, evts := q.key.queue.MoveFocus(q.handlers, q.lastState().keyState, dir)
return q.changeKeyState(ks, evts) return q.changeKeyState(ks, evts)
} }
@@ -451,8 +472,9 @@ func (q *Router) RevealFocus(viewport image.Rectangle) {
if focus == nil { if focus == nil {
return return
} }
bounds := q.key.queue.BoundsFor(focus) kh := &q.handlers[focus].key
area := q.key.queue.AreaFor(focus) bounds := q.key.queue.BoundsFor(kh)
area := q.key.queue.AreaFor(kh)
viewport = q.pointer.queue.ClipFor(area, viewport) viewport = q.pointer.queue.ClipFor(area, viewport)
topleft := bounds.Min.Sub(viewport.Min) topleft := bounds.Min.Sub(viewport.Min)
@@ -478,8 +500,9 @@ func (q *Router) ScrollFocus(dist image.Point) {
if focus == nil { if focus == nil {
return return
} }
area := q.key.queue.AreaFor(focus) kh := &q.handlers[focus].key
q.addEvents(q.pointer.queue.Deliver(area, pointer.Event{ area := q.key.queue.AreaFor(kh)
q.addEvents(q.pointer.queue.Deliver(q.handlers, area, pointer.Event{
Kind: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Touch, Source: pointer.Touch,
Scroll: f32internal.FPt(dist), Scroll: f32internal.FPt(dist),
@@ -517,17 +540,18 @@ func (q *Router) ClickFocus() {
if focus == nil { if focus == nil {
return return
} }
bounds := q.key.queue.BoundsFor(focus) kh := &q.handlers[focus].key
bounds := q.key.queue.BoundsFor(kh)
center := bounds.Max.Add(bounds.Min).Div(2) center := bounds.Max.Add(bounds.Min).Div(2)
e := pointer.Event{ e := pointer.Event{
Position: f32.Pt(float32(center.X), float32(center.Y)), Position: f32.Pt(float32(center.X), float32(center.Y)),
Source: pointer.Touch, Source: pointer.Touch,
} }
area := q.key.queue.AreaFor(focus) area := q.key.queue.AreaFor(kh)
e.Kind = pointer.Press e.Kind = pointer.Press
q.addEvents(q.pointer.queue.Deliver(area, e)) q.addEvents(q.pointer.queue.Deliver(q.handlers, area, e))
e.Kind = pointer.Release e.Kind = pointer.Release
q.addEvents(q.pointer.queue.Deliver(area, e)) q.addEvents(q.pointer.queue.Deliver(q.handlers, area, e))
} }
// TextInputState returns the input state from the most recent // TextInputState returns the input state from the most recent
@@ -540,7 +564,7 @@ func (q *Router) TextInputState() TextInputState {
// TextInputHint returns the input mode from the most recent key.InputOp. // TextInputHint returns the input mode from the most recent key.InputOp.
func (q *Router) TextInputHint() (key.InputHint, bool) { func (q *Router) TextInputHint() (key.InputHint, bool) {
return q.key.queue.InputHint(q.state().keyState) return q.key.queue.InputHint(q.handlers, q.state().keyState)
} }
// WriteClipboard returns the most recent content to be copied // WriteClipboard returns the most recent content to be copied
@@ -576,14 +600,27 @@ func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode {
// EditorState returns the editor state for the focused handler, or the // EditorState returns the editor state for the focused handler, or the
// zero value if there is none. // zero value if there is none.
func (q *Router) EditorState() EditorState { func (q *Router) EditorState() EditorState {
return q.key.queue.editorState(q.state().keyState) return q.key.queue.editorState(q.handlers, q.state().keyState)
}
func (q *Router) stateFor(tag event.Tag) *handler {
s, ok := q.handlers[tag]
if !ok {
s = new(handler)
if q.handlers == nil {
q.handlers = make(map[event.Tag]*handler)
}
q.handlers[tag] = s
}
s.active = true
return s
} }
func (q *Router) collect() { func (q *Router) collect() {
q.transStack = q.transStack[:0] q.transStack = q.transStack[:0]
pc := &q.pointer.collector pc := &q.pointer.collector
pc.q = &q.pointer.queue pc.q = &q.pointer.queue
pc.reset() pc.Reset()
kq := &q.key.queue kq := &q.key.queue
q.key.queue.Reset() q.key.queue.Reset()
var t f32.Affine2D var t f32.Affine2D
@@ -628,10 +665,13 @@ func (q *Router) collect() {
case ops.TypeInput: case ops.TypeInput:
tag := encOp.Refs[0].(event.Tag) tag := encOp.Refs[0].(event.Tag)
pc.inputOp(tag) s := q.stateFor(tag)
pc.inputOp(tag, &s.pointer)
a := pc.currentArea() a := pc.currentArea()
b := pc.currentAreaBounds() b := pc.currentAreaBounds()
kq.inputOp(tag, t, a, b) if s.filter.focusable {
kq.inputOp(tag, &s.key, t, a, b)
}
// Pointer ops. // Pointer ops.
case ops.TypePass: case ops.TypePass:
@@ -649,7 +689,8 @@ func (q *Router) collect() {
Tag: encOp.Refs[0].(event.Tag), Tag: encOp.Refs[0].(event.Tag),
Hint: key.InputHint(encOp.Data[1]), Hint: key.InputHint(encOp.Data[1]),
} }
kq.inputHint(op) s := q.stateFor(op.Tag)
s.key.inputHint(op.Hint)
// Semantic ops. // Semantic ops.
case ops.TypeSemanticLabel: case ops.TypeSemanticLabel: