mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 18:05:35 +00:00
io/key: improve InputOp focus and blur
The existing implementation cannot remove the focus of some widget, doesn't have an option to focus without display the on-screen keyboard and it automatically focuses the first InputOp, aggressively. That change aims to make possible: remove focus from any widget. Add focus without displaying the on-screen-keyboard/soft keyboard. Don't automatically focus any widget. Don't recover focus when the widget is visible again. Fixes gio#180. Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gioui.org/io/event"
|
||||
"gioui.org/io/key"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
func TestKeyMultiples(t *testing.T) {
|
||||
handlers := make([]int, 3)
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.FocusOp{Focus: true}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
|
||||
// The last one must be focused:
|
||||
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||
assertKeyEvent(t, r.Events(&handlers[1]), false)
|
||||
assertKeyEvent(t, r.Events(&handlers[2]), true)
|
||||
assertFocus(t, r, &handlers[2])
|
||||
assertKeyboard(t, r, TextInputOpen)
|
||||
}
|
||||
|
||||
func TestKeyStacked(t *testing.T) {
|
||||
handlers := make([]int, 4)
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
s := op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
// FocusOp must not overwrite the
|
||||
// FocusOp{Focus: true}.
|
||||
key.FocusOp{Focus: false}.Add(ops)
|
||||
s.Pop()
|
||||
s = op.Push(ops)
|
||||
key.SoftKeyboardOp{Show: false}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
key.FocusOp{Focus: true}.Add(ops)
|
||||
s.Pop()
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[2]}.Add(ops)
|
||||
// SoftwareKeyboardOp will open the keyboard,
|
||||
// overwriting `SoftKeyboardOp{Show: false}`.
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Pop()
|
||||
s = op.Push(ops)
|
||||
key.SoftKeyboardOp{Show: false}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[3]}.Add(ops)
|
||||
// FocusOp must not overwrite the
|
||||
// FocusOp{Focus: true}.
|
||||
key.FocusOp{Focus: false}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
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)
|
||||
assertFocus(t, r, &handlers[1])
|
||||
assertKeyboard(t, r, TextInputOpen)
|
||||
}
|
||||
|
||||
func TestKeySoftKeyboardNoFocus(t *testing.T) {
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
// It's possible to open the keyboard
|
||||
// without any active focus:
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertFocus(t, r, nil)
|
||||
assertKeyboard(t, r, TextInputOpen)
|
||||
}
|
||||
|
||||
func TestKeyRemoveFocus(t *testing.T) {
|
||||
handlers := make([]int, 2)
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
// New InputOp with Focus and Keyboard:
|
||||
s := op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.FocusOp{Focus: true}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// New InputOp without any focus:
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
// Add some key events:
|
||||
event := event.Event(key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press})
|
||||
r.Add(event)
|
||||
|
||||
assertKeyEvent(t, r.Events(&handlers[0]), true, event)
|
||||
assertKeyEvent(t, r.Events(&handlers[1]), false)
|
||||
assertFocus(t, r, &handlers[0])
|
||||
assertKeyboard(t, r, TextInputOpen)
|
||||
|
||||
ops.Reset()
|
||||
|
||||
// Will get the focus removed:
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// Unchanged:
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// Removing any Focus:
|
||||
s = op.Push(ops)
|
||||
key.FocusOp{Focus: false}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||
assertFocus(t, r, nil)
|
||||
assertKeyboard(t, r, TextInputClose)
|
||||
|
||||
ops.Reset()
|
||||
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// Setting Focus without InputOp:
|
||||
s = op.Push(ops)
|
||||
key.FocusOp{Focus: true}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[0]))
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||
assertFocus(t, r, nil)
|
||||
assertKeyboard(t, r, TextInputKeep)
|
||||
|
||||
ops.Reset()
|
||||
|
||||
// Set focus to InputOp which already
|
||||
// exists in the previous frame:
|
||||
s = op.Push(ops)
|
||||
key.FocusOp{Focus: true}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// Tries to remove focus:
|
||||
// It must not overwrite the previous `FocusOp`.
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
key.FocusOp{Focus: false}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEvent(t, r.Events(&handlers[0]), true)
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||
assertFocus(t, r, &handlers[0])
|
||||
assertKeyboard(t, r, TextInputOpen)
|
||||
}
|
||||
|
||||
func TestKeyFocusedInvisible(t *testing.T) {
|
||||
handlers := make([]int, 2)
|
||||
ops := new(op.Ops)
|
||||
r := new(Router)
|
||||
|
||||
// Set new InputOp with focus:
|
||||
s := op.Push(ops)
|
||||
key.FocusOp{Focus: true}.Add(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
key.SoftKeyboardOp{Show: true}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// Set new InputOp without focus:
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEvent(t, r.Events(&handlers[0]), true)
|
||||
assertKeyEvent(t, r.Events(&handlers[1]), false)
|
||||
assertFocus(t, r, &handlers[0])
|
||||
assertKeyboard(t, r, TextInputOpen)
|
||||
|
||||
ops.Reset()
|
||||
|
||||
//
|
||||
// Removed first (focused) element!
|
||||
//
|
||||
|
||||
// Unchanged:
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[0]))
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||
assertFocus(t, r, nil)
|
||||
assertKeyboard(t, r, TextInputClose)
|
||||
|
||||
ops.Reset()
|
||||
|
||||
// Respawn the first element:
|
||||
// It must receive one `Event{Focus: false}`.
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[0]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
// Unchanged
|
||||
s = op.Push(ops)
|
||||
key.InputOp{Tag: &handlers[1]}.Add(ops)
|
||||
s.Pop()
|
||||
|
||||
r.Frame(ops)
|
||||
|
||||
assertKeyEvent(t, r.Events(&handlers[0]), false)
|
||||
assertKeyEventUnexpected(t, r.Events(&handlers[1]))
|
||||
assertFocus(t, r, nil)
|
||||
assertKeyboard(t, r, TextInputKeep)
|
||||
|
||||
}
|
||||
|
||||
func assertKeyEvent(t *testing.T, events []event.Event, expected bool, expectedInputs ...event.Event) {
|
||||
t.Helper()
|
||||
var evtFocus int
|
||||
var evtKeyPress int
|
||||
for _, e := range events {
|
||||
switch ev := e.(type) {
|
||||
case key.FocusEvent:
|
||||
if ev.Focus != expected {
|
||||
t.Errorf("focus is expected to be %v, got %v", expected, ev.Focus)
|
||||
}
|
||||
evtFocus++
|
||||
case key.Event, key.EditEvent:
|
||||
if len(expectedInputs) <= evtKeyPress {
|
||||
t.Errorf("unexpected key events")
|
||||
}
|
||||
if !reflect.DeepEqual(ev, expectedInputs[evtKeyPress]) {
|
||||
t.Errorf("expected %v events, got %v", expectedInputs[evtKeyPress], ev)
|
||||
}
|
||||
evtKeyPress++
|
||||
}
|
||||
}
|
||||
if evtFocus <= 0 {
|
||||
t.Errorf("expected focus event")
|
||||
}
|
||||
if evtFocus > 1 {
|
||||
t.Errorf("expected single focus event")
|
||||
}
|
||||
if evtKeyPress != len(expectedInputs) {
|
||||
t.Errorf("expected key events")
|
||||
}
|
||||
}
|
||||
|
||||
func assertKeyEventUnexpected(t *testing.T, events []event.Event) {
|
||||
t.Helper()
|
||||
var evtFocus int
|
||||
for _, e := range events {
|
||||
switch e.(type) {
|
||||
case key.FocusEvent:
|
||||
evtFocus++
|
||||
}
|
||||
}
|
||||
if evtFocus > 1 {
|
||||
t.Errorf("unexpected focus event")
|
||||
}
|
||||
}
|
||||
|
||||
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
|
||||
t.Helper()
|
||||
if router.kqueue.focus != expected {
|
||||
t.Errorf("expected %v to be focused, got %v", expected, router.kqueue.focus)
|
||||
}
|
||||
}
|
||||
|
||||
func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
|
||||
t.Helper()
|
||||
if router.kqueue.state != expected {
|
||||
t.Errorf("expected %v keyboard, got %v", expected, router.kqueue.state)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user