forked from joejulian/gio
218a9588d6
Rename the assertEventSequence helper to assertEventPointerTypeSequence. Fix typos. Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
865 lines
22 KiB
Go
865 lines
22 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/key"
|
|
"gioui.org/io/pointer"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
)
|
|
|
|
func TestPointerWakeup(t *testing.T) {
|
|
handler := new(int)
|
|
var ops op.Ops
|
|
addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
|
|
|
|
var r Router
|
|
// Test that merely adding a handler doesn't trigger redraw.
|
|
r.Frame(&ops)
|
|
if _, wake := r.WakeupTime(); wake {
|
|
t.Errorf("adding pointer.InputOp triggered a redraw")
|
|
}
|
|
// However, adding a handler queues a Cancel event.
|
|
assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel)
|
|
// Verify that r.Events does trigger a redraw.
|
|
r.Frame(&ops)
|
|
if _, wake := r.WakeupTime(); !wake {
|
|
t.Errorf("pointer.Cancel event didn't trigger a redraw")
|
|
}
|
|
}
|
|
|
|
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.Queue(
|
|
// Press.
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
// Move outside the area.
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(150, 150),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Leave, pointer.Drag)
|
|
}
|
|
|
|
func TestPointerDragNegative(t *testing.T) {
|
|
handler := new(int)
|
|
var ops op.Ops
|
|
addPointerHandler(&ops, handler, image.Rect(-100, -100, 0, 0))
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
// Press.
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(-50, -50),
|
|
},
|
|
// Move outside the area.
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(-150, -150),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Leave, pointer.Drag)
|
|
}
|
|
|
|
func TestPointerGrab(t *testing.T) {
|
|
handler1 := new(int)
|
|
handler2 := new(int)
|
|
handler3 := new(int)
|
|
var ops op.Ops
|
|
|
|
types := pointer.Press | pointer.Release
|
|
|
|
pointer.InputOp{Tag: handler1, Types: types, Grab: true}.Add(&ops)
|
|
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
|
|
pointer.InputOp{Tag: handler3, Types: types}.Add(&ops)
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(handler3), pointer.Cancel, pointer.Press)
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Release)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel)
|
|
assertEventPointerTypeSequence(t, r.Events(handler3), pointer.Cancel)
|
|
}
|
|
|
|
func TestPointerGrabSameHandlerTwice(t *testing.T) {
|
|
handler1 := new(int)
|
|
handler2 := new(int)
|
|
var ops op.Ops
|
|
|
|
types := pointer.Press | pointer.Release
|
|
|
|
pointer.InputOp{Tag: handler1, Types: types, Grab: true}.Add(&ops)
|
|
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
|
|
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Press)
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Release)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel)
|
|
}
|
|
|
|
func TestPointerMove(t *testing.T) {
|
|
handler1 := new(int)
|
|
handler2 := new(int)
|
|
var ops op.Ops
|
|
|
|
types := pointer.Move | pointer.Enter | pointer.Leave
|
|
|
|
// Handler 1 area: (0, 0) - (100, 100)
|
|
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
|
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
|
|
// Handler 2 area: (50, 50) - (100, 100) (areas intersect).
|
|
r2 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops)
|
|
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
|
|
r2.Pop()
|
|
r1.Pop()
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
// Hit both handlers.
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
// Hit handler 1.
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(49, 50),
|
|
},
|
|
// Hit no handlers.
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(100, 50),
|
|
},
|
|
pointer.Event{
|
|
Type: pointer.Cancel,
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Leave, pointer.Cancel)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Leave, pointer.Cancel)
|
|
}
|
|
|
|
func TestPointerTypes(t *testing.T) {
|
|
handler := new(int)
|
|
var ops op.Ops
|
|
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
|
pointer.InputOp{
|
|
Tag: handler,
|
|
Types: pointer.Press | pointer.Release,
|
|
}.Add(&ops)
|
|
r1.Pop()
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(150, 150),
|
|
},
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
Position: f32.Pt(150, 150),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Press, pointer.Release)
|
|
}
|
|
|
|
func TestPointerPriority(t *testing.T) {
|
|
handler1 := new(int)
|
|
handler2 := new(int)
|
|
handler3 := new(int)
|
|
var ops op.Ops
|
|
|
|
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
|
pointer.InputOp{
|
|
Tag: handler1,
|
|
Types: pointer.Scroll,
|
|
ScrollBounds: image.Rectangle{Max: image.Point{X: 100}},
|
|
}.Add(&ops)
|
|
|
|
r2 := clip.Rect(image.Rect(0, 0, 100, 50)).Push(&ops)
|
|
pointer.InputOp{
|
|
Tag: handler2,
|
|
Types: pointer.Scroll,
|
|
ScrollBounds: image.Rectangle{Max: image.Point{X: 20}},
|
|
}.Add(&ops)
|
|
r2.Pop()
|
|
r1.Pop()
|
|
|
|
r3 := clip.Rect(image.Rect(0, 100, 100, 200)).Push(&ops)
|
|
pointer.InputOp{
|
|
Tag: handler3,
|
|
Types: pointer.Scroll,
|
|
ScrollBounds: image.Rectangle{Min: image.Point{X: -20, Y: -40}},
|
|
}.Add(&ops)
|
|
r3.Pop()
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
// Hit handler 1 and 2.
|
|
pointer.Event{
|
|
Type: pointer.Scroll,
|
|
Position: f32.Pt(50, 25),
|
|
Scroll: f32.Pt(50, 0),
|
|
},
|
|
// Hit handler 1.
|
|
pointer.Event{
|
|
Type: pointer.Scroll,
|
|
Position: f32.Pt(50, 75),
|
|
Scroll: f32.Pt(50, 50),
|
|
},
|
|
// Hit handler 3.
|
|
pointer.Event{
|
|
Type: pointer.Scroll,
|
|
Position: f32.Pt(50, 150),
|
|
Scroll: f32.Pt(-30, -30),
|
|
},
|
|
// Hit no handlers.
|
|
pointer.Event{
|
|
Type: pointer.Scroll,
|
|
Position: f32.Pt(50, 225),
|
|
},
|
|
)
|
|
|
|
hev1 := r.Events(handler1)
|
|
hev2 := r.Events(handler2)
|
|
hev3 := r.Events(handler3)
|
|
assertEventPointerTypeSequence(t, hev1, pointer.Cancel, pointer.Scroll, pointer.Scroll)
|
|
assertEventPointerTypeSequence(t, hev2, pointer.Cancel, pointer.Scroll)
|
|
assertEventPointerTypeSequence(t, hev3, pointer.Cancel, pointer.Scroll)
|
|
assertEventPriorities(t, hev1, pointer.Shared, pointer.Shared, pointer.Foremost)
|
|
assertEventPriorities(t, hev2, pointer.Shared, pointer.Foremost)
|
|
assertEventPriorities(t, hev3, pointer.Shared, pointer.Foremost)
|
|
assertScrollEvent(t, hev1[1], f32.Pt(30, 0))
|
|
assertScrollEvent(t, hev2[1], f32.Pt(20, 0))
|
|
assertScrollEvent(t, hev1[2], f32.Pt(50, 0))
|
|
assertScrollEvent(t, hev3[1], f32.Pt(-20, -30))
|
|
}
|
|
|
|
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) - (200, 200) (areas overlap).
|
|
addPointerHandler(&ops, handler2, image.Rect(50, 50, 200, 200))
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
// Hit both handlers.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(50, 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.
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move)
|
|
|
|
// Leave the second area by moving into the first.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(45, 45),
|
|
},
|
|
)
|
|
// The cursor leaves handler2 and enters handler1.
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Enter, pointer.Move)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Leave)
|
|
|
|
// Move, but stay within the same hit area.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(40, 40),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Move)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2))
|
|
|
|
// Move outside of both inputs.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(300, 300),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Leave)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2))
|
|
|
|
// Check that a Press event generates Enter Events.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(125, 125),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1))
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Enter, pointer.Press)
|
|
|
|
// Check that a drag only affects the participating handlers.
|
|
r.Queue(
|
|
// Leave
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(25, 25),
|
|
},
|
|
// Enter
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1))
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Leave, pointer.Drag, pointer.Enter, pointer.Drag)
|
|
|
|
// Check that a Release event generates Enter/Leave Events.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
Position: f32.Pt(25,
|
|
25),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Enter)
|
|
// The second handler gets the release event because the press started inside it.
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Release, pointer.Leave)
|
|
|
|
}
|
|
|
|
func TestMultipleAreas(t *testing.T) {
|
|
handler := new(int)
|
|
|
|
var ops op.Ops
|
|
|
|
addPointerHandler(&ops, handler, image.Rect(0, 0, 100, 100))
|
|
r1 := clip.Rect(image.Rect(50, 50, 200, 200)).Push(&ops)
|
|
// Second area has no Types set, yet should receive events because
|
|
// Types for the same handles are or-ed together.
|
|
pointer.InputOp{Tag: handler}.Add(&ops)
|
|
r1.Pop()
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
// Hit first area, then second area, then both.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(25, 25),
|
|
},
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(150, 150),
|
|
},
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler), pointer.Cancel, pointer.Enter, pointer.Move, pointer.Move, pointer.Move)
|
|
}
|
|
|
|
func TestPointerEnterLeaveNested(t *testing.T) {
|
|
handler1 := new(int)
|
|
handler2 := new(int)
|
|
var ops op.Ops
|
|
|
|
types := pointer.Press | pointer.Move | pointer.Release | pointer.Enter | pointer.Leave
|
|
|
|
// Handler 1 area: (0, 0) - (100, 100)
|
|
r1 := clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops)
|
|
pointer.InputOp{Tag: handler1, Types: types}.Add(&ops)
|
|
|
|
// Handler 2 area: (25, 25) - (75, 75) (nested within first).
|
|
r2 := clip.Rect(image.Rect(25, 25, 75, 75)).Push(&ops)
|
|
pointer.InputOp{Tag: handler2, Types: types}.Add(&ops)
|
|
r2.Pop()
|
|
r1.Pop()
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
// Hit both handlers.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(50, 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.
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Cancel, pointer.Enter, pointer.Move)
|
|
|
|
// Leave the second area by moving into the first.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(20, 20),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Move)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Leave)
|
|
|
|
// Move, but stay within the same hit area.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(10, 10),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Move)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2))
|
|
|
|
// Move outside of both inputs.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(200, 200),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Leave)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2))
|
|
|
|
// Check that a Press event generates Enter Events.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Enter, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Enter, pointer.Press)
|
|
|
|
// Check that a Release event generates Enter/Leave Events.
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
Position: f32.Pt(20, 20),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Release)
|
|
assertEventPointerTypeSequence(t, r.Events(handler2), pointer.Release, pointer.Leave)
|
|
}
|
|
|
|
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.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(25, 25),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1), pointer.Cancel, pointer.Enter, pointer.Move)
|
|
|
|
// Re-render with handler missing.
|
|
ops.Reset()
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(25, 25),
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(handler1))
|
|
}
|
|
|
|
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.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: h1pt,
|
|
PointerID: p1,
|
|
},
|
|
)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
Position: h2pt,
|
|
PointerID: p2,
|
|
},
|
|
)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
Position: h2pt,
|
|
PointerID: p2,
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(h1), pointer.Cancel, pointer.Enter, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(h2), pointer.Cancel, pointer.Enter, pointer.Press, pointer.Release)
|
|
}
|
|
|
|
func TestCursorNameOp(t *testing.T) {
|
|
ops := new(op.Ops)
|
|
var r Router
|
|
var h, h2 int
|
|
var widget2 func()
|
|
widget := func() {
|
|
// This is the area where the cursor is changed to CursorPointer.
|
|
defer clip.Rect(image.Rectangle{Max: image.Pt(100, 100)}).Push(ops).Pop()
|
|
// The cursor is checked and changed upon cursor movement.
|
|
pointer.InputOp{Tag: &h}.Add(ops)
|
|
pointer.CursorNameOp{Name: pointer.CursorPointer}.Add(ops)
|
|
if widget2 != nil {
|
|
widget2()
|
|
}
|
|
}
|
|
// Register the handlers.
|
|
widget()
|
|
// No cursor change as the mouse has not moved yet.
|
|
if got, want := r.Cursor(), pointer.CursorDefault; got != want {
|
|
t.Errorf("got %q; want %q", got, want)
|
|
}
|
|
|
|
_at := func(x, y float32) pointer.Event {
|
|
return pointer.Event{
|
|
Type: pointer.Move,
|
|
Source: pointer.Mouse,
|
|
Buttons: pointer.ButtonPrimary,
|
|
Position: f32.Pt(x, y),
|
|
}
|
|
}
|
|
for _, tc := range []struct {
|
|
label string
|
|
event interface{}
|
|
want pointer.CursorName
|
|
}{
|
|
{label: "move inside",
|
|
event: _at(50, 50),
|
|
want: pointer.CursorPointer,
|
|
},
|
|
{label: "move outside",
|
|
event: _at(200, 200),
|
|
want: pointer.CursorDefault,
|
|
},
|
|
{label: "move back inside",
|
|
event: _at(50, 50),
|
|
want: pointer.CursorPointer,
|
|
},
|
|
{label: "send key events while inside",
|
|
event: []event.Event{
|
|
key.Event{Name: "A", State: key.Press},
|
|
key.Event{Name: "A", State: key.Release},
|
|
},
|
|
want: pointer.CursorPointer,
|
|
},
|
|
{label: "send key events while outside",
|
|
event: []event.Event{
|
|
_at(200, 200),
|
|
key.Event{Name: "A", State: key.Press},
|
|
key.Event{Name: "A", State: key.Release},
|
|
},
|
|
want: pointer.CursorDefault,
|
|
},
|
|
{label: "add new input on top while inside",
|
|
event: func() []event.Event {
|
|
widget2 = func() {
|
|
pointer.InputOp{Tag: &h2}.Add(ops)
|
|
pointer.CursorNameOp{Name: pointer.CursorCrossHair}.Add(ops)
|
|
}
|
|
return []event.Event{
|
|
_at(50, 50),
|
|
key.Event{
|
|
Name: "A",
|
|
State: key.Press,
|
|
},
|
|
}
|
|
},
|
|
want: pointer.CursorCrossHair,
|
|
},
|
|
{label: "remove input on top while inside",
|
|
event: func() []event.Event {
|
|
widget2 = nil
|
|
return []event.Event{
|
|
_at(50, 50),
|
|
key.Event{
|
|
Name: "A",
|
|
State: key.Press,
|
|
},
|
|
}
|
|
},
|
|
want: pointer.CursorPointer,
|
|
},
|
|
} {
|
|
t.Run(tc.label, func(t *testing.T) {
|
|
ops.Reset()
|
|
widget()
|
|
r.Frame(ops)
|
|
switch ev := tc.event.(type) {
|
|
case event.Event:
|
|
r.Queue(ev)
|
|
case []event.Event:
|
|
r.Queue(ev...)
|
|
case func() event.Event:
|
|
r.Queue(ev())
|
|
case func() []event.Event:
|
|
r.Queue(ev()...)
|
|
default:
|
|
panic(fmt.Sprintf("unknown event %T", ev))
|
|
}
|
|
widget()
|
|
r.Frame(ops)
|
|
// The cursor should now have been changed if the mouse moved over the declared area.
|
|
if got, want := r.Cursor(), tc.want; got != want {
|
|
t.Errorf("got %q; want %q", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPassOp(t *testing.T) {
|
|
var ops op.Ops
|
|
|
|
h1, h2, h3, h4 := new(int), new(int), new(int), new(int)
|
|
area := clip.Rect(image.Rect(0, 0, 100, 100))
|
|
root := area.Push(&ops)
|
|
pointer.InputOp{Tag: h1, Types: pointer.Press}.Add(&ops)
|
|
child1 := area.Push(&ops)
|
|
pointer.InputOp{Tag: h2, Types: pointer.Press}.Add(&ops)
|
|
child1.Pop()
|
|
child2 := area.Push(&ops)
|
|
pass := pointer.PassOp{}.Push(&ops)
|
|
pointer.InputOp{Tag: h3, Types: pointer.Press}.Add(&ops)
|
|
pointer.InputOp{Tag: h4, Types: pointer.Press}.Add(&ops)
|
|
pass.Pop()
|
|
child2.Pop()
|
|
root.Pop()
|
|
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(h1), pointer.Cancel, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(h2), pointer.Cancel, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(h3), pointer.Cancel, pointer.Press)
|
|
assertEventPointerTypeSequence(t, r.Events(h4), pointer.Cancel, pointer.Press)
|
|
}
|
|
|
|
func TestAreaPassthrough(t *testing.T) {
|
|
var ops op.Ops
|
|
|
|
h := new(int)
|
|
pointer.InputOp{Tag: h, Types: pointer.Press}.Add(&ops)
|
|
clip.Rect(image.Rect(0, 0, 100, 100)).Push(&ops).Pop()
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Press,
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press)
|
|
}
|
|
|
|
func TestEllipse(t *testing.T) {
|
|
var ops op.Ops
|
|
|
|
h := new(int)
|
|
cl := clip.Ellipse(f32.Rect(0, 0, 100, 100)).Push(&ops)
|
|
pointer.InputOp{Tag: h, Types: pointer.Press}.Add(&ops)
|
|
cl.Pop()
|
|
var r Router
|
|
r.Frame(&ops)
|
|
r.Queue(
|
|
// Outside ellipse.
|
|
pointer.Event{
|
|
Position: f32.Pt(10, 10),
|
|
Type: pointer.Press,
|
|
},
|
|
pointer.Event{
|
|
Type: pointer.Release,
|
|
},
|
|
// Inside ellipse.
|
|
pointer.Event{
|
|
Position: f32.Pt(50, 50),
|
|
Type: pointer.Press,
|
|
},
|
|
)
|
|
assertEventPointerTypeSequence(t, r.Events(h), pointer.Cancel, pointer.Press)
|
|
}
|
|
|
|
// addPointerHandler adds a pointer.InputOp for the tag in a
|
|
// rectangular area.
|
|
func addPointerHandler(ops *op.Ops, tag event.Tag, area image.Rectangle) {
|
|
defer clip.Rect(area).Push(ops).Pop()
|
|
pointer.InputOp{
|
|
Tag: tag,
|
|
Types: pointer.Press | pointer.Release | pointer.Move | pointer.Drag | pointer.Enter | pointer.Leave,
|
|
}.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
|
|
}
|
|
|
|
// assertEventPointerTypeSequence checks that the provided events match the expected pointer event types
|
|
// in the provided order.
|
|
func assertEventPointerTypeSequence(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)
|
|
}
|
|
}
|
|
|
|
// assertEventPriorities checks that the pointer.Event priorities of events match prios.
|
|
func assertEventPriorities(t *testing.T, events []event.Event, prios ...pointer.Priority) {
|
|
t.Helper()
|
|
var got []pointer.Priority
|
|
for _, e := range events {
|
|
if e, ok := e.(pointer.Event); ok {
|
|
got = append(got, e.Priority)
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(got, prios) {
|
|
t.Errorf("expected priorities %v, got %v", prios, got)
|
|
}
|
|
}
|
|
|
|
// assertScrollEvent checks that the event scrolling amount matches the supplied value.
|
|
func assertScrollEvent(t *testing.T, ev event.Event, scroll f32.Point) {
|
|
t.Helper()
|
|
if got, want := ev.(pointer.Event).Scroll, scroll; got != want {
|
|
t.Errorf("got %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
clip.Rect(image.Rectangle{
|
|
Max: image.Point{
|
|
X: 100,
|
|
Y: 100,
|
|
},
|
|
}).
|
|
Push(&ops)
|
|
pointer.InputOp{
|
|
Tag: handlers[i],
|
|
Types: pointer.Move,
|
|
}.Add(&ops)
|
|
}
|
|
var r Router
|
|
r.Frame(&ops)
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
r.Queue(
|
|
pointer.Event{
|
|
Type: pointer.Move,
|
|
Position: f32.Pt(50, 50),
|
|
},
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|