mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
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:
+30
-24
@@ -558,8 +558,9 @@ func (c *callbacks) SetEditorSnippet(r key.Range) {
|
||||
c.Event(key.SnippetEvent(r))
|
||||
}
|
||||
|
||||
func (w *Window) moveFocus(dir key.FocusDirection, d driver) {
|
||||
if w.queue.MoveFocus(dir) {
|
||||
func (w *Window) moveFocus(dir key.FocusDirection) {
|
||||
w.queue.MoveFocus(dir)
|
||||
if _, handled := w.queue.WakeupTime(); handled {
|
||||
w.queue.RevealFocus(w.viewport)
|
||||
} else {
|
||||
var v image.Point
|
||||
@@ -881,32 +882,37 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
|
||||
w.out <- e2
|
||||
case wakeupEvent:
|
||||
case event.Event:
|
||||
handled := w.queue.Queue(e2)
|
||||
if e, ok := e.(key.Event); ok && !handled {
|
||||
if e.State == key.Press {
|
||||
handled = true
|
||||
isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android"
|
||||
switch {
|
||||
case e.Name == key.NameTab && e.Modifiers == 0:
|
||||
w.moveFocus(key.FocusForward, d)
|
||||
case e.Name == key.NameTab && e.Modifiers == key.ModShift:
|
||||
w.moveFocus(key.FocusBackward, d)
|
||||
case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile:
|
||||
w.moveFocus(key.FocusUp, d)
|
||||
case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile:
|
||||
w.moveFocus(key.FocusDown, d)
|
||||
case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile:
|
||||
w.moveFocus(key.FocusLeft, d)
|
||||
case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile:
|
||||
w.moveFocus(key.FocusRight, d)
|
||||
default:
|
||||
handled = false
|
||||
}
|
||||
focusDir := key.FocusDirection(-1)
|
||||
if e, ok := e2.(key.Event); ok && e.State == key.Press {
|
||||
isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android"
|
||||
switch {
|
||||
case e.Name == key.NameTab && e.Modifiers == 0:
|
||||
focusDir = key.FocusForward
|
||||
case e.Name == key.NameTab && e.Modifiers == key.ModShift:
|
||||
focusDir = key.FocusBackward
|
||||
case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusUp
|
||||
case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusDown
|
||||
case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusLeft
|
||||
case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile:
|
||||
focusDir = key.FocusRight
|
||||
}
|
||||
}
|
||||
e := e2
|
||||
if focusDir != -1 {
|
||||
e = input.SystemEvent{Event: e}
|
||||
}
|
||||
w.queue.Queue(e)
|
||||
t, handled := w.queue.WakeupTime()
|
||||
if focusDir != -1 && !handled {
|
||||
w.moveFocus(focusDir)
|
||||
t, handled = w.queue.WakeupTime()
|
||||
}
|
||||
w.updateCursor(d)
|
||||
if handled {
|
||||
w.setNextFrame(time.Time{})
|
||||
w.setNextFrame(t)
|
||||
w.updateAnimation(d)
|
||||
}
|
||||
return handled
|
||||
|
||||
+4
-4
@@ -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
@@ -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
@@ -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() {}
|
||||
|
||||
Reference in New Issue
Block a user