From d25912678c6bea9c51cfd360e121c681ed245930 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 14 Nov 2023 18:23:57 -0600 Subject: [PATCH] io/input: deliver reset events lazily Refactor delivery of reset events to be resolved and delivered as part of Source.Events. This is a preparation for changing event handling to be lazy. Reset events are delivered to event handlers that are either new or haven't been active in the previous frame for a particular event type (pointer or key events), to ensure the handler state is reset. Signed-off-by: Elias Naur --- io/input/key.go | 24 ++++++++++++++--------- io/input/key_test.go | 45 ++++++++++++++++++++------------------------ io/input/pointer.go | 39 +++++++++++++++++++++++--------------- io/input/router.go | 25 +++++++++++++----------- 4 files changed, 73 insertions(+), 60 deletions(-) diff --git a/io/input/key.go b/io/input/key.go index cdecb3f6..98a0e26b 100644 --- a/io/input/key.go +++ b/io/input/key.go @@ -36,8 +36,10 @@ type keyQueue struct { type keyHandler struct { // visible will be true if the InputOp is present // in the current frame. - visible bool - new bool + visible bool + // reset tracks whether the handler has seen a + // focus reset. + reset bool focusable bool active bool hint key.InputHint @@ -97,7 +99,16 @@ func (q *keyQueue) Reset() { q.dirOrder = q.dirOrder[:0] } -func (q *keyQueue) Frame(events *handlerEvents) { +func (q *keyQueue) ResetEvent(k event.Tag) (event.Event, bool) { + h, ok := q.handlers[k] + if !ok || h.reset { + return nil, false + } + h.reset = true + return key.FocusEvent{Focus: false}, true +} + +func (q *keyQueue) Frame() { for k, h := range q.handlers { if !h.visible || !h.focusable { if q.focus == k { @@ -110,11 +121,6 @@ func (q *keyQueue) Frame(events *handlerEvents) { continue } } - if h.new && k != q.focus { - // Reset the handler on (each) first appearance, but don't trigger redraw. - events.AddNoRedraw(k, key.FocusEvent{Focus: false}) - } - h.new = false h.visible = false h.focusable = false h.active = false @@ -325,7 +331,7 @@ func (q *keyQueue) focusable(tag event.Tag) { func (q *keyQueue) handlerFor(tag event.Tag) *keyHandler { h, ok := q.handlers[tag] if !ok { - h = &keyHandler{new: true, order: -1} + h = &keyHandler{order: -1} if q.handlers == nil { q.handlers = make(map[event.Tag]*keyHandler) } diff --git a/io/input/key_test.go b/io/input/key_test.go index 12e277f0..e4a707aa 100644 --- a/io/input/key_test.go +++ b/io/input/key_test.go @@ -22,13 +22,12 @@ func TestInputWakeup(t *testing.T) { var r Router // Test that merely adding a handler doesn't trigger redraw. - r.Events(handler, key.FocusFilter{}) + evts := r.Events(handler, key.FocusFilter{}) r.Frame(&ops) if _, wake := r.WakeupTime(); wake { t.Errorf("adding key.InputOp triggered a redraw") } - // However, adding a handler queues a Focus(false) event. - if evts := r.Events(handler, key.FocusFilter{}); len(evts) != 1 { + if len(evts) != 1 { t.Errorf("no Focus event for newly registered key.InputOp") } } @@ -47,15 +46,14 @@ func TestKeyMultiples(t *testing.T) { event.InputOp(ops, &handlers[2]) for i := range handlers { - r.Events(&handlers[i], key.FocusFilter{}) + assertKeyEvent(t, r.Events(&handlers[i], key.FocusFilter{}), false) } r.Frame(ops) - assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false) - assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false) assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), true) assertFocus(t, r, &handlers[2]) + assertKeyboard(t, r, TextInputOpen) } @@ -74,15 +72,12 @@ func TestKeyStacked(t *testing.T) { event.InputOp(ops, &handlers[3]) for i := range handlers { - r.Events(&handlers[i], key.FocusFilter{}) + assertKeyEvent(t, r.Events(&handlers[i], key.FocusFilter{}), false) } r.Frame(ops) - assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false) assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), true) - assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), false) - assertKeyEvent(t, r.Events(&handlers[3], key.FocusFilter{}), false) assertFocus(t, r, &handlers[1]) assertKeyboard(t, r, TextInputOpen) } @@ -119,7 +114,7 @@ func TestKeyRemoveFocus(t *testing.T) { key.Filter{Name: key.NameTab, Required: key.ModShortcut}, } for i := range handlers { - r.Events(&handlers[i], filters...) + assertKeyEvent(t, r.Events(&handlers[i], filters...), false) } r.Frame(ops) @@ -129,7 +124,6 @@ func TestKeyRemoveFocus(t *testing.T) { r.Queue(evt) assertKeyEvent(t, r.Events(&handlers[0], filters...), true, evt) - assertKeyEvent(t, r.Events(&handlers[1], filters...), false) assertFocus(t, r, &handlers[0]) assertKeyboard(t, r, TextInputOpen) @@ -195,13 +189,12 @@ func TestKeyFocusedInvisible(t *testing.T) { event.InputOp(ops, &handlers[1]) for i := range handlers { - r.Events(&handlers[i], key.FocusFilter{}) + assertKeyEvent(t, r.Events(&handlers[i], key.FocusFilter{}), false) } r.Frame(ops) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), true) - assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false) assertFocus(t, r, &handlers[0]) assertKeyboard(t, r, TextInputOpen) @@ -238,12 +231,11 @@ func TestKeyFocusedInvisible(t *testing.T) { event.InputOp(ops, &handlers[1]) for i := range handlers { - r.Events(&handlers[i], key.FocusFilter{}) + assertKeyEventUnexpected(t, r.Events(&handlers[i], key.FocusFilter{})) } r.Frame(ops) - assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false) assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{})) assertFocus(t, r, nil) assertKeyboard(t, r, TextInputClose) @@ -393,11 +385,15 @@ func TestKeyRouting(t *testing.T) { // With no focus, the events should traverse the final branch of the hit tree // searching for handlers. - assertKeyEvent(t, r.Events(&handlers[4], fa...), false, A) - assertKeyEvent(t, r.Events(&handlers[3], key.FocusFilter{}), false) - assertKeyEvent(t, r.Events(&handlers[2], fa...), false) - assertKeyEvent(t, r.Events(&handlers[1], fb...), false, B) - assertKeyEvent(t, r.Events(&handlers[0], fa...), false) + if evts := r.Events(&handlers[4], fa...); len(evts) != 1 || evts[0] != A { + t.Errorf("expected key event") + } + r.Events(&handlers[3], key.FocusFilter{}) + r.Events(&handlers[2], fa...) + if evts := r.Events(&handlers[1], fb...); len(evts) != 1 || evts[0] != B { + t.Errorf("expected key event") + } + r.Events(&handlers[0], fa...) r2 := new(Router) @@ -414,11 +410,10 @@ func TestKeyRouting(t *testing.T) { // With focus, the events should traverse the branch of the hit tree // containing the focused element. - assertKeyEvent(t, r2.Events(&handlers[4], fa...), false) assertKeyEvent(t, r2.Events(&handlers[3], key.FocusFilter{}), true) - assertKeyEvent(t, r2.Events(&handlers[2], fa...), false) - assertKeyEvent(t, r2.Events(&handlers[1], fb...), false) - assertKeyEvent(t, r2.Events(&handlers[0], fa...), false, A) + if evts := r2.Events(&handlers[0], fa...); len(evts) != 1 || evts[0] != A { + t.Errorf("expected key event") + } } func assertKeyEvent(t *testing.T, events []event.Event, expectedFocus bool, expectedInputs ...event.Event) { diff --git a/io/input/pointer.go b/io/input/pointer.go index 758cd5a4..243ade19 100644 --- a/io/input/pointer.go +++ b/io/input/pointer.go @@ -61,7 +61,10 @@ type pointerInfo struct { } type pointerHandler struct { - area int + area int + // setup tracks whether the handler has received + // the pointer.Cancel event that resets its state. + setup bool active bool types pointer.Kind // min and max horizontal/vertical scroll @@ -224,19 +227,19 @@ func (c *pointerCollector) addHitNode(n hitNode) { } // newHandler returns the current handler or a new one for tag. -func (c *pointerCollector) newHandler(tag event.Tag, events *handlerEvents) *pointerHandler { +func (c *pointerCollector) newHandler(tag event.Tag) *pointerHandler { areaID := c.currentArea() c.addHitNode(hitNode{ area: areaID, tag: tag, pass: c.state.pass > 0, }) - h := c.q.handlerFor(tag, events) + h := c.q.handlerFor(tag) h.area = areaID return h } -func (q *pointerQueue) handlerFor(tag event.Tag, events *handlerEvents) *pointerHandler { +func (q *pointerQueue) handlerFor(tag event.Tag) *pointerHandler { h, ok := q.handlers[tag] if !ok { h = &pointerHandler{ @@ -246,9 +249,6 @@ func (q *pointerQueue) handlerFor(tag event.Tag, events *handlerEvents) *pointer q.handlers = make(map[event.Tag]*pointerHandler) } q.handlers[tag] = h - // Cancel handlers on (each) first appearance, but don't - // trigger redraw. - events.AddNoRedraw(tag, pointer.Event{Kind: pointer.Cancel}) } if !h.active { h.types = 0 @@ -286,19 +286,28 @@ func (q *pointerQueue) grab(req pointer.GrabCmd, events *handlerEvents) { } } -func (c *pointerCollector) inputOp(tag event.Tag, events *handlerEvents) { +func (c *pointerCollector) inputOp(tag event.Tag) { areaID := c.currentArea() area := &c.q.areas[areaID] area.semantic.content.tag = tag - c.newHandler(tag, events) + c.newHandler(tag) } -func (q *pointerQueue) filterTag(tag event.Tag, f pointer.Filter, events *handlerEvents) { - h := q.handlerFor(tag, events) +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 (q *pointerQueue) ResetEvent(tag event.Tag) (event.Event, bool) { + h, ok := q.handlers[tag] + if !ok || h.setup { + return nil, false + } + h.setup = true + return pointer.Event{Kind: pointer.Cancel}, true +} + func (c *pointerCollector) semanticLabel(lbl string) { areaID := c.currentArea() area := &c.q.areas[areaID] @@ -340,13 +349,13 @@ func (c *pointerCollector) cursor(cursor pointer.Cursor) { area.cursor = cursor } -func (q *pointerQueue) sourceFilter(tag event.Tag, f transfer.SourceFilter, events *handlerEvents) { - h := q.handlerFor(tag, events) +func (q *pointerQueue) sourceFilter(tag event.Tag, f transfer.SourceFilter) { + h := q.handlerFor(tag) h.sourceMimes = append(h.sourceMimes, f.Type) } -func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter, events *handlerEvents) { - h := q.handlerFor(tag, events) +func (q *pointerQueue) targetFilter(tag event.Tag, f transfer.TargetFilter) { + h := q.handlerFor(tag) h.targetMimes = append(h.targetMimes, f.Type) } diff --git a/io/input/router.go b/io/input/router.go index d5aafb9c..1a9f36de 100644 --- a/io/input/router.go +++ b/io/input/router.go @@ -129,22 +129,29 @@ 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 resetEvents []event.Event for _, f := range filters { switch f := f.(type) { case key.Filter: q.key.queue.filter(k, f) case key.FocusFilter: q.key.queue.focusable(k) + if reset, ok := q.key.queue.ResetEvent(k); ok { + resetEvents = append(resetEvents, reset) + } case pointer.Filter: - q.pointer.queue.filterTag(k, f, &q.handlers) + q.pointer.queue.filterTag(k, f) + if reset, ok := q.pointer.queue.ResetEvent(k); ok { + resetEvents = append(resetEvents, reset) + } case transfer.SourceFilter: - q.pointer.queue.sourceFilter(k, f, &q.handlers) + q.pointer.queue.sourceFilter(k, f) case transfer.TargetFilter: - q.pointer.queue.targetFilter(k, f, &q.handlers) + q.pointer.queue.targetFilter(k, f) } } events := q.handlers.Events(k, filters...) - return events + return append(resetEvents, events...) } // Frame replaces the declared handlers from the supplied @@ -161,7 +168,7 @@ func (q *Router) Frame(frame *op.Ops) { q.collect() q.executeCommands() q.pointer.queue.Frame(&q.handlers) - q.key.queue.Frame(&q.handlers) + q.key.queue.Frame() if q.handlers.HadEvents() { q.wakeup = true @@ -461,7 +468,7 @@ func (q *Router) collect() { case ops.TypeInput: tag := encOp.Refs[0].(event.Tag) - pc.inputOp(tag, &q.handlers) + pc.inputOp(tag) a := pc.currentArea() b := pc.currentAreaBounds() kq.inputOp(tag, t, a, b) @@ -522,13 +529,9 @@ func (h *handlerEvents) init() { } } -func (h *handlerEvents) AddNoRedraw(k event.Tag, e event.Event) { +func (h *handlerEvents) Add(k event.Tag, e event.Event) { h.init() h.handlers[k] = append(h.handlers[k], e) -} - -func (h *handlerEvents) Add(k event.Tag, e event.Event) { - h.AddNoRedraw(k, e) h.hadEvents = true }