io/key: [API] introduce FocusFilter for matching focus and editor events

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2023-10-19 18:31:07 -05:00
parent 12a0ad7038
commit 73c3849da4
8 changed files with 127 additions and 60 deletions
+28 -16
View File
@@ -36,13 +36,14 @@ type keyQueue struct {
type keyHandler struct { type keyHandler struct {
// visible will be true if the InputOp is present // visible will be true if the InputOp is present
// in the current frame. // in the current frame.
visible bool visible bool
new bool new bool
hint key.InputHint focusable bool
order int hint key.InputHint
dirOrder int order int
filter key.Set dirOrder int
trans f32.Affine2D filter key.Set
trans f32.Affine2D
} }
type dirFocusEntry struct { type dirFocusEntry struct {
@@ -87,11 +88,7 @@ func (q *keyQueue) InputHint() (key.InputHint, bool) {
} }
func (q *keyQueue) Reset() { func (q *keyQueue) Reset() {
if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler)
}
for _, h := range q.handlers { for _, h := range q.handlers {
h.visible, h.new = false, false
h.order = -1 h.order = -1
h.hint = key.HintAny h.hint = key.HintAny
} }
@@ -101,17 +98,24 @@ func (q *keyQueue) Reset() {
func (q *keyQueue) Frame(events *handlerEvents) { func (q *keyQueue) Frame(events *handlerEvents) {
for k, h := range q.handlers { for k, h := range q.handlers {
if !h.visible { if !h.visible || !h.focusable {
delete(q.handlers, k)
if q.focus == k { if q.focus == k {
// Remove focus from the handler that is no longer visible. // Remove focus from the handler that is no longer focusable.
q.focus = nil q.focus = nil
q.state = TextInputClose q.state = TextInputClose
} }
} else if h.new && k != q.focus { if !h.visible && !h.focusable {
delete(q.handlers, k)
continue
}
}
if h.new && k != q.focus {
// Reset the handler on (each) first appearance, but don't trigger redraw. // Reset the handler on (each) first appearance, but don't trigger redraw.
events.AddNoRedraw(k, key.FocusEvent{Focus: false}) events.AddNoRedraw(k, key.FocusEvent{Focus: false})
} }
h.new = false
h.visible = false
h.focusable = false
} }
q.updateFocusLayout() q.updateFocusLayout()
} }
@@ -284,10 +288,18 @@ func (q *keyQueue) softKeyboard(show bool) {
} }
} }
func (q *keyQueue) focusable(tag event.Tag) {
h := q.handlerFor(tag)
h.focusable = true
}
func (q *keyQueue) handlerFor(tag event.Tag) *keyHandler { func (q *keyQueue) handlerFor(tag event.Tag) *keyHandler {
h, ok := q.handlers[tag] h, ok := q.handlers[tag]
if !ok { if !ok {
h = &keyHandler{new: true, order: -1} h = &keyHandler{new: true, order: -1}
if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler)
}
q.handlers[tag] = h q.handlers[tag] = h
} }
return h return h
@@ -300,8 +312,8 @@ func (q *keyQueue) inputOp(op key.InputOp, t f32.Affine2D, area int, bounds imag
q.order = append(q.order, op.Tag) q.order = append(q.order, op.Tag)
q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: op.Tag, area: area, bounds: bounds}) q.dirOrder = append(q.dirOrder, dirFocusEntry{tag: op.Tag, area: area, bounds: bounds})
} }
h.visible = true
h.filter = op.Keys h.filter = op.Keys
h.visible = true
h.trans = t h.trans = t
} }
+82 -40
View File
@@ -22,12 +22,13 @@ func TestKeyWakeup(t *testing.T) {
var r Router var r Router
// Test that merely adding a handler doesn't trigger redraw. // Test that merely adding a handler doesn't trigger redraw.
r.Events(handler, key.FocusFilter{})
r.Frame(&ops) r.Frame(&ops)
if _, wake := r.WakeupTime(); wake { if _, wake := r.WakeupTime(); wake {
t.Errorf("adding key.InputOp triggered a redraw") t.Errorf("adding key.InputOp triggered a redraw")
} }
// However, adding a handler queues a Focus(false) event. // However, adding a handler queues a Focus(false) event.
if evts := r.Events(handler); len(evts) != 1 { if evts := r.Events(handler, key.FocusFilter{}); len(evts) != 1 {
t.Errorf("no Focus event for newly registered key.InputOp") t.Errorf("no Focus event for newly registered key.InputOp")
} }
} }
@@ -45,11 +46,15 @@ func TestKeyMultiples(t *testing.T) {
// The last one must be focused: // The last one must be focused:
key.InputOp{Tag: &handlers[2]}.Add(ops) key.InputOp{Tag: &handlers[2]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops) r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), false) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[1]), false) assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[2]), true) assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), true)
assertFocus(t, r, &handlers[2]) assertFocus(t, r, &handlers[2])
assertKeyboard(t, r, TextInputOpen) assertKeyboard(t, r, TextInputOpen)
} }
@@ -68,12 +73,16 @@ func TestKeyStacked(t *testing.T) {
r.Source().Queue(key.SoftKeyboardCmd{Show: true}) r.Source().Queue(key.SoftKeyboardCmd{Show: true})
key.InputOp{Tag: &handlers[3]}.Add(ops) key.InputOp{Tag: &handlers[3]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops) r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), false) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[1]), true) assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), true)
assertKeyEvent(t, r.Events(&handlers[2]), false) assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[3]), false) assertKeyEvent(t, r.Events(&handlers[3], key.FocusFilter{}), false)
assertFocus(t, r, &handlers[1]) assertFocus(t, r, &handlers[1])
assertKeyboard(t, r, TextInputOpen) assertKeyboard(t, r, TextInputOpen)
} }
@@ -105,14 +114,18 @@ func TestKeyRemoveFocus(t *testing.T) {
// New InputOp without any focus: // New InputOp without any focus:
key.InputOp{Tag: &handlers[1], Keys: "Short-Tab"}.Add(ops) key.InputOp{Tag: &handlers[1], Keys: "Short-Tab"}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops) r.Frame(ops)
// Add some key events: // Add some key events:
event := event.Event(key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press}) event := event.Event(key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press})
r.Queue(event) r.Queue(event)
assertKeyEvent(t, r.Events(&handlers[0]), true, event) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), true, event)
assertKeyEvent(t, r.Events(&handlers[1]), false) assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false)
assertFocus(t, r, &handlers[0]) assertFocus(t, r, &handlers[0])
assertKeyboard(t, r, TextInputOpen) assertKeyboard(t, r, TextInputOpen)
@@ -129,7 +142,7 @@ func TestKeyRemoveFocus(t *testing.T) {
r.Frame(ops) r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[1])) assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil) assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose) assertKeyboard(t, r, TextInputClose)
@@ -141,8 +154,8 @@ func TestKeyRemoveFocus(t *testing.T) {
r.Frame(ops) r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[0])) assertKeyEventUnexpected(t, r.Events(&handlers[0], key.FocusFilter{}))
assertKeyEventUnexpected(t, r.Events(&handlers[1])) assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil) assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose) assertKeyboard(t, r, TextInputClose)
@@ -160,7 +173,7 @@ func TestKeyRemoveFocus(t *testing.T) {
r.Frame(ops) r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[1])) assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil) assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose) assertKeyboard(t, r, TextInputClose)
} }
@@ -178,10 +191,14 @@ func TestKeyFocusedInvisible(t *testing.T) {
// Set new InputOp without focus: // Set new InputOp without focus:
key.InputOp{Tag: &handlers[1]}.Add(ops) key.InputOp{Tag: &handlers[1]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops) r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), true) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), true)
assertKeyEvent(t, r.Events(&handlers[1]), false) assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false)
assertFocus(t, r, &handlers[0]) assertFocus(t, r, &handlers[0])
assertKeyboard(t, r, TextInputOpen) assertKeyboard(t, r, TextInputOpen)
@@ -196,11 +213,18 @@ func TestKeyFocusedInvisible(t *testing.T) {
r.Frame(ops) r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[0])) assertKeyEventUnexpected(t, r.Events(&handlers[0], key.FocusFilter{}))
assertKeyEventUnexpected(t, r.Events(&handlers[1])) assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil) assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose) assertKeyboard(t, r, TextInputClose)
r.Frame(ops)
// Unchanged
key.InputOp{Tag: &handlers[1]}.Add(ops)
r.Frame(ops)
ops.Reset() ops.Reset()
// Respawn the first element: // Respawn the first element:
@@ -210,13 +234,16 @@ func TestKeyFocusedInvisible(t *testing.T) {
// Unchanged // Unchanged
key.InputOp{Tag: &handlers[1]}.Add(ops) key.InputOp{Tag: &handlers[1]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops) r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), false) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false)
assertKeyEventUnexpected(t, r.Events(&handlers[1])) assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil) assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose) assertKeyboard(t, r, TextInputClose)
} }
func TestNoOps(t *testing.T) { func TestNoOps(t *testing.T) {
@@ -238,6 +265,7 @@ func TestDirectionalFocus(t *testing.T) {
cl := clip.Rect(bounds).Push(ops) cl := clip.Rect(bounds).Push(ops)
key.InputOp{Tag: &handlers[i]}.Add(ops) key.InputOp{Tag: &handlers[i]}.Add(ops)
cl.Pop() cl.Pop()
r.Events(&handlers[i], key.FocusFilter{})
} }
r.Frame(ops) r.Frame(ops)
@@ -269,11 +297,14 @@ func TestFocusScroll(t *testing.T) {
r := new(Router) r := new(Router)
h := new(int) h := new(int)
f := pointer.Filter{ filters := []event.Filter{
Kinds: pointer.Scroll, key.FocusFilter{},
ScrollBounds: image.Rect(-100, -100, 100, 100), pointer.Filter{
Kinds: pointer.Scroll,
ScrollBounds: image.Rect(-100, -100, 100, 100),
},
} }
r.Events(h, f) r.Events(h, filters...)
parent := clip.Rect(image.Rect(1, 1, 14, 39)).Push(ops) parent := clip.Rect(image.Rect(1, 1, 14, 39)).Push(ops)
cl := clip.Rect(image.Rect(10, -20, 20, 30)).Push(ops) cl := clip.Rect(image.Rect(10, -20, 20, 30)).Push(ops)
key.InputOp{Tag: h}.Add(ops) key.InputOp{Tag: h}.Add(ops)
@@ -286,7 +317,7 @@ func TestFocusScroll(t *testing.T) {
r.MoveFocus(key.FocusLeft) r.MoveFocus(key.FocusLeft)
r.RevealFocus(image.Rect(0, 0, 15, 40)) r.RevealFocus(image.Rect(0, 0, 15, 40))
evts := r.Events(h, f) evts := r.Events(h, filters...)
assertScrollEvent(t, evts[len(evts)-1], f32.Pt(6, -9)) assertScrollEvent(t, evts[len(evts)-1], f32.Pt(6, -9))
} }
@@ -295,10 +326,13 @@ func TestFocusClick(t *testing.T) {
r := new(Router) r := new(Router)
h := new(int) h := new(int)
f := pointer.Filter{ filters := []event.Filter{
Kinds: pointer.Press | pointer.Release, key.FocusFilter{},
pointer.Filter{
Kinds: pointer.Press | pointer.Release,
},
} }
assertEventPointerTypeSequence(t, r.Events(h, f), pointer.Cancel) assertEventPointerTypeSequence(t, r.Events(h, filters...), pointer.Cancel)
cl := clip.Rect(image.Rect(0, 0, 10, 10)).Push(ops) cl := clip.Rect(image.Rect(0, 0, 10, 10)).Push(ops)
key.InputOp{Tag: h}.Add(ops) key.InputOp{Tag: h}.Add(ops)
event.InputOp(ops, h) event.InputOp(ops, h)
@@ -308,7 +342,7 @@ func TestFocusClick(t *testing.T) {
r.MoveFocus(key.FocusLeft) r.MoveFocus(key.FocusLeft)
r.ClickFocus() r.ClickFocus()
assertEventPointerTypeSequence(t, r.Events(h, f), pointer.Press, pointer.Release) assertEventPointerTypeSequence(t, r.Events(h, filters...), pointer.Press, pointer.Release)
} }
func TestNoFocus(t *testing.T) { func TestNoFocus(t *testing.T) {
@@ -337,6 +371,10 @@ func TestKeyRouting(t *testing.T) {
call := macro.Stop() call := macro.Stop()
call.Add(ops) call.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops) r.Frame(ops)
A, B := key.Event{Name: "A"}, key.Event{Name: "B"} A, B := key.Event{Name: "A"}, key.Event{Name: "B"}
@@ -344,14 +382,18 @@ func TestKeyRouting(t *testing.T) {
// With no focus, the events should traverse the final branch of the hit tree // With no focus, the events should traverse the final branch of the hit tree
// searching for handlers. // searching for handlers.
assertKeyEvent(t, r.Events(&handlers[4]), false, A) assertKeyEvent(t, r.Events(&handlers[4], key.FocusFilter{}), false, A)
assertKeyEvent(t, r.Events(&handlers[3]), false) assertKeyEvent(t, r.Events(&handlers[3], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[2]), false) assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[1]), false, B) assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false, B)
assertKeyEvent(t, r.Events(&handlers[0]), false) assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false)
r2 := new(Router) r2 := new(Router)
for i := range handlers {
r2.Events(&handlers[i], key.FocusFilter{})
}
r2.Source().Queue(key.FocusCmd{Tag: &handlers[3]}) r2.Source().Queue(key.FocusCmd{Tag: &handlers[3]})
r2.Frame(ops) r2.Frame(ops)
@@ -359,11 +401,11 @@ func TestKeyRouting(t *testing.T) {
// With focus, the events should traverse the branch of the hit tree // With focus, the events should traverse the branch of the hit tree
// containing the focused element. // containing the focused element.
assertKeyEvent(t, r2.Events(&handlers[4]), false) assertKeyEvent(t, r2.Events(&handlers[4], key.FocusFilter{}), false)
assertKeyEvent(t, r2.Events(&handlers[3]), true) assertKeyEvent(t, r2.Events(&handlers[3], key.FocusFilter{}), true)
assertKeyEvent(t, r2.Events(&handlers[2]), false) assertKeyEvent(t, r2.Events(&handlers[2], key.FocusFilter{}), false)
assertKeyEvent(t, r2.Events(&handlers[1]), false) assertKeyEvent(t, r2.Events(&handlers[1], key.FocusFilter{}), false)
assertKeyEvent(t, r2.Events(&handlers[0]), false, A) assertKeyEvent(t, r2.Events(&handlers[0], key.FocusFilter{}), false, A)
} }
func assertKeyEvent(t *testing.T, events []event.Event, expectedFocus bool, expectedInputs ...event.Event) { func assertKeyEvent(t *testing.T, events []event.Event, expectedFocus bool, expectedInputs ...event.Event) {
+8
View File
@@ -131,6 +131,8 @@ func (s Source) Events(k event.Tag, filters ...event.Filter) []event.Event {
func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event { func (q *Router) Events(k event.Tag, filters ...event.Filter) []event.Event {
for _, f := range filters { for _, f := range filters {
switch f := f.(type) { switch f := f.(type) {
case key.FocusFilter:
q.key.queue.focusable(k)
case pointer.Filter: case pointer.Filter:
q.pointer.queue.filterTag(k, f, &q.handlers) q.pointer.queue.filterTag(k, f, &q.handlers)
case transfer.SourceFilter: case transfer.SourceFilter:
@@ -581,6 +583,12 @@ func (h *handlerEvents) Events(k event.Tag, filters ...event.Filter) []event.Eve
func filtersMatches(filters []event.Filter, e event.Event) bool { func filtersMatches(filters []event.Filter, e event.Event) bool {
switch e := e.(type) { switch e := e.(type) {
case key.FocusEvent, key.SnippetEvent, key.EditEvent, key.SelectionEvent:
for _, f := range filters {
if _, ok := f.(key.FocusFilter); ok {
return true
}
}
case pointer.Event: case pointer.Event:
for _, f := range filters { for _, f := range filters {
if f, ok := f.(pointer.Filter); ok && f.Kinds&e.Kind == e.Kind { if f, ok := f.(pointer.Filter); ok && f.Kinds&e.Kind == e.Kind {
+5
View File
@@ -128,6 +128,9 @@ type EditEvent struct {
Text string Text string
} }
// FocusFilter matches [FocusEvent]s.
type FocusFilter struct{}
// InputHint changes the on-screen-keyboard type. That hints the // InputHint changes the on-screen-keyboard type. That hints the
// type of data that might be entered by the user. // type of data that might be entered by the user.
type InputHint uint8 type InputHint uint8
@@ -357,6 +360,8 @@ func (SoftKeyboardCmd) ImplementsCommand() {}
func (SelectionCmd) ImplementsCommand() {} func (SelectionCmd) ImplementsCommand() {}
func (SnippetCmd) ImplementsCommand() {} func (SnippetCmd) ImplementsCommand() {}
func (FocusFilter) ImplementsFilter() {}
func (m Modifiers) String() string { func (m Modifiers) String() string {
var strs []string var strs []string
if m.Contain(ModCtrl) { if m.Contain(ModCtrl) {
+1 -1
View File
@@ -162,7 +162,7 @@ func (b *Clickable) Update(gtx layout.Context) []Click {
}) })
} }
} }
for _, e := range gtx.Events(&b.keyTag) { for _, e := range gtx.Events(&b.keyTag, key.FocusFilter{}) {
switch e := e.(type) { switch e := e.(type) {
case key.FocusEvent: case key.FocusEvent:
b.focused = e.Focus b.focused = e.Focus
+1 -1
View File
@@ -332,7 +332,7 @@ func (e *Editor) processKey(gtx layout.Context) {
} }
// adjust keeps track of runes dropped because of MaxLen. // adjust keeps track of runes dropped because of MaxLen.
var adjust int var adjust int
for _, ke := range gtx.Events(&e.eventKey, transfer.TargetFilter{Type: "application/text"}) { for _, ke := range gtx.Events(&e.eventKey, transfer.TargetFilter{Type: "application/text"}, key.FocusFilter{}) {
e.blinkStart = gtx.Now e.blinkStart = gtx.Now
switch ke := ke.(type) { switch ke := ke.(type) {
case key.FocusEvent: case key.FocusEvent:
+1 -1
View File
@@ -59,7 +59,7 @@ func (e *Enum) Update(gtx layout.Context) bool {
} }
} }
} }
for _, ev := range gtx.Events(&state.tag) { for _, ev := range gtx.Events(&state.tag, key.FocusFilter{}) {
switch ev := ev.(type) { switch ev := ev.(type) {
case key.FocusEvent: case key.FocusEvent:
if ev.Focus { if ev.Focus {
+1 -1
View File
@@ -304,7 +304,7 @@ func (e *Selectable) clickDragEvents(gtx layout.Context) []event.Event {
} }
func (e *Selectable) processKey(gtx layout.Context) { func (e *Selectable) processKey(gtx layout.Context) {
for _, ke := range gtx.Events(e) { for _, ke := range gtx.Events(e, key.FocusFilter{}) {
switch ke := ke.(type) { switch ke := ke.(type) {
case key.FocusEvent: case key.FocusEvent:
e.focused = ke.Focus e.focused = ke.Focus