Files
gio/io/router/pointer_test.go
T
Thomas Bruyelle ae8a377cda op: add op.Push and op.Record funcs
The funcs replace stack.Push and macro.Record, which become private.
This makes stack and macro faster to write, in particular for stacks
where you can just write the following line to save and restore the
state :

  defer op.Push(ops).Pop()

This usage requires Push to return a pointer (since Pop has a pointer
receiver), or else the code doesn't compile.

For consistancy, I tried to do the same for op.Record, but this implied
to turn all the MacroOp fields into pointers, and this caused some
panics. As a result, op.Record doesn't return a pointer.

An other side effect pointed by Larry Clapp: StackOp and MacroOp are not
re-usable any more, you have to allocate a new one for each usage, using
the described funcs above.

Signed-off-by: Thomas Bruyelle <thomas.bruyelle@gmail.com>
2020-06-02 10:39:56 +02:00

461 lines
10 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package router
import (
"fmt"
"image"
"reflect"
"testing"
"gioui.org/f32"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/op"
)
func TestPointerDrag(t *testing.T) {
handler := new(int)
var ops op.Ops
addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
var r Router
r.Frame(&ops)
r.Add(
// Press.
pointer.Event{
Type: pointer.Press,
Position: f32.Point{
X: 50,
Y: 50,
},
},
// Move outside the area.
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 150,
Y: 150,
},
},
)
ev := r.Events(handler)
if moves := countPointerEvents(pointer.Move, ev); moves != 1 {
t.Errorf("got %d move events, expected 1", moves)
}
}
func TestPointerMove(t *testing.T) {
handler1 := new(int)
handler2 := new(int)
var ops op.Ops
// Handler 1 area: (0, 0) - (100, 100)
pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
pointer.InputOp{Tag: handler1}.Add(&ops)
// Handler 2 area: (50, 50) - (100, 100) (areas intersect).
pointer.Rect(image.Rect(50, 50, 200, 200)).Add(&ops)
pointer.InputOp{Tag: handler2}.Add(&ops)
var r Router
r.Frame(&ops)
r.Add(
// Hit both handlers.
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 50,
Y: 50,
},
},
// Hit handler 1.
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 49,
Y: 50,
},
},
// Hit no handlers.
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 100,
Y: 50,
},
},
)
ev1 := r.Events(handler1)
if cancels := countPointerEvents(pointer.Cancel, ev1); cancels != 1 {
t.Errorf("got %d cancel events, expected 1", cancels)
}
ev2 := r.Events(handler2)
if cancels := countPointerEvents(pointer.Cancel, ev2); cancels != 1 {
t.Errorf("got %d cancel events, expected 1", cancels)
}
if moves := countPointerEvents(pointer.Move, ev1); moves != 2 {
t.Errorf("got %d move events, expected 2", moves)
}
if moves := countPointerEvents(pointer.Move, ev2); moves != 1 {
t.Errorf("got %d move events, expected 1", moves)
}
}
func countPointerEvents(typ pointer.Type, events []event.Event) int {
c := 0
for _, e := range events {
if e, ok := e.(pointer.Event); ok {
if e.Type == typ {
c++
}
}
}
return c
}
func TestPointerEnterLeave(t *testing.T) {
handler1 := new(int)
handler2 := new(int)
var ops op.Ops
// Handler 1 area: (0, 0) - (100, 100)
addPointerHandler(&ops, handler1, image.Rect(0, 0, 100, 100))
// Handler 2 area: (50, 50) - (100, 100) (areas overlap).
addPointerHandler(&ops, handler2, image.Rect(50, 50, 200, 200))
var r Router
r.Frame(&ops)
// Hit both handlers.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 50,
Y: 50,
},
},
)
// First event for a handler is always a Cancel.
// Only handler2 should receive the enter/move events because it is on top
// and handler1 is not an ancestor in the hit tree.
assertEventSequence(t, r.Events(handler1), pointer.Cancel)
assertEventSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move)
// Leave the second area by moving into the first.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 45,
Y: 45,
},
},
)
// The cursor leaves handler2 and enters handler1.
assertEventSequence(t, r.Events(handler1), pointer.Enter, pointer.Move)
assertEventSequence(t, r.Events(handler2), pointer.Leave)
// Move, but stay within the same hit area.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 40,
Y: 40,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Move)
assertEventSequence(t, r.Events(handler2))
// Move outside of both inputs.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 300,
Y: 300,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Leave)
assertEventSequence(t, r.Events(handler2))
// Check that a Press event generates Enter Events.
r.Add(
pointer.Event{
Type: pointer.Press,
Position: f32.Point{
X: 125,
Y: 125,
},
},
)
assertEventSequence(t, r.Events(handler1))
assertEventSequence(t, r.Events(handler2), pointer.Enter, pointer.Press)
// Check that a Release event generates Enter/Leave Events.
r.Add(
pointer.Event{
Type: pointer.Release,
Position: f32.Point{
// Move out of the second hit area and into the first.
X: 25,
Y: 25,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Enter)
// The second handler gets the release event because the press started inside it.
assertEventSequence(t, r.Events(handler2), pointer.Leave, pointer.Release)
}
func TestPointerEnterLeaveNested(t *testing.T) {
handler1 := new(int)
handler2 := new(int)
var ops op.Ops
// Handler 1 area: (0, 0) - (100, 100)
pointer.Rect(image.Rect(0, 0, 100, 100)).Add(&ops)
pointer.InputOp{Tag: handler1}.Add(&ops)
// Handler 2 area: (25, 25) - (75, 75) (nested within first).
pointer.Rect(image.Rect(25, 25, 75, 75)).Add(&ops)
pointer.InputOp{Tag: handler2}.Add(&ops)
var r Router
r.Frame(&ops)
// Hit both handlers.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 50,
Y: 50,
},
},
)
// First event for a handler is always a Cancel.
// Both handlers should receive the Enter and Move events because handler2 is a child of handler1.
assertEventSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move)
assertEventSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move)
// Leave the second area by moving into the first.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 20,
Y: 20,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Move)
assertEventSequence(t, r.Events(handler2), pointer.Leave)
// Move, but stay within the same hit area.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 10,
Y: 10,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Move)
assertEventSequence(t, r.Events(handler2))
// Move outside of both inputs.
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 200,
Y: 200,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Leave)
assertEventSequence(t, r.Events(handler2))
// Check that a Press event generates Enter Events.
r.Add(
pointer.Event{
Type: pointer.Press,
Position: f32.Point{
X: 50,
Y: 50,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Enter, pointer.Press)
assertEventSequence(t, r.Events(handler2), pointer.Enter, pointer.Press)
// Check that a Release event generates Enter/Leave Events.
r.Add(
pointer.Event{
Type: pointer.Release,
Position: f32.Point{
// Move out of the second hit area and into the first.
X: 20,
Y: 20,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Release)
assertEventSequence(t, r.Events(handler2), pointer.Leave, pointer.Release)
}
func TestPointerActiveInputDisappears(t *testing.T) {
handler1 := new(int)
var ops op.Ops
var r Router
// Draw handler.
ops.Reset()
addPointerHandler(&ops, handler1, image.Rect(0, 0, 100, 100))
r.Frame(&ops)
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 25,
Y: 25,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move)
// Re-render with handler missing.
ops.Reset()
r.Frame(&ops)
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 25,
Y: 25,
},
},
)
assertEventSequence(t, r.Events(handler1), pointer.Cancel)
}
func TestMultitouch(t *testing.T) {
var ops op.Ops
// Add two separate handlers.
h1, h2 := new(int), new(int)
addPointerHandler(&ops, h1, image.Rect(0, 0, 100, 100))
addPointerHandler(&ops, h2, image.Rect(0, 100, 100, 200))
h1pt, h2pt := f32.Pt(0, 0), f32.Pt(0, 100)
var p1, p2 pointer.ID = 0, 1
var r Router
r.Frame(&ops)
r.Add(
pointer.Event{
Type: pointer.Press,
Position: h1pt,
PointerID: p1,
},
)
r.Add(
pointer.Event{
Type: pointer.Press,
Position: h2pt,
PointerID: p2,
},
)
r.Add(
pointer.Event{
Type: pointer.Release,
Position: h2pt,
PointerID: p2,
},
)
assertEventSequence(t, r.Events(h1), pointer.Cancel, pointer.Enter, pointer.Press)
assertEventSequence(t, r.Events(h2), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Release)
}
// addPointerHandler adds a pointer.InputOp for the tag in a
// rectangular area.
func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
defer op.Push(ops).Pop()
pointer.Rect(area).Add(ops)
pointer.InputOp{Tag: tag}.Add(ops)
}
// pointerTypes converts a sequence of event.Event to their pointer.Types. It assumes
// that all input events are of underlying type pointer.Event, and thus will
// panic if some are not.
func pointerTypes(events []event.Event) []pointer.Type {
var types []pointer.Type
for _, e := range events {
if e, ok := e.(pointer.Event); ok {
types = append(types, e.Type)
}
}
return types
}
// assertEventSequence ensures that the provided actualEvents match the expected event types
// in the provided order.
func assertEventSequence(t *testing.T, events []event.Event, expected ...pointer.Type) {
t.Helper()
got := pointerTypes(events)
if !reflect.DeepEqual(got, expected) {
t.Errorf("expected %v events, got %v", expected, got)
}
}
func BenchmarkRouterAdd(b *testing.B) {
// Set this to the number of overlapping handlers that you want to
// evaluate performance for. Typical values for the example applications
// are 1-3, though checking highers values helps evaluate performance for
// more complex applications.
const startingHandlerCount = 3
const maxHandlerCount = 100
for i := startingHandlerCount; i < maxHandlerCount; i *= 3 {
handlerCount := i
b.Run(fmt.Sprintf("%d-handlers", i), func(b *testing.B) {
handlers := make([]event.Tag, handlerCount)
for i := 0; i < handlerCount; i++ {
h := new(int)
*h = i
handlers[i] = h
}
var ops op.Ops
for i := range handlers {
pointer.Rect(image.Rectangle{
Max: image.Point{
X: 100,
Y: 100,
},
}).Add(&ops)
pointer.InputOp{Tag: handlers[i]}.Add(&ops)
}
var r Router
r.Frame(&ops)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Add(
pointer.Event{
Type: pointer.Move,
Position: f32.Point{
X: 50,
Y: 50,
},
},
)
}
})
}
}