github.com/utopiagio/gio@v0.0.8/io/input/key_test.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package input
     4  
     5  import (
     6  	"image"
     7  	"testing"
     8  
     9  	"github.com/utopiagio/gio/f32"
    10  	"github.com/utopiagio/gio/io/event"
    11  	"github.com/utopiagio/gio/io/key"
    12  	"github.com/utopiagio/gio/io/pointer"
    13  	"github.com/utopiagio/gio/op"
    14  	"github.com/utopiagio/gio/op/clip"
    15  )
    16  
    17  func TestAllMatchKeyFilter(t *testing.T) {
    18  	r := new(Router)
    19  	r.Event(key.Filter{})
    20  	ke := key.Event{Name: "A"}
    21  	r.Queue(ke)
    22  	// Catch-all gets all non-system events.
    23  	assertEventSequence(t, events(r, -1, key.Filter{}), ke)
    24  
    25  	r = new(Router)
    26  	r.Event(key.Filter{Name: "A"})
    27  	r.Queue(SystemEvent{ke})
    28  	if _, handled := r.WakeupTime(); !handled {
    29  		t.Errorf("system event was unexpectedly ignored")
    30  	}
    31  	// Only specific filters match system events.
    32  	assertEventSequence(t, events(r, -1, key.Filter{Name: "A"}), ke)
    33  }
    34  
    35  func TestInputHint(t *testing.T) {
    36  	r := new(Router)
    37  	if hint, changed := r.TextInputHint(); hint != key.HintAny || changed {
    38  		t.Fatal("unexpected hint")
    39  	}
    40  	ops := new(op.Ops)
    41  	h := new(int)
    42  	key.InputHintOp{Tag: h, Hint: key.HintEmail}.Add(ops)
    43  	r.Frame(ops)
    44  	if hint, changed := r.TextInputHint(); hint != key.HintAny || changed {
    45  		t.Fatal("unexpected hint")
    46  	}
    47  	r.Source().Execute(key.FocusCmd{Tag: h})
    48  	if hint, changed := r.TextInputHint(); hint != key.HintEmail || !changed {
    49  		t.Fatal("unexpected hint")
    50  	}
    51  }
    52  
    53  func TestDeferred(t *testing.T) {
    54  	r := new(Router)
    55  	h := new(int)
    56  	f := []event.Filter{
    57  		key.FocusFilter{Target: h},
    58  		key.Filter{Name: "A"},
    59  	}
    60  	// Provoke deferring by exhausting events for h.
    61  	events(r, -1, f...)
    62  	r.Source().Execute(key.FocusCmd{Tag: h})
    63  	ke := key.Event{Name: "A"}
    64  	r.Queue(ke)
    65  	// All events are deferred at this point.
    66  	assertEventSequence(t, events(r, -1, f...))
    67  	r.Frame(new(op.Ops))
    68  	// But delivered after a frame.
    69  	assertEventSequence(t, events(r, -1, f...), key.FocusEvent{Focus: true}, ke)
    70  }
    71  
    72  func TestInputWakeup(t *testing.T) {
    73  	handler := new(int)
    74  	var ops op.Ops
    75  	// InputOps shouldn't trigger redraws.
    76  	event.Op(&ops, handler)
    77  
    78  	var r Router
    79  	// Reset events shouldn't either.
    80  	evts := events(&r, -1, key.FocusFilter{Target: new(int)}, key.Filter{Name: "A"})
    81  	assertEventSequence(t, evts, key.FocusEvent{Focus: false})
    82  	r.Frame(&ops)
    83  	if _, wake := r.WakeupTime(); wake {
    84  		t.Errorf("InputOp or the resetting FocusEvent triggered a wakeup")
    85  	}
    86  	// And neither does events that don't match anything.
    87  	r.Queue(key.SnippetEvent{})
    88  	if _, handled := r.WakeupTime(); handled {
    89  		t.Errorf("a not-matching event triggered a wakeup")
    90  	}
    91  	// However, events that does match should trigger wakeup.
    92  	r.Queue(key.Event{Name: "A"})
    93  	if _, handled := r.WakeupTime(); !handled {
    94  		t.Errorf("a key.Event didn't trigger redraw")
    95  	}
    96  }
    97  
    98  func TestKeyMultiples(t *testing.T) {
    99  	handlers := make([]int, 3)
   100  	r := new(Router)
   101  	r.Source().Execute(key.SoftKeyboardCmd{Show: true})
   102  	for i := range handlers {
   103  		assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[i]}), key.FocusEvent{Focus: false})
   104  	}
   105  	r.Source().Execute(key.FocusCmd{Tag: &handlers[2]})
   106  	assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[2]}), key.FocusEvent{Focus: true})
   107  	assertFocus(t, r, &handlers[2])
   108  
   109  	assertKeyboard(t, r, TextInputOpen)
   110  }
   111  
   112  func TestKeySoftKeyboardNoFocus(t *testing.T) {
   113  	r := new(Router)
   114  
   115  	// It's possible to open the keyboard
   116  	// without any active focus:
   117  	r.Source().Execute(key.SoftKeyboardCmd{Show: true})
   118  
   119  	assertFocus(t, r, nil)
   120  	assertKeyboard(t, r, TextInputOpen)
   121  }
   122  
   123  func TestKeyRemoveFocus(t *testing.T) {
   124  	handlers := make([]int, 2)
   125  	r := new(Router)
   126  
   127  	filters := func(h event.Tag) []event.Filter {
   128  		return []event.Filter{
   129  			key.FocusFilter{Target: h},
   130  			key.Filter{Focus: h, Name: key.NameTab, Required: key.ModShortcut},
   131  		}
   132  	}
   133  	var all []event.Filter
   134  	for i := range handlers {
   135  		all = append(all, filters(&handlers[i])...)
   136  	}
   137  	assertEventSequence(t, events(r, len(handlers), all...), key.FocusEvent{}, key.FocusEvent{})
   138  	r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
   139  	r.Source().Execute(key.SoftKeyboardCmd{Show: true})
   140  
   141  	evt := key.Event{Name: key.NameTab, Modifiers: key.ModShortcut, State: key.Press}
   142  	r.Queue(evt)
   143  
   144  	assertEventSequence(t, events(r, 2, filters(&handlers[0])...), key.FocusEvent{Focus: true}, evt)
   145  	assertFocus(t, r, &handlers[0])
   146  	assertKeyboard(t, r, TextInputOpen)
   147  
   148  	// Frame removes focus from tags that don't filter for focus events nor mentioned in an InputOp.
   149  	r.Source().Execute(key.FocusCmd{Tag: new(int)})
   150  	r.Frame(new(op.Ops))
   151  
   152  	assertEventSequence(t, events(r, -1, filters(&handlers[1])...))
   153  	assertFocus(t, r, nil)
   154  	assertKeyboard(t, r, TextInputClose)
   155  
   156  	// Set focus to InputOp which already
   157  	// exists in the previous frame:
   158  	r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
   159  	assertFocus(t, r, &handlers[0])
   160  }
   161  
   162  func TestKeyFocusedInvisible(t *testing.T) {
   163  	handlers := make([]int, 2)
   164  	ops := new(op.Ops)
   165  	r := new(Router)
   166  
   167  	for i := range handlers {
   168  		assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[i]}), key.FocusEvent{Focus: false})
   169  	}
   170  
   171  	// Set new InputOp with focus:
   172  	r.Source().Execute(key.FocusCmd{Tag: &handlers[0]})
   173  	r.Source().Execute(key.SoftKeyboardCmd{Show: true})
   174  
   175  	assertEventSequence(t, events(r, 1, key.FocusFilter{Target: &handlers[0]}), key.FocusEvent{Focus: true})
   176  	assertFocus(t, r, &handlers[0])
   177  	assertKeyboard(t, r, TextInputOpen)
   178  
   179  	// Frame will clear the focus because the handler is not visible.
   180  	r.Frame(ops)
   181  
   182  	for i := range handlers {
   183  		assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[i]}))
   184  	}
   185  	assertFocus(t, r, nil)
   186  	assertKeyboard(t, r, TextInputClose)
   187  
   188  	r.Frame(ops)
   189  	r.Frame(ops)
   190  
   191  	ops.Reset()
   192  
   193  	// Respawn the first element:
   194  	// It must receive one `Event{Focus: false}`.
   195  	event.Op(ops, &handlers[0])
   196  
   197  	assertEventSequence(t, events(r, -1, key.FocusFilter{Target: &handlers[0]}), key.FocusEvent{Focus: false})
   198  }
   199  
   200  func TestNoOps(t *testing.T) {
   201  	r := new(Router)
   202  	r.Frame(nil)
   203  }
   204  
   205  func TestDirectionalFocus(t *testing.T) {
   206  	ops := new(op.Ops)
   207  	r := new(Router)
   208  	handlers := []image.Rectangle{
   209  		image.Rect(10, 10, 50, 50),
   210  		image.Rect(50, 20, 100, 80),
   211  		image.Rect(20, 26, 60, 80),
   212  		image.Rect(10, 60, 50, 100),
   213  	}
   214  
   215  	for i, bounds := range handlers {
   216  		cl := clip.Rect(bounds).Push(ops)
   217  		event.Op(ops, &handlers[i])
   218  		cl.Pop()
   219  		events(r, -1, key.FocusFilter{Target: &handlers[i]})
   220  	}
   221  	r.Frame(ops)
   222  
   223  	r.MoveFocus(key.FocusLeft)
   224  	assertFocus(t, r, &handlers[0])
   225  	r.MoveFocus(key.FocusLeft)
   226  	assertFocus(t, r, &handlers[0])
   227  	r.MoveFocus(key.FocusRight)
   228  	assertFocus(t, r, &handlers[1])
   229  	r.MoveFocus(key.FocusRight)
   230  	assertFocus(t, r, &handlers[1])
   231  	r.MoveFocus(key.FocusDown)
   232  	assertFocus(t, r, &handlers[2])
   233  	r.MoveFocus(key.FocusDown)
   234  	assertFocus(t, r, &handlers[2])
   235  	r.MoveFocus(key.FocusLeft)
   236  	assertFocus(t, r, &handlers[3])
   237  	r.MoveFocus(key.FocusUp)
   238  	assertFocus(t, r, &handlers[0])
   239  
   240  	r.MoveFocus(key.FocusForward)
   241  	assertFocus(t, r, &handlers[1])
   242  	r.MoveFocus(key.FocusBackward)
   243  	assertFocus(t, r, &handlers[0])
   244  }
   245  
   246  func TestFocusScroll(t *testing.T) {
   247  	ops := new(op.Ops)
   248  	r := new(Router)
   249  	h := new(int)
   250  
   251  	filters := []event.Filter{
   252  		key.FocusFilter{Target: h},
   253  		pointer.Filter{
   254  			Target:       h,
   255  			Kinds:        pointer.Scroll,
   256  			ScrollBounds: image.Rect(-100, -100, 100, 100),
   257  		},
   258  	}
   259  	events(r, -1, filters...)
   260  	parent := clip.Rect(image.Rect(1, 1, 14, 39)).Push(ops)
   261  	cl := clip.Rect(image.Rect(10, -20, 20, 30)).Push(ops)
   262  	event.Op(ops, h)
   263  	// Test that h is scrolled even if behind another handler.
   264  	event.Op(ops, new(int))
   265  	cl.Pop()
   266  	parent.Pop()
   267  	r.Frame(ops)
   268  
   269  	r.MoveFocus(key.FocusLeft)
   270  	r.RevealFocus(image.Rect(0, 0, 15, 40))
   271  	evts := events(r, -1, filters...)
   272  	assertScrollEvent(t, evts[len(evts)-1], f32.Pt(6, -9))
   273  }
   274  
   275  func TestFocusClick(t *testing.T) {
   276  	ops := new(op.Ops)
   277  	r := new(Router)
   278  	h := new(int)
   279  
   280  	filters := []event.Filter{
   281  		key.FocusFilter{Target: h},
   282  		pointer.Filter{
   283  			Target: h,
   284  			Kinds:  pointer.Press | pointer.Release | pointer.Cancel,
   285  		},
   286  	}
   287  	assertEventPointerTypeSequence(t, events(r, -1, filters...), pointer.Cancel)
   288  	cl := clip.Rect(image.Rect(0, 0, 10, 10)).Push(ops)
   289  	event.Op(ops, h)
   290  	cl.Pop()
   291  	r.Frame(ops)
   292  
   293  	r.MoveFocus(key.FocusLeft)
   294  	r.ClickFocus()
   295  
   296  	assertEventPointerTypeSequence(t, events(r, -1, filters...), pointer.Press, pointer.Release)
   297  }
   298  
   299  func TestNoFocus(t *testing.T) {
   300  	r := new(Router)
   301  	r.MoveFocus(key.FocusForward)
   302  }
   303  
   304  func TestKeyRouting(t *testing.T) {
   305  	r := new(Router)
   306  	h := new(int)
   307  	A, B := key.Event{Name: "A"}, key.Event{Name: "B"}
   308  	// Register filters.
   309  	events(r, -1, key.Filter{Name: "A"}, key.Filter{Name: "B"})
   310  	r.Frame(new(op.Ops))
   311  	r.Queue(A, B)
   312  	// The handler is not focused, so only B is delivered.
   313  	assertEventSequence(t, events(r, -1, key.Filter{Focus: h, Name: "A"}, key.Filter{Name: "B"}), B)
   314  	r.Source().Execute(key.FocusCmd{Tag: h})
   315  	// A is delivered to the focused handler.
   316  	assertEventSequence(t, events(r, -1, key.Filter{Focus: h, Name: "A"}, key.Filter{Name: "B"}), A)
   317  }
   318  
   319  func assertFocus(t *testing.T, router *Router, expected event.Tag) {
   320  	t.Helper()
   321  	if !router.Source().Focused(expected) {
   322  		t.Errorf("expected %v to be focused", expected)
   323  	}
   324  }
   325  
   326  func assertKeyboard(t *testing.T, router *Router, expected TextInputState) {
   327  	t.Helper()
   328  	if got := router.state().state; got != expected {
   329  		t.Errorf("expected %v keyboard, got %v", expected, got)
   330  	}
   331  }