From 4d8caba6c95f540ecd0902eafc4be55452b09139 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 20 Nov 2023 18:02:35 -0600 Subject: [PATCH] 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 --- io/input/key.go | 146 +++++++++++------------- io/input/pointer.go | 272 +++++++++++++++++++++----------------------- io/input/router.go | 101 +++++++++++----- 3 files changed, 263 insertions(+), 256 deletions(-) diff --git a/io/input/key.go b/io/input/key.go index c3b73e0b..4d979c71 100644 --- a/io/input/key.go +++ b/io/input/key.go @@ -26,7 +26,6 @@ type TextInputState uint8 type keyQueue struct { order []event.Tag dirOrder []dirFocusEntry - handlers map[event.Tag]*keyHandler hint key.InputHint } @@ -43,14 +42,21 @@ type keyHandler struct { visible bool // reset tracks whether the handler has seen a // 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 - active bool - hint key.InputHint - order int - dirOrder int filters []key.Filter - trans f32.Affine2D } type dirFocusEntry struct { @@ -66,9 +72,8 @@ const ( TextInputOpen ) -func (q *keyQueue) inputHint(op key.InputHintOp) { - h := q.handlerFor(op.Tag) - h.hint = op.Hint +func (k *keyHandler) inputHint(hint key.InputHint) { + k.hint = hint } // 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 // changed since the last call. -func (q *keyQueue) InputHint(state keyState) (key.InputHint, bool) { - focused, ok := q.handlers[state.focus] +func (q *keyQueue) InputHint(handlers map[event.Tag]*handler, state keyState) (key.InputHint, bool) { + focused, ok := handlers[state.focus] if !ok { return q.hint, false } old := q.hint - q.hint = focused.hint + q.hint = focused.key.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() { - for _, h := range q.handlers { - h.order = -1 - h.hint = key.HintAny - } q.order = q.order[:0] q.dirOrder = q.dirOrder[:0] } -func (q *keyQueue) ResetEvent(k event.Tag) (event.Event, bool) { - h, ok := q.handlers[k] - if !ok || h.reset { +func (k *keyHandler) ResetEvent() (event.Event, bool) { + if k.reset { return nil, false } - h.reset = true + k.reset = true return key.FocusEvent{Focus: false}, true } -func (q *keyQueue) Frame(state keyState) keyState { - for k, h := range q.handlers { - if !h.visible || !h.focusable { - if state.focus == k { - // Remove focus from the handler that is no longer focusable. - state.focus = nil - state.state = TextInputClose - } - if !h.visible && !h.focusable { - delete(q.handlers, k) - continue - } +func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState { + if state.focus != nil { + if h, ok := handlers[state.focus]; !ok || !h.key.isFocusable() { + // Remove focus from the handler that is no longer focusable. + state.focus = nil + state.state = TextInputClose } - h.visible = false - h.focusable = false - h.active = false } - q.updateFocusLayout() + q.updateFocusLayout(handlers) return state } @@ -137,7 +136,7 @@ func (q *keyQueue) Frame(state keyState) keyState { // containing it. Then, extend the handler bounds to a horizontal beam // and add to the row every handler whose center intersect it. Repeat // until no handlers remain. -func (q *keyQueue) updateFocusLayout() { +func (q *keyQueue) updateFocusLayout(handlers map[event.Tag]*handler) { order := q.dirOrder // Sort by ascending y position. sort.SliceStable(order, func(i, j int) bool { @@ -165,18 +164,18 @@ func (q *keyQueue) updateFocusLayout() { row++ } 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. -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 { return state, nil } order := 0 if state.focus != nil { - order = q.handlers[state.focus].dirOrder + order = handlers[state.focus].key.dirOrder } focus := q.dirOrder[order] switch dir { @@ -189,7 +188,7 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState, order = -1 } if state.focus != nil { - order = q.handlers[state.focus].order + order = handlers[state.focus].key.orderPlusOne - 1 if dir == key.FocusForward { order++ } else { @@ -197,7 +196,7 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState, } } 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: next := order 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) { newFocus := q.dirOrder[next] if newFocus.row == focus.row { - return q.Focus(state, newFocus.tag) + return q.Focus(handlers, state, newFocus.tag) } } case key.FocusUp, key.FocusDown: @@ -245,24 +244,24 @@ func (q *keyQueue) MoveFocus(state keyState, dir key.FocusDirection) (keyState, order += delta } if closest != nil { - return q.Focus(state, closest) + return q.Focus(handlers, state, closest) } } return state, nil } -func (q *keyQueue) BoundsFor(t event.Tag) image.Rectangle { - order := q.handlers[t].dirOrder +func (q *keyQueue) BoundsFor(k *keyHandler) image.Rectangle { + order := k.dirOrder return q.dirOrder[order].bounds } -func (q *keyQueue) AreaFor(t event.Tag) int { - order := q.handlers[t].dirOrder +func (q *keyQueue) AreaFor(k *keyHandler) int { + order := k.dirOrder return q.dirOrder[order].area } -func (q *keyQueue) Accepts(t event.Tag, e key.Event) bool { - for _, f := range q.handlers[t].filters { +func (k *keyHandler) Accepts(e key.Event) bool { + for _, f := range k.filter.filters { if keyFilterMatch(f, e) { return true } @@ -283,9 +282,9 @@ func keyFilterMatch(f key.Filter, e key.Event) bool { 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 _, exists := q.handlers[focus]; !exists { + if h, exists := handlers[focus]; !exists || !h.key.isFocusable() { focus = nil } } @@ -316,41 +315,26 @@ func (s keyState) softKeyboard(show bool) keyState { return s } -func (q *keyQueue) filter(tag event.Tag, f key.Filter) { - h := q.handlerFor(tag) - if !h.active { - h.active = true - h.filters = h.filters[:0] - } - h.filters = append(h.filters, f) +func (k *keyHandler) Filter(f key.Filter) { + k.nextFilter.filters = append(k.nextFilter.filters, f) } -func (q *keyQueue) focusable(tag event.Tag) { - h := q.handlerFor(tag) - h.focusable = true +func (k *keyHandler) isFocusable() bool { + return k.filter.focusable && k.visible } -func (q *keyQueue) handlerFor(tag event.Tag) *keyHandler { - h, ok := q.handlers[tag] - if !ok { - h = &keyHandler{order: -1} - if q.handlers == nil { - q.handlers = make(map[event.Tag]*keyHandler) - } - q.handlers[tag] = h - } - return h +func (k *keyHandler) Focusable() { + k.nextFilter.focusable = true } -func (q *keyQueue) inputOp(tag event.Tag, t f32.Affine2D, area int, bounds image.Rectangle) { - h := q.handlerFor(tag) - if h.order == -1 { - h.order = len(q.order) +func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) { + state.visible = true + if state.orderPlusOne == 0 { + state.orderPlusOne = len(q.order) + 1 q.order = append(q.order, tag) q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: tag, area: area, bounds: bounds}) } - h.visible = true - h.trans = t + state.trans = t } 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 } -func (q *keyQueue) editorState(state keyState) EditorState { +func (q *keyQueue) editorState(handlers map[event.Tag]*handler, state keyState) EditorState { s := state.content if f := state.focus; f != nil { - s.Selection.Transform = q.handlers[f].trans + s.Selection.Transform = handlers[f].key.trans } return s } diff --git a/io/input/pointer.go b/io/input/pointer.go index 1fd9f924..cf56e899 100644 --- a/io/input/pointer.go +++ b/io/input/pointer.go @@ -17,9 +17,8 @@ import ( ) type pointerQueue struct { - hitTree []hitNode - areas []areaNode - handlers map[event.Tag]*pointerHandler + hitTree []hitNode + areas []areaNode semantic struct { idsAssigned bool @@ -62,12 +61,22 @@ type pointerInfo 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 // the pointer.Cancel event that resets its state. - setup bool - active bool - types pointer.Kind + setup bool + // filter is the combined filter of every filter the handler has + // 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 scrollRange image.Rectangle @@ -228,37 +237,20 @@ func (c *pointerCollector) addHitNode(n hitNode) { } // 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() c.addHitNode(hitNode{ area: areaID, tag: tag, pass: c.state.pass > 0, }) - h := c.q.handlerFor(tag) - h.area = areaID - return h + state.areaPlusOne = areaID + 1 } -func (q *pointerQueue) handlerFor(tag event.Tag) *pointerHandler { - h, ok := q.handlers[tag] - if !ok { - h = &pointerHandler{ - 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 (s *pointerHandler) Reset() { + s.areaPlusOne = 0 + s.filter = s.nextFilter + s.nextFilter = pointerFilter{} } func (c *pointerCollector) actionInputOp(act system.Action) { @@ -288,25 +280,23 @@ func (q *pointerQueue) grab(state pointerState, req pointer.GrabCmd) (pointerSta return state, evts } -func (c *pointerCollector) inputOp(tag event.Tag) { +func (c *pointerCollector) inputOp(tag event.Tag, state *pointerHandler) { areaID := c.currentArea() area := &c.q.areas[areaID] area.semantic.content.tag = tag - c.newHandler(tag) + c.newHandler(tag, state) } -func (q *pointerQueue) filterTag(tag event.Tag, f pointer.Filter) { - h := q.handlerFor(tag) - h.types = h.types | f.Kinds - h.scrollRange = h.scrollRange.Union(f.ScrollBounds) +func (s *pointerHandler) Filter(tag event.Tag, f pointer.Filter) { + s.nextFilter.kinds = s.nextFilter.kinds | f.Kinds + s.nextFilter.scrollRange = s.nextFilter.scrollRange.Union(f.ScrollBounds) } -func (q *pointerQueue) ResetEvent(tag event.Tag) (event.Event, bool) { - h, ok := q.handlers[tag] - if !ok || h.setup { +func (s *pointerHandler) ResetEvent() (event.Event, bool) { + if s.setup { return nil, false } - h.setup = true + s.setup = true return pointer.Event{Kind: pointer.Cancel}, true } @@ -351,17 +341,15 @@ func (c *pointerCollector) cursor(cursor pointer.Cursor) { area.cursor = cursor } -func (q *pointerQueue) sourceFilter(tag event.Tag, f transfer.SourceFilter) { - h := q.handlerFor(tag) - h.sourceMimes = append(h.sourceMimes, f.Type) +func (s *pointerHandler) SourceFilter(tag event.Tag, f transfer.SourceFilter) { + s.nextFilter.sourceMimes = append(s.nextFilter.sourceMimes, f.Type) } -func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter) { - h := q.handlerFor(tag) - h.targetMimes = append(h.targetMimes, f.Type) +func (s *pointerHandler) TargetFilter(tag event.Tag, f transfer.TargetFilter) { + s.nextFilter.targetMimes = append(s.nextFilter.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 for i, p := range state.pointers { 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[i], evts = q.deliverTransferCancelEvent(p, evts) + state.pointers[i], evts = q.deliverTransferCancelEvent(handlers, p, evts) break } return state, evts } -func (c *pointerCollector) reset() { +func (c *pointerCollector) Reset() { c.q.reset() c.resetState() c.ensureRoot() @@ -537,19 +525,6 @@ func (q *pointerQueue) hitTest(pos f32.Point, onNode func(*hitNode) bool) pointe 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 { if areaIdx == -1 { return p @@ -574,10 +549,6 @@ func (q *pointerQueue) hit(areaIdx int, p f32.Point) (bool, pointer.Cursor) { } func (q *pointerQueue) reset() { - for _, h := range q.handlers { - // Reset handler. - h.area = -1 - } q.hitTree = q.hitTree[:0] q.areas = q.areas[:0] q.semantic.idsAssigned = false @@ -597,20 +568,14 @@ func (q *pointerQueue) reset() { } } -func (q *pointerQueue) Frame(state pointerState) (pointerState, []taggedEvent) { - for k, h := range q.handlers { - if !h.active { - state = dropHandler(state, k) - delete(q.handlers, k) - continue - } - h.active = false - if h.area != -1 { - area := &q.areas[h.area] - if h.types&(pointer.Press|pointer.Release) != 0 { +func (q *pointerQueue) Frame(handlers map[event.Tag]*handler, state pointerState) (pointerState, []taggedEvent) { + for _, h := range handlers { + if h.pointer.areaPlusOne != 0 { + area := &q.areas[h.pointer.areaPlusOne-1] + if h.pointer.filter.kinds&(pointer.Press|pointer.Release) != 0 { 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.valid = area.semantic.content.gestures != 0 @@ -619,7 +584,7 @@ func (q *pointerQueue) Frame(state pointerState) (pointerState, []taggedEvent) { var evts []taggedEvent for i, p := range state.pointers { 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 { state.pointers = append([]pointerInfo{}, state.pointers...) 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. -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 idx := len(q.hitTree) - 1 // Locate first potential receiver. @@ -679,23 +644,21 @@ func (q *pointerQueue) Deliver(areaIdx int, e pointer.Event) []taggedEvent { for idx != -1 { n := &q.hitTree[idx] idx = n.next - if n.tag == nil { - continue - } - h := q.handlers[n.tag] - if h == nil || e.Kind&h.types == 0 { + h, ok := handlers[n.tag] + if !ok || e.Kind&h.pointer.filter.kinds == 0 { continue } + f := h.pointer.filter e := e if e.Kind == pointer.Scroll { if sx == 0 && sy == 0 { break } // Distribute the scroll to the handler based on its ScrollRange. - sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X) - sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y) + sx, e.Scroll.X = setScrollEvent(sx, f.scrollRange.Min.X, f.scrollRange.Max.X) + 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}) if e.Kind != pointer.Scroll { break @@ -717,10 +680,10 @@ func (q *pointerQueue) SemanticArea(areaIdx int) (semanticContent, int) { 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 if e.Kind == pointer.Cancel { - for k := range q.handlers { + for k := range handlers { evts = append(evts, taggedEvent{ event: pointer.Event{Kind: pointer.Cancel}, tag: k, @@ -734,26 +697,26 @@ func (q *pointerQueue) Push(state pointerState, e pointer.Event) (pointerState, switch e.Kind { 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 - evts = q.deliverEvent(p, evts, e) + evts = q.deliverEvent(handlers, p, evts, e) case pointer.Move: if p.pressed { e.Kind = pointer.Drag } - p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) - evts = q.deliverEvent(p, evts, e) + p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e) + evts = q.deliverEvent(handlers, p, evts, e) if p.pressed { - p, evts = q.deliverDragEvent(p, evts) + p, evts = q.deliverDragEvent(handlers, p, evts) } case pointer.Release: - evts = q.deliverEvent(p, evts, e) + evts = q.deliverEvent(handlers, p, evts, e) p.pressed = false - p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) - p, evts = q.deliverDropEvent(p, evts) + p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e) + p, evts = q.deliverDropEvent(handlers, p, evts) case pointer.Scroll: - p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(state.cursor, p, evts, e) - evts = q.deliverEvent(p, evts, e) + p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e) + evts = q.deliverEvent(handlers, p, evts, e) default: panic("unsupported pointer event type") } @@ -770,7 +733,7 @@ func (q *pointerQueue) Push(state pointerState, e pointer.Event) (pointerState, 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 if p.pressed && len(p.handlers) == 1 { 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 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 sx == 0 && sy == 0 { return evts } // Distribute the scroll to the handler based on its ScrollRange. - sx, e.Scroll.X = setScrollEvent(sx, h.scrollRange.Min.X, h.scrollRange.Max.X) - sy, e.Scroll.Y = setScrollEvent(sy, h.scrollRange.Min.Y, h.scrollRange.Max.Y) + sx, e.Scroll.X = setScrollEvent(sx, f.scrollRange.Min.X, f.scrollRange.Max.X) + 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 } e := e @@ -795,38 +762,47 @@ func (q *pointerQueue) deliverEvent(p pointerInfo, evts []taggedEvent, e pointer foremost = false 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}) } 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 var hits []event.Tag if e.Source != pointer.Mouse && !p.pressed && e.Kind != pointer.Press { // Consider non-mouse pointers leaving when they're released. } else { - hits, cursor = q.opHit(e.Position) - if p.pressed { - // Filter out non-participating handlers, - // except potential transfer targets when a transfer has been initiated. - var hitsHaveTarget bool - if p.dataSource != nil { - transferSource := q.handlers[p.dataSource] - for _, hit := range hits { - if _, ok := firstMimeMatch(transferSource, q.handlers[hit]); ok { - hitsHaveTarget = true - break + var transSrc *pointerHandler + if p.dataSource != nil { + transSrc = &handlers[p.dataSource].pointer + } + cursor = q.hitTest(e.Position, func(n *hitNode) bool { + h, ok := handlers[n.tag] + if !ok { + return true + } + add := true + 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 _, found := searchTag(p.handlers, hits[i]); !found && !hitsHaveTarget { - hits = append(hits[:i], hits[i+1:]...) - } + if add { + hits = addHandler(hits, n.tag) } - } else { + return true + }) + if !p.pressed { changed = true p.handlers = hits } @@ -836,28 +812,34 @@ func (q *pointerQueue) deliverEnterLeaveEvents(cursor pointer.Cursor, p pointerI if _, found := searchTag(hits, k); found { continue } + h, ok := handlers[k] + if !ok { + continue + } changed = true - h := q.handlers[k] e := e e.Kind = pointer.Leave - if e.Kind&h.types != 0 { - e.Position = q.invTransform(h.area, e.Position) + if e.Kind&h.pointer.filter.kinds != 0 { + e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position) evts = append(evts, taggedEvent{tag: k, event: e}) } } // Deliver Enter events. for _, k := range hits { - h := q.handlers[k] if _, found := searchTag(p.entered, k); found { continue } + h, ok := handlers[k] + if !ok { + continue + } changed = true e := e e.Kind = pointer.Enter - if e.Kind&h.types != 0 { - e.Position = q.invTransform(h.area, e.Position) + if e.Kind&h.pointer.filter.kinds != 0 { + e.Position = q.invTransform(h.pointer.areaPlusOne-1, e.Position) 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 } -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 { return p, evts } // Identify the data source. for _, k := range p.entered { - src := q.handlers[k] - if len(src.sourceMimes) == 0 { + src := &handlers[k].pointer + if len(src.filter.sourceMimes) == 0 { continue } // One data source handler per pointer. p.dataSource = k // Notify all potential targets. - for k, tgt := range q.handlers { - if _, ok := firstMimeMatch(src, tgt); ok { + for k, tgt := range handlers { + if _, ok := firstMimeMatch(src, &tgt.pointer); ok { 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 } -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 { return p, evts } // Request data from the source. - src := q.handlers[p.dataSource] + src := &handlers[p.dataSource].pointer for _, k := range p.entered { - h := q.handlers[k] - if m, ok := firstMimeMatch(src, h); ok { + h := handlers[k] + if m, ok := firstMimeMatch(src, &h.pointer); ok { p.dataTarget = k evts = append(evts, taggedEvent{tag: p.dataSource, event: transfer.RequestEvent{Type: m}}) return p, evts } } // 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{}}) // Cancel all potential targets. - src := q.handlers[p.dataSource] - for k, h := range q.handlers { - if _, ok := firstMimeMatch(src, h); ok { + src := &handlers[p.dataSource].pointer + for k, h := range handlers { + if _, ok := firstMimeMatch(src, &h.pointer); ok { 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. func firstMimeMatch(src, tgt *pointerHandler) (first string, matched bool) { - for _, m1 := range tgt.targetMimes { - for _, m2 := range src.sourceMimes { + for _, m1 := range tgt.filter.targetMimes { + for _, m2 := range src.filter.sourceMimes { if m1 == m2 { return m1, true } diff --git a/io/input/router.go b/io/input/router.go index 288e7512..2fabe15c 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -27,6 +27,7 @@ import ( type Router struct { savedTrans []f32.Affine2D transStack []f32.Affine2D + handlers map[event.Tag]*handler pointer struct { queue pointerQueue collector pointerCollector @@ -101,6 +102,15 @@ const ( // By convention, the zero value denotes the non-existent ID. 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 // resulting from an incoming event. 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 { var events []event.Event + h := q.stateFor(k) // Record handler filters and add reset events. for _, f := range filters { switch f := f.(type) { case key.Filter: - q.key.queue.filter(k, f) + h.key.Filter(f) case key.FocusFilter: - q.key.queue.focusable(k) - if reset, ok := q.key.queue.ResetEvent(k); ok { + h.key.Focusable() + if reset, ok := h.key.ResetEvent(); ok { events = append(events, reset) } case pointer.Filter: - q.pointer.queue.filterTag(k, f) - if reset, ok := q.pointer.queue.ResetEvent(k); ok { + h.pointer.Filter(k, f) + if reset, ok := h.pointer.ResetEvent(); ok { events = append(events, reset) } case transfer.SourceFilter: - q.pointer.queue.sourceFilter(k, f) + h.pointer.SourceFilter(k, f) case transfer.TargetFilter: - q.pointer.queue.targetFilter(k, f) + h.pointer.TargetFilter(k, f) } } // 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 q.changes = append(q.changes[:0], stateChange{state: state}) } + for _, h := range q.handlers { + h.pointer.Reset() + h.key.Reset() + } var ops *ops.Ops if frame != nil { ops = &frame.Internal } q.reader.Reset(ops) q.collect() + for k, h := range q.handlers { + if !h.active { + delete(q.handlers, k) + } else { + h.active = false + } + } q.executeCommands() - q.changePointerState(q.pointer.queue.Frame(q.lastState().pointerState)) - kstate := q.key.queue.Frame(q.lastState().keyState) + q.changePointerState(q.pointer.queue.Frame(q.handlers, q.lastState().pointerState)) + kstate := q.key.queue.Frame(q.handlers, q.lastState().keyState) q.changeKeyState(kstate, nil) // Collapse state and events. q.collapseState(len(q.changes) - 1) @@ -258,7 +280,7 @@ func (q *Router) processEvent(e event.Event) bool { state := q.lastState() switch e := e.(type) { 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: return q.addEvents(q.queueKeyEvent(state.keyState, e)) case key.SnippetEvent: @@ -333,7 +355,7 @@ func (q *Router) executeCommands() { kstate := q.key.queue.setSelection(state.keyState, req) q.changeKeyState(kstate, nil) 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: kstate := state.keyState.softKeyboard(req.Show) q.changeKeyState(kstate, nil) @@ -341,7 +363,7 @@ func (q *Router) executeCommands() { kstate := q.key.queue.setSnippet(state.keyState, req) q.changeKeyState(kstate, nil) 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: q.cqueue.ProcessWriteClipboard(req) case clipboard.ReadCmd: @@ -404,10 +426,9 @@ func rangeNorm(r key.Range) key.Range { } func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent { - kq := &q.key.queue f := state.focus 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}) return evts } @@ -430,7 +451,7 @@ func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent { if n.tag == nil { continue } - if kq.Accepts(n.tag, e) { + if q.handlers[n.tag].key.Accepts(e) { evts = append(evts, taggedEvent{tag: n.tag, event: e}) break } @@ -439,7 +460,7 @@ func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent { } 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) } @@ -451,8 +472,9 @@ func (q *Router) RevealFocus(viewport image.Rectangle) { if focus == nil { return } - bounds := q.key.queue.BoundsFor(focus) - area := q.key.queue.AreaFor(focus) + kh := &q.handlers[focus].key + bounds := q.key.queue.BoundsFor(kh) + area := q.key.queue.AreaFor(kh) viewport = q.pointer.queue.ClipFor(area, viewport) topleft := bounds.Min.Sub(viewport.Min) @@ -478,8 +500,9 @@ func (q *Router) ScrollFocus(dist image.Point) { if focus == nil { return } - area := q.key.queue.AreaFor(focus) - q.addEvents(q.pointer.queue.Deliver(area, pointer.Event{ + kh := &q.handlers[focus].key + area := q.key.queue.AreaFor(kh) + q.addEvents(q.pointer.queue.Deliver(q.handlers, area, pointer.Event{ Kind: pointer.Scroll, Source: pointer.Touch, Scroll: f32internal.FPt(dist), @@ -517,17 +540,18 @@ func (q *Router) ClickFocus() { if focus == nil { 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) e := pointer.Event{ Position: f32.Pt(float32(center.X), float32(center.Y)), Source: pointer.Touch, } - area := q.key.queue.AreaFor(focus) + area := q.key.queue.AreaFor(kh) 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 - 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 @@ -540,7 +564,7 @@ func (q *Router) TextInputState() TextInputState { // TextInputHint returns the input mode from the most recent key.InputOp. 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 @@ -576,14 +600,27 @@ func (q *Router) AppendSemantics(nodes []SemanticNode) []SemanticNode { // EditorState returns the editor state for the focused handler, or the // zero value if there is none. 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() { q.transStack = q.transStack[:0] pc := &q.pointer.collector pc.q = &q.pointer.queue - pc.reset() + pc.Reset() kq := &q.key.queue q.key.queue.Reset() var t f32.Affine2D @@ -628,10 +665,13 @@ func (q *Router) collect() { case ops.TypeInput: tag := encOp.Refs[0].(event.Tag) - pc.inputOp(tag) + s := q.stateFor(tag) + pc.inputOp(tag, &s.pointer) a := pc.currentArea() b := pc.currentAreaBounds() - kq.inputOp(tag, t, a, b) + if s.filter.focusable { + kq.inputOp(tag, &s.key, t, a, b) + } // Pointer ops. case ops.TypePass: @@ -649,7 +689,8 @@ func (q *Router) collect() { Tag: encOp.Refs[0].(event.Tag), Hint: key.InputHint(encOp.Data[1]), } - kq.inputHint(op) + s := q.stateFor(op.Tag) + s.key.inputHint(op.Hint) // Semantic ops. case ops.TypeSemanticLabel: