forked from joejulian/gio
33f9a850c8
It's semantically problematic that a zero Kind matches Cancel, and outweighs the downside of having to explicitly mention Cancel in filters. For example, GrabCmd was always deferred because the resulting Cancel events always match the processed filters. Remove Frame from a few tests now that GrabCmd can be executed immediately. Signed-off-by: Elias Naur <mail@eliasnaur.com>
275 lines
7.4 KiB
Go
275 lines
7.4 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package input
|
|
|
|
import (
|
|
"image"
|
|
"testing"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/io/event"
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
)
|
|
|
|
func TestInputWakeup(t *testing.T) {
|
|
handler := new(int)
|
|
var ops op.Ops
|
|
// InputOps shouldn't trigger redraws.
|
|
event.InputOp(&ops, handler)
|
|
|
|
var r Router
|
|
// Reset events shouldn't either.
|
|
evts := events(&r, -1, key.FocusFilter{Target: new(int)}, key.Filter{Name: "A"})
|
|
assertEventSequence(t, evts, key.FocusEvent{Focus: false})
|
|
r.Frame(&ops)
|
|
if _, wake := r.WakeupTime(); wake {
|
|
t.Errorf("InputOp or the resetting FocusEvent triggered a wakeup")
|
|
}
|
|
// And neither does events that don't match anything.
|
|
if r.Queue(key.SnippetEvent{}) {
|
|
t.Errorf("a not-matching event triggered a wakeup")
|
|
}
|
|
// However, events that does match should trigger wakeup.
|
|
if !r.Queue(key.Event{Name: "A"}) {
|
|
t.Errorf("a key.Event didn't trigger redraw")
|
|
}
|
|
}
|
|
|
|
func TestKeyMultiples(t *testing.T) {
|
|
handlers := make([]int, 3)
|
|
r := new(Router)
|
|
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
for i := range handlers {
|
|
assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[i]}), key.FocusEvent{Focus: false})
|
|
}
|
|
r.Source().Execute(key.FocusCmd{Tag: &handlers[2]})
|
|
assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[2]}), key.FocusEvent{Focus: true})
|
|
assertFocus(t, r, &handlers[2])
|
|
|
|
assertKeyboard(t, r, TextInputOpen)
|
|
}
|
|
|
|
func TestKeySoftKeyboardNoFocus(t *testing.T) {
|
|
r := new(Router)
|
|
|
|
// It's possible to open the keyboard
|
|
// without any active focus:
|
|
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
|
|
assertFocus(t, r, nil)
|
|
assertKeyboard(t, r, TextInputOpen)
|
|
}
|
|
|
|
func TestKeyRemoveFocus(t *testing.T) {
|
|
handlers := make([]int, 2)
|
|
r := new(Router)
|
|
|
|
filters := func(h event.Tag) []event.Filter {
|
|
return []event.Filter{
|
|
key.FocusFilter{Target: h},
|
|
key.Filter{Focus: h, Name: key.NameTab, Required: key.ModShortcut},
|
|
}
|
|
}
|
|
var all []event.Filter
|
|
for i := range handlers {
|
|
all = append(all, filters(&handlers[i])...)
|
|
}
|
|
assertEventSequence(t, events(r, len(handlers), all...), key.FocusEvent{}, key.FocusEvent{})
|
|
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
|
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
|
|
evt := key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press}
|
|
r.Queue(evt)
|
|
|
|
assertEventSequence(t, events(r, 2, filters(&handlers[0])...), key.FocusEvent{Focus: true}, evt)
|
|
assertFocus(t, r, &handlers[0])
|
|
assertKeyboard(t, r, TextInputOpen)
|
|
|
|
// Frame removes focus from tags that don't filter for focus events nor mentioned in an InputOp.
|
|
r.Source().Execute(key.FocusCmd{Tag: new(int)})
|
|
r.Frame(new(op.Ops))
|
|
|
|
assertEventSequence(t, events(r, -1, filters(&handlers[1])...))
|
|
assertFocus(t, r, nil)
|
|
assertKeyboard(t, r, TextInputClose)
|
|
|
|
// Set focus to InputOp which already
|
|
// exists in the previous frame:
|
|
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
|
assertFocus(t, r, &handlers[0])
|
|
}
|
|
|
|
func TestKeyFocusedInvisible(t *testing.T) {
|
|
handlers := make([]int, 2)
|
|
ops := new(op.Ops)
|
|
r := new(Router)
|
|
|
|
for i := range handlers {
|
|
assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[i]}), key.FocusEvent{Focus: false})
|
|
}
|
|
|
|
// Set new InputOp with focus:
|
|
r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
|
|
r.Source().Execute(key.SoftKeyboardCmd{Show: true})
|
|
|
|
assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[0]}), key.FocusEvent{Focus: true})
|
|
assertFocus(t, r, &handlers[0])
|
|
assertKeyboard(t, r, TextInputOpen)
|
|
|
|
// Frame will clear the focus because the handler is not visible.
|
|
r.Frame(ops)
|
|
|
|
for i := range handlers {
|
|
assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[i]}))
|
|
}
|
|
assertFocus(t, r, nil)
|
|
assertKeyboard(t, r, TextInputClose)
|
|
|
|
r.Frame(ops)
|
|
r.Frame(ops)
|
|
|
|
ops.Reset()
|
|
|
|
// Respawn the first element:
|
|
// It must receive one `Event{Focus: false}`.
|
|
event.InputOp(ops, &handlers[0])
|
|
|
|
assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[0]}), key.FocusEvent{Focus: false})
|
|
}
|
|
|
|
func TestNoOps(t *testing.T) {
|
|
r := new(Router)
|
|
r.Frame(nil)
|
|
}
|
|
|
|
func TestDirectionalFocus(t *testing.T) {
|
|
ops := new(op.Ops)
|
|
r := new(Router)
|
|
handlers := []image.Rectangle{
|
|
image.Rect(10, 10, 50, 50),
|
|
image.Rect(50, 20, 100, 80),
|
|
image.Rect(20, 26, 60, 80),
|
|
image.Rect(10, 60, 50, 100),
|
|
}
|
|
|
|
for i, bounds := range handlers {
|
|
cl := clip.Rect(bounds).Push(ops)
|
|
event.InputOp(ops, &handlers[i])
|
|
cl.Pop()
|
|
events(r, -1, key.FocusFilter{Target: &handlers[i]})
|
|
}
|
|
r.Frame(ops)
|
|
|
|
r.MoveFocus(key.FocusLeft)
|
|
assertFocus(t, r, &handlers[0])
|
|
r.MoveFocus(key.FocusLeft)
|
|
assertFocus(t, r, &handlers[0])
|
|
r.MoveFocus(key.FocusRight)
|
|
assertFocus(t, r, &handlers[1])
|
|
r.MoveFocus(key.FocusRight)
|
|
assertFocus(t, r, &handlers[1])
|
|
r.MoveFocus(key.FocusDown)
|
|
assertFocus(t, r, &handlers[2])
|
|
r.MoveFocus(key.FocusDown)
|
|
assertFocus(t, r, &handlers[2])
|
|
r.MoveFocus(key.FocusLeft)
|
|
assertFocus(t, r, &handlers[3])
|
|
r.MoveFocus(key.FocusUp)
|
|
assertFocus(t, r, &handlers[0])
|
|
|
|
r.MoveFocus(key.FocusForward)
|
|
assertFocus(t, r, &handlers[1])
|
|
r.MoveFocus(key.FocusBackward)
|
|
assertFocus(t, r, &handlers[0])
|
|
}
|
|
|
|
func TestFocusScroll(t *testing.T) {
|
|
ops := new(op.Ops)
|
|
r := new(Router)
|
|
h := new(int)
|
|
|
|
filters := []event.Filter{
|
|
key.FocusFilter{Target: h},
|
|
pointer.Filter{
|
|
Target: h,
|
|
Kinds: pointer.Scroll,
|
|
ScrollBounds: image.Rect(-100, -100, 100, 100),
|
|
},
|
|
}
|
|
events(r, -1, filters...)
|
|
parent := clip.Rect(image.Rect(1, 1, 14, 39)).Push(ops)
|
|
cl := clip.Rect(image.Rect(10, -20, 20, 30)).Push(ops)
|
|
event.InputOp(ops, h)
|
|
// Test that h is scrolled even if behind another handler.
|
|
event.InputOp(ops, new(int))
|
|
cl.Pop()
|
|
parent.Pop()
|
|
r.Frame(ops)
|
|
|
|
r.MoveFocus(key.FocusLeft)
|
|
r.RevealFocus(image.Rect(0, 0, 15, 40))
|
|
evts := events(r, -1, filters...)
|
|
assertScrollEvent(t, evts[len(evts)-1], f32.Pt(6, -9))
|
|
}
|
|
|
|
func TestFocusClick(t *testing.T) {
|
|
ops := new(op.Ops)
|
|
r := new(Router)
|
|
h := new(int)
|
|
|
|
filters := []event.Filter{
|
|
key.FocusFilter{Target: h},
|
|
pointer.Filter{
|
|
Target: h,
|
|
Kinds: pointer.Press | pointer.Release | pointer.Cancel,
|
|
},
|
|
}
|
|
assertEventPointerTypeSequence(t, events(r, -1, filters...), pointer.Cancel)
|
|
cl := clip.Rect(image.Rect(0, 0, 10, 10)).Push(ops)
|
|
event.InputOp(ops, h)
|
|
cl.Pop()
|
|
r.Frame(ops)
|
|
|
|
r.MoveFocus(key.FocusLeft)
|
|
r.ClickFocus()
|
|
|
|
assertEventPointerTypeSequence(t, events(r, -1, filters...), pointer.Press, pointer.Release)
|
|
}
|
|
|
|
func TestNoFocus(t *testing.T) {
|
|
r := new(Router)
|
|
r.MoveFocus(key.FocusForward)
|
|
}
|
|
|
|
func TestKeyRouting(t *testing.T) {
|
|
r := new(Router)
|
|
h := new(int)
|
|
A, B := key.Event{Name: "A"}, key.Event{Name: "B"}
|
|
// Register filters.
|
|
events(r, -1, key.Filter{Name: "A"}, key.Filter{Name: "B"})
|
|
r.Frame(new(op.Ops))
|
|
r.Queue(A, B)
|
|
// The handler is not focused, so only B is delivered.
|
|
assertEventSequence(t, events(r, -1, key.Filter{Focus: h, Name: "A"}, key.Filter{Name: "B"}), B)
|
|
r.Source().Execute(key.FocusCmd{Tag: h})
|
|
// A is delivered to the focused handler.
|
|
assertEventSequence(t, events(r, -1, key.Filter{Focus: h, Name: "A"}, key.Filter{Name: "B"}), A)
|
|
}
|
|
|
|
func assertFocus(t *testing.T, router *Router, expected event.Tag) {
|
|
t.Helper()
|
|
if got := router.state().focus; got != expected {
|
|
t.Errorf("expected %v to be focused, got %v", expected, got)
|
|
}
|
|
}
|
|
|
|
func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
|
|
t.Helper()
|
|
if got := router.state().state; got != expected {
|
|
t.Errorf("expected %v keyboard, got %v", expected, got)
|
|
}
|
|
}
|