io/input: implement key.Filter.Name special case for matching every key

The empty key.Filter.Name now means matching every key name. This is a
replacement for the previous special case where the top-level key.InputOp
handler would get all unmatched events.

Add special case for system events such as focus switch shortcuts.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-11-28 19:11:22 -06:00
parent 77ff21605c
commit c3f2abebca
4 changed files with 87 additions and 54 deletions
+4 -4
View File
@@ -250,20 +250,20 @@ func (q *keyQueue) AreaFor(k *keyHandler) int {
return q.dirOrder[order].area
}
func (k *keyFilter) Matches(focus event.Tag, e key.Event) bool {
func (k *keyFilter) Matches(focus event.Tag, e key.Event, system bool) bool {
for _, f := range *k {
if keyFilterMatch(focus, f, e) {
if keyFilterMatch(focus, f, e, system) {
return true
}
}
return false
}
func keyFilterMatch(focus event.Tag, f key.Filter, e key.Event) bool {
func keyFilterMatch(focus event.Tag, f key.Filter, e key.Event, system bool) bool {
if f.Focus != nil && f.Focus != focus {
return false
}
if f.Name != e.Name {
if (f.Name != "" || system) && f.Name != e.Name {
return false
}
if e.Modifiers&f.Required != f.Required {
+22 -2
View File
@@ -14,6 +14,24 @@ import (
"gioui.org/op/clip"
)
func TestAllMatchKeyFilter(t *testing.T) {
r := new(Router)
r.Event(key.Filter{})
ke := key.Event{Name: "A"}
r.Queue(ke)
// Catch-all gets all non-system events.
assertEventSequence(t, events(r, -1, key.Filter{}), ke)
r = new(Router)
r.Event(key.Filter{Name: "A"})
r.Queue(SystemEvent{ke})
if _, handled := r.WakeupTime(); !handled {
t.Errorf("system event was unexpectedly ignored")
}
// Only specific filters match system events.
assertEventSequence(t, events(r, -1, key.Filter{Name: "A"}), ke)
}
func TestInputHint(t *testing.T) {
r := new(Router)
if hint, changed := r.TextInputHint(); hint != key.HintAny || changed {
@@ -66,11 +84,13 @@ func TestInputWakeup(t *testing.T) {
t.Errorf("InputOp or the resetting FocusEvent triggered a wakeup")
}
// And neither does events that don't match anything.
if r.Queue(key.SnippetEvent{}) {
r.Queue(key.SnippetEvent{})
if _, handled := r.WakeupTime(); handled {
t.Errorf("a not-matching event triggered a wakeup")
}
// However, events that does match should trigger wakeup.
if !r.Queue(key.Event{Name: "A"}) {
r.Queue(key.Event{Name: "A"})
if _, handled := r.WakeupTime(); !handled {
t.Errorf("a key.Event didn't trigger redraw")
}
}
+31 -24
View File
@@ -107,6 +107,12 @@ const (
// By convention, the zero value denotes the non-existent ID.
type SemanticID uint
// SystemEvent is a marker for events that have platform specific
// side-effects. SystemEvents are never matched by catch-all filters.
type SystemEvent struct {
Event event.Event
}
// handler contains the per-handler state tracked by a [Router].
type handler struct {
// active tracks whether the handler was active in the current
@@ -267,7 +273,7 @@ func (q *Router) Event(filters ...event.Filter) (event.Event, bool) {
match := false
switch e := evt.event.(type) {
case key.Event:
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e)
match = q.key.scratchFilter.Matches(change.state.keyState.focus, e, false)
default:
for _, tf := range q.scratchFilters {
if evt.tag == tf.tag && tf.filter.Matches(evt.event) {
@@ -363,21 +369,17 @@ func (q *Router) Frame(frame *op.Ops) {
// Collapse state and events.
q.collapseState(len(q.changes) - 1)
if len(q.changes) > 0 && len(q.changes[0].events) > 0 {
q.wakeup = true
q.wakeupTime = time.Time{}
}
}
// Queue events and report whether at least one event matched a handler.
func (q *Router) Queue(events ...event.Event) bool {
matched := false
func (q *Router) Queue(events ...event.Event) {
for _, e := range events {
hadEvents := q.processEvent(e)
matched = matched || hadEvents
se, system := e.(SystemEvent)
if system {
e = se.Event
}
q.processEvent(e, system)
}
return matched
}
func (f *filter) Add(flt event.Filter) {
@@ -415,19 +417,19 @@ func (f *filter) Reset() {
}
}
func (q *Router) processEvent(e event.Event) bool {
func (q *Router) processEvent(e event.Event, system bool) {
state := q.lastState()
switch e := e.(type) {
case pointer.Event:
pstate, evts := q.pointer.queue.Push(q.handlers, state.pointerState, e)
state.pointerState = pstate
return q.changeState(e, state, evts)
q.changeState(e, state, evts)
case key.Event:
var evts []taggedEvent
if q.key.filter.Matches(state.keyState.focus, e) {
if q.key.filter.Matches(state.keyState.focus, e, system) {
evts = append(evts, taggedEvent{event: e})
}
return q.changeState(e, state, evts)
q.changeState(e, state, evts)
case key.SnippetEvent:
// Expand existing, overlapping snippet.
if r := state.content.Snippet.Range; rangeOverlaps(r, key.Range(e)) {
@@ -442,17 +444,17 @@ func (q *Router) processEvent(e event.Event) bool {
if f := state.focus; f != nil {
evts = append(evts, taggedEvent{tag: f, event: e})
}
return q.changeState(e, state, evts)
q.changeState(e, state, evts)
case key.EditEvent, key.FocusEvent, key.SelectionEvent:
var evts []taggedEvent
if f := state.focus; f != nil {
evts = append(evts, taggedEvent{tag: f, event: e})
}
return q.changeState(e, state, evts)
q.changeState(e, state, evts)
case transfer.DataEvent:
cstate, evts := q.cqueue.Push(state.clipboardState, e)
state.clipboardState = cstate
return q.changeState(e, state, evts)
q.changeState(e, state, evts)
default:
panic("unknown event type")
}
@@ -541,7 +543,7 @@ func (q *Router) executeCommand(c Command) stateChange {
return stateChange{state: state, events: evts}
}
func (q *Router) changeState(e event.Event, state inputState, evts []taggedEvent) bool {
func (q *Router) changeState(e event.Event, state inputState, evts []taggedEvent) {
// Wrap pointer.DataEvent.Open functions to detect them not being called.
for i := range evts {
e := &evts[i]
@@ -571,7 +573,6 @@ func (q *Router) changeState(e event.Event, state inputState, evts []taggedEvent
prev.state = state
prev.events = append(prev.events, evts...)
}
return len(evts) > 0
}
func rangeOverlaps(r1, r2 key.Range) bool {
@@ -588,11 +589,11 @@ func rangeNorm(r key.Range) key.Range {
return r
}
func (q *Router) MoveFocus(dir key.FocusDirection) bool {
func (q *Router) MoveFocus(dir key.FocusDirection) {
state := q.lastState()
kstate, evts := q.key.queue.MoveFocus(q.handlers, state.keyState, dir)
state.keyState = kstate
return q.changeState(nil, state, evts)
q.changeState(nil, state, evts)
}
// RevealFocus scrolls the current focus (if any) into viewport
@@ -852,9 +853,13 @@ func (q *Router) collect() {
// WakeupTime returns the most recent time for doing another frame,
// as determined from the last call to Frame.
func (q *Router) WakeupTime() (time.Time, bool) {
w := q.wakeup
t, w := q.wakeupTime, q.wakeup
q.wakeup = false
return q.wakeupTime, w
// Pending events always trigger wakeups.
if len(q.changes) > 1 || len(q.changes) == 1 && len(q.changes[0].events) > 0 {
t, w = time.Time{}, true
}
return t, w
}
func (s SemanticGestures) String() string {
@@ -864,3 +869,5 @@ func (s SemanticGestures) String() string {
}
return strings.Join(gestures, ",")
}
func (SystemEvent) ImplementsEvent() {}