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 {
// visible will be true if the InputOp is present
// in the current frame.
visible bool
new bool
hint key.InputHint
order int
dirOrder int
filter key.Set
trans f32.Affine2D
visible bool
new bool
focusable bool
hint key.InputHint
order int
dirOrder int
filter key.Set
trans f32.Affine2D
}
type dirFocusEntry struct {
@@ -87,11 +88,7 @@ func (q *keyQueue) InputHint() (key.InputHint, bool) {
}
func (q *keyQueue) Reset() {
if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler)
}
for _, h := range q.handlers {
h.visible, h.new = false, false
h.order = -1
h.hint = key.HintAny
}
@@ -101,17 +98,24 @@ func (q *keyQueue) Reset() {
func (q *keyQueue) Frame(events *handlerEvents) {
for k, h := range q.handlers {
if !h.visible {
delete(q.handlers, k)
if !h.visible || !h.focusable {
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.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.
events.AddNoRedraw(k, key.FocusEvent{Focus: false})
}
h.new = false
h.visible = false
h.focusable = false
}
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 {
h, ok := q.handlers[tag]
if !ok {
h = &keyHandler{new: true, order: -1}
if q.handlers == nil {
q.handlers = make(map[event.Tag]*keyHandler)
}
q.handlers[tag] = 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.dirOrder = append(q.dirOrder, dirFocusEntry{tag: op.Tag, area: area, bounds: bounds})
}
h.visible = true
h.filter = op.Keys
h.visible = true
h.trans = t
}
+82 -40
View File
@@ -22,12 +22,13 @@ func TestKeyWakeup(t *testing.T) {
var r Router
// Test that merely adding a handler doesn't trigger redraw.
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); len(evts) != 1 {
if evts := r.Events(handler, key.FocusFilter{}); len(evts) != 1 {
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:
key.InputOp{Tag: &handlers[2]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), false)
assertKeyEvent(t, r.Events(&handlers[1]), false)
assertKeyEvent(t, r.Events(&handlers[2]), true)
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)
}
@@ -68,12 +73,16 @@ func TestKeyStacked(t *testing.T) {
r.Source().Queue(key.SoftKeyboardCmd{Show: true})
key.InputOp{Tag: &handlers[3]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), false)
assertKeyEvent(t, r.Events(&handlers[1]), true)
assertKeyEvent(t, r.Events(&handlers[2]), false)
assertKeyEvent(t, r.Events(&handlers[3]), false)
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)
}
@@ -105,14 +114,18 @@ func TestKeyRemoveFocus(t *testing.T) {
// New InputOp without any focus:
key.InputOp{Tag: &handlers[1], Keys: "Short-Tab"}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
// Add some key events:
event := event.Event(key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press})
r.Queue(event)
assertKeyEvent(t, r.Events(&handlers[0]), true, event)
assertKeyEvent(t, r.Events(&handlers[1]), false)
assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), true, event)
assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false)
assertFocus(t, r, &handlers[0])
assertKeyboard(t, r, TextInputOpen)
@@ -129,7 +142,7 @@ func TestKeyRemoveFocus(t *testing.T) {
r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose)
@@ -141,8 +154,8 @@ func TestKeyRemoveFocus(t *testing.T) {
r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[0]))
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
assertKeyEventUnexpected(t, r.Events(&handlers[0], key.FocusFilter{}))
assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose)
@@ -160,7 +173,7 @@ func TestKeyRemoveFocus(t *testing.T) {
r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose)
}
@@ -178,10 +191,14 @@ func TestKeyFocusedInvisible(t *testing.T) {
// Set new InputOp without focus:
key.InputOp{Tag: &handlers[1]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), true)
assertKeyEvent(t, r.Events(&handlers[1]), false)
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)
@@ -196,11 +213,18 @@ func TestKeyFocusedInvisible(t *testing.T) {
r.Frame(ops)
assertKeyEventUnexpected(t, r.Events(&handlers[0]))
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
assertKeyEventUnexpected(t, r.Events(&handlers[0], key.FocusFilter{}))
assertKeyEventUnexpected(t, r.Events(&handlers[1], key.FocusFilter{}))
assertFocus(t, r, nil)
assertKeyboard(t, r, TextInputClose)
r.Frame(ops)
// Unchanged
key.InputOp{Tag: &handlers[1]}.Add(ops)
r.Frame(ops)
ops.Reset()
// Respawn the first element:
@@ -210,13 +234,16 @@ func TestKeyFocusedInvisible(t *testing.T) {
// Unchanged
key.InputOp{Tag: &handlers[1]}.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
assertKeyEvent(t, r.Events(&handlers[0]), false)
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
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)
}
func TestNoOps(t *testing.T) {
@@ -238,6 +265,7 @@ func TestDirectionalFocus(t *testing.T) {
cl := clip.Rect(bounds).Push(ops)
key.InputOp{Tag: &handlers[i]}.Add(ops)
cl.Pop()
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
@@ -269,11 +297,14 @@ func TestFocusScroll(t *testing.T) {
r := new(Router)
h := new(int)
f := pointer.Filter{
Kinds: pointer.Scroll,
ScrollBounds: image.Rect(-100, -100, 100, 100),
filters := []event.Filter{
key.FocusFilter{},
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)
cl := clip.Rect(image.Rect(10, -20, 20, 30)).Push(ops)
key.InputOp{Tag: h}.Add(ops)
@@ -286,7 +317,7 @@ func TestFocusScroll(t *testing.T) {
r.MoveFocus(key.FocusLeft)
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))
}
@@ -295,10 +326,13 @@ func TestFocusClick(t *testing.T) {
r := new(Router)
h := new(int)
f := pointer.Filter{
Kinds: pointer.Press | pointer.Release,
filters := []event.Filter{
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)
key.InputOp{Tag: h}.Add(ops)
event.InputOp(ops, h)
@@ -308,7 +342,7 @@ func TestFocusClick(t *testing.T) {
r.MoveFocus(key.FocusLeft)
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) {
@@ -337,6 +371,10 @@ func TestKeyRouting(t *testing.T) {
call := macro.Stop()
call.Add(ops)
for i := range handlers {
r.Events(&handlers[i], key.FocusFilter{})
}
r.Frame(ops)
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
// searching for handlers.
assertKeyEvent(t, r.Events(&handlers[4]), false, A)
assertKeyEvent(t, r.Events(&handlers[3]), false)
assertKeyEvent(t, r.Events(&handlers[2]), false)
assertKeyEvent(t, r.Events(&handlers[1]), false, B)
assertKeyEvent(t, r.Events(&handlers[0]), false)
assertKeyEvent(t, r.Events(&handlers[4], key.FocusFilter{}), false, A)
assertKeyEvent(t, r.Events(&handlers[3], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[2], key.FocusFilter{}), false)
assertKeyEvent(t, r.Events(&handlers[1], key.FocusFilter{}), false, B)
assertKeyEvent(t, r.Events(&handlers[0], key.FocusFilter{}), false)
r2 := new(Router)
for i := range handlers {
r2.Events(&handlers[i], key.FocusFilter{})
}
r2.Source().Queue(key.FocusCmd{Tag: &handlers[3]})
r2.Frame(ops)
@@ -359,11 +401,11 @@ 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]), false)
assertKeyEvent(t, r2.Events(&handlers[3]), true)
assertKeyEvent(t, r2.Events(&handlers[2]), false)
assertKeyEvent(t, r2.Events(&handlers[1]), false)
assertKeyEvent(t, r2.Events(&handlers[0]), false, A)
assertKeyEvent(t, r2.Events(&handlers[4], key.FocusFilter{}), false)
assertKeyEvent(t, r2.Events(&handlers[3], key.FocusFilter{}), true)
assertKeyEvent(t, r2.Events(&handlers[2], key.FocusFilter{}), false)
assertKeyEvent(t, r2.Events(&handlers[1], key.FocusFilter{}), false)
assertKeyEvent(t, r2.Events(&handlers[0], key.FocusFilter{}), false, A)
}
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 {
for _, f := range filters {
switch f := f.(type) {
case key.FocusFilter:
q.key.queue.focusable(k)
case pointer.Filter:
q.pointer.queue.filterTag(k, f, &q.handlers)
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 {
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:
for _, f := range filters {
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
}
// FocusFilter matches [FocusEvent]s.
type FocusFilter struct{}
// InputHint changes the on-screen-keyboard type. That hints the
// type of data that might be entered by the user.
type InputHint uint8
@@ -357,6 +360,8 @@ func (SoftKeyboardCmd) ImplementsCommand() {}
func (SelectionCmd) ImplementsCommand() {}
func (SnippetCmd) ImplementsCommand() {}
func (FocusFilter) ImplementsFilter() {}
func (m Modifiers) String() string {
var strs []string
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) {
case key.FocusEvent:
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.
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
switch ke := ke.(type) {
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) {
case key.FocusEvent:
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) {
for _, ke := range gtx.Events(e) {
for _, ke := range gtx.Events(e, key.FocusFilter{}) {
switch ke := ke.(type) {
case key.FocusEvent:
e.focused = ke.Focus