all: [API] deliver key events to the first matching filter

Replace the key.Filter.Target field with a Focus field that matches only
of the specified tag has focus. This has the advantage of simpler event
delivery and for lower latency in delivering key events to new handlers.

For example, consider a UI where a button is activated by a key press,
which is turn displays a dialog with another button activated by the
same key. This change allows two button press(+releases) in the same frame
to arrive at the intended targets: one key press(+release) for each
button.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-26 17:54:51 -06:00
parent d9a007586c
commit ed0d5d5767
9 changed files with 169 additions and 184 deletions
+16 -27
View File
@@ -49,10 +49,7 @@ type keyHandler struct {
trans f32.Affine2D
}
type keyFilter struct {
focusable bool
filters []key.Filter
}
type keyFilter []key.Filter
type dirFocusEntry struct {
tag event.Tag
@@ -112,7 +109,7 @@ func (k *keyHandler) ResetEvent() (event.Event, bool) {
func (q *keyQueue) Frame(handlers map[event.Tag]*handler, state keyState) keyState {
if state.focus != nil {
if h, ok := handlers[state.focus]; !ok || !h.filter.key.focusable || !h.key.visible {
if h, ok := handlers[state.focus]; !ok || !h.filter.focusable || !h.key.visible {
// Remove focus from the handler that is no longer focusable.
state.focus = nil
state.state = TextInputClose
@@ -253,21 +250,19 @@ func (q *keyQueue) AreaFor(k *keyHandler) int {
return q.dirOrder[order].area
}
func (k *keyFilter) Matches(e event.Event) bool {
switch e := e.(type) {
case key.Event:
for _, f := range k.filters {
if keyFilterMatch(f, e) {
return true
}
func (k *keyFilter) Matches(focus event.Tag, e key.Event) bool {
for _, f := range *k {
if keyFilterMatch(focus, f, e) {
return true
}
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
return k.focusable
}
return false
}
func keyFilterMatch(f key.Filter, e key.Event) bool {
func keyFilterMatch(focus event.Tag, f key.Filter, e key.Event) bool {
if f.Focus != nil && f.Focus != focus {
return false
}
if f.Name != e.Name {
return false
}
@@ -308,23 +303,17 @@ func (s keyState) softKeyboard(show bool) keyState {
return s
}
func (k *keyFilter) Add(f event.Filter) {
switch f := f.(type) {
case key.FocusFilter:
k.focusable = true
case key.Filter:
for _, f2 := range k.filters {
if f == f2 {
return
}
func (k *keyFilter) Add(f key.Filter) {
for _, f2 := range *k {
if f == f2 {
return
}
k.filters = append(k.filters, f)
}
*k = append(*k, f)
}
func (k *keyFilter) Merge(k2 keyFilter) {
k.focusable = k.focusable || k2.focusable
k.filters = append(k.filters, k2.filters...)
*k = append(*k, k2...)
}
func (q *keyQueue) inputOp(tag event.Tag, state *keyHandler, t f32.Affine2D, area int, bounds image.Rectangle) {
-3
View File
@@ -69,9 +69,6 @@ func events(r *Router, h event.Tag, filters ...event.Filter) []event.Event {
// Hack to facilitate transition to per-filter tags.
for i, f := range filters {
switch f := f.(type) {
case key.Filter:
f.Target = h
filters[i] = f
case key.FocusFilter:
f.Target = h
filters[i] = f
+66 -61
View File
@@ -33,6 +33,12 @@ type Router struct {
}
key struct {
queue keyQueue
// The following fields have the same purpose as the fields in
// type handler, but for key.Events.
filter keyFilter
nextFilter keyFilter
processedFilter keyFilter
scratchFilter keyFilter
}
cqueue clipboardQueue
// states is the list of pending state changes resulting from
@@ -120,8 +126,8 @@ type handler struct {
// filter is the union of a set of [io/event.Filters].
type filter struct {
pointer pointerFilter
key keyFilter
pointer pointerFilter
focusable bool
}
// taggedFilter is a filter for a particular tag.
@@ -183,11 +189,13 @@ func (s Source) Event(filters ...event.Filter) (event.Event, bool) {
func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
// Merge filters into scratch filters.
q.scratchFilters = q.scratchFilters[:0]
q.key.scratchFilter = q.key.scratchFilter[:0]
for _, f := range filters {
var t event.Tag
switch f := f.(type) {
case key.Filter:
t = f.Target
q.key.scratchFilter = append(q.key.scratchFilter, f)
continue
case transfer.SourceFilter:
t = f.Target
case transfer.TargetFilter:
@@ -210,8 +218,17 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
}
if filter == nil {
n := len(q.scratchFilters)
q.scratchFilters = append(q.scratchFilters, taggedFilter{tag: t})
filter = &q.scratchFilters[n].filter
if n < cap(q.scratchFilters) {
// Re-use previously allocated filter.
q.scratchFilters = q.scratchFilters[:n+1]
tf := &q.scratchFilters[n]
tf.tag = t
filter = &tf.filter
filter.Reset()
} else {
q.scratchFilters = append(q.scratchFilters, taggedFilter{tag: t})
filter = &q.scratchFilters[n].filter
}
}
filter.Add(f)
}
@@ -220,6 +237,8 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
h.filter.Merge(tf.filter)
h.nextFilter.Merge(tf.filter)
}
q.key.filter = append(q.key.filter, q.key.scratchFilter...)
q.key.nextFilter = append(q.key.nextFilter, q.key.scratchFilter...)
// Deliver reset event, if any.
for _, f := range filters {
switch f := f.(type) {
@@ -245,14 +264,24 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
for i := range q.changes {
change := &q.changes[i]
for j, evt := range change.events {
for _, tf := range q.scratchFilters {
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
change.events = append(change.events[:j], change.events[j+1:]...)
// Fast forward state to last matched.
q.collapseState(i)
return evt.event, true
match := false
switch e := evt.event.(type) {
case key.Event:
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e)
default:
for _, tf := range q.scratchFilters {
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
match = true
break
}
}
}
if match {
change.events = append(change.events[:j], change.events[j+1:]...)
// Fast forward state to last matched.
q.collapseState(i)
return evt.event, true
}
}
}
}
@@ -260,6 +289,7 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
h := q.stateFor(tf.tag)
h.processedFilter.Merge(tf.filter)
}
q.key.processedFilter = append(q.key.processedFilter, q.key.scratchFilter...)
return nil, false
}
@@ -308,6 +338,8 @@ func (q *Router) Frame(frame *op.Ops) {
h.pointer.Reset()
h.key.Reset()
}
q.key.filter, q.key.nextFilter = q.key.nextFilter, q.key.filter
q.key.nextFilter = q.key.nextFilter[:0]
var ops *ops.Ops
if frame != nil {
ops = &frame.Internal
@@ -348,21 +380,10 @@ func (q *Router) Queue(events ...event.Event) bool {
return matched
}
func (f *filter) Reset() {
*f = filter{
key: keyFilter{
// Re-use filter slice storage.
filters: f.key.filters[:0],
},
}
}
func (f *filter) Add(flt event.Filter) {
switch flt := flt.(type) {
case key.Filter:
f.key.Add(flt)
case key.FocusFilter:
f.key.Add(flt)
f.focusable = true
case pointer.Filter:
f.pointer.Add(flt)
case transfer.SourceFilter, transfer.TargetFilter:
@@ -372,12 +393,26 @@ func (f *filter) Add(flt event.Filter) {
// Merge f2 into f.
func (f *filter) Merge(f2 filter) {
f.key.Merge(f2.key)
f.focusable = f.focusable || f2.focusable
f.pointer.Merge(f2.pointer)
}
func (f *filter) Matches(e event.Event) bool {
return f.key.Matches(e) || f.pointer.Matches(e)
switch e.(type) {
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
return f.focusable
default:
return f.pointer.Matches(e)
}
}
func (f *filter) Reset() {
*f = filter{
pointer: pointerFilter{
sourceMimes: f.pointer.sourceMimes[:0],
targetMimes: f.pointer.targetMimes[:0],
},
}
}
func (q *Router) processEvent(e event.Event) bool {
@@ -388,7 +423,11 @@ func (q *Router) processEvent(e event.Event) bool {
state.pointerState = pstate
return q.changeState(e, state, evts)
case key.Event:
return q.changeState(e, state, q.queueKeyEvent(state.keyState, e))
var evts []taggedEvent
if q.key.filter.Matches(state.keyState.focus, e) {
evts = append(evts, taggedEvent{event: e})
}
return q.changeState(e, state, evts)
case key.SnippetEvent:
// Expand existing, overlapping snippet.
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
@@ -549,40 +588,6 @@ func rangeNorm(r key.Range) key.Range {
return r
}
func (q *Router) queueKeyEvent(state keyState, e key.Event) []taggedEvent {
f := state.focus
var evts []taggedEvent
if f != nil && q.handlers[f].filter.key.Matches(e) {
evts = append(evts, taggedEvent{tag: f, event: e})
return evts
}
pq := &q.pointer.queue
idx := len(pq.hitTree) - 1
focused := f != nil
if focused {
// If there is a focused tag, traverse its ancestry through the
// hit tree to search for handlers.
for ; pq.hitTree[idx].tag != f; idx-- {
}
}
for idx != -1 {
n := &pq.hitTree[idx]
if focused {
idx = n.next
} else {
idx--
}
if n.tag == nil {
continue
}
if q.handlers[n.tag].filter.key.Matches(e) {
evts = append(evts, taggedEvent{tag: n.tag, event: e})
break
}
}
return evts
}
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
state := q.lastState()
kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
@@ -795,7 +800,7 @@ func (q *Router) collect() {
pc.inputOp(tag, &s.pointer)
a := pc.currentArea()
b := pc.currentAreaBounds()
if s.filter.key.focusable {
if s.filter.focusable {
kq.inputOp(tag, &s.key, t, a, b)
}
+4 -8
View File
@@ -1,12 +1,6 @@
// SPDX-License-Identifier: Unlicense OR MIT
/*
Package key implements key and text events and operations.
The InputOp operations is used for declaring key input handlers. Use
an implementation of the Queue interface from package ui to receive
events.
*/
// Package key implements key and text events and operations.
package key
import (
@@ -20,7 +14,9 @@ import (
// Filter matches any [Event] that matches the parameters.
type Filter struct {
Target event.Tag
// Focus is the tag that must be focused for the filter to match. It has no effect
// if it is nil.
Focus event.Tag
// Required is the set of modifiers that must be included in events matched.
Required Modifiers
// Optional is the set of modifiers that may be included in events matched.