diff --git a/io/input/pointer.go b/io/input/pointer.go index c3e399f8..7ba9aa36 100644 --- a/io/input/pointer.go +++ b/io/input/pointer.go @@ -739,6 +739,10 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState, state.pointers = nil return state, evts } + if e.Kind == pointer.Scroll { + // Scroll events are not bound to a pointer; see pointer.Event.PointerID. + return state, q.deliverScrollEvent(handlers, evts, e) + } state, pidx := state.pointerOf(e) p := state.pointers[pidx] @@ -761,9 +765,6 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState, p.pressed = false p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e) p, evts = q.deliverDropEvent(handlers, p, evts) - case pointer.Scroll: - p, evts, state.cursor, _ = q.deliverEnterLeaveEvents(handlers, state.cursor, p, evts, e) - evts = q.deliverEvent(handlers, p, evts, e) default: panic("unsupported pointer event type") } @@ -780,6 +781,18 @@ func (q *pointerQueue) Push(handlers map[event.Tag]*handler, state pointerState, return state, evts } +// deliverScrollEvent delivers scroll events to the handlers hit by the event coordinate. +func (q *pointerQueue) deliverScrollEvent(handlers map[event.Tag]*handler, evts []taggedEvent, e pointer.Event) []taggedEvent { + var hits []event.Tag + q.hitTest(e.Position, func(n *hitNode) bool { + if _, ok := handlers[n.tag]; ok { + hits = addHandler(hits, n.tag) + } + return true + }) + return q.deliverEvent(handlers, pointerInfo{handlers: hits}, evts, e) +} + func (q *pointerQueue) deliverEvent(handlers map[event.Tag]*handler, p pointerInfo, evts []taggedEvent, e pointer.Event) []taggedEvent { if p.pressed && len(p.handlers) == 1 { e.Priority = pointer.Grabbed diff --git a/io/input/pointer_test.go b/io/input/pointer_test.go index fbe92a8a..a5e3b4a9 100644 --- a/io/input/pointer_test.go +++ b/io/input/pointer_test.go @@ -1345,3 +1345,40 @@ func events(r *Router, n int, filters ...event.Filter) []event.Event { } return events } + +// TestPointerScrollDoesNotTrackPointer queues two events over two cursor +// regions. The Move puts the live pointer over the button (CursorPointer); +// the Scroll happens over the cell (CursorText) and must not update the +// cursor. +func TestPointerScrollDoesNotTrackPointer(t *testing.T) { + var ops op.Ops + + button := clip.Rect(image.Rect(0, 0, 50, 50)).Push(&ops) + pointer.CursorPointer.Add(&ops) + button.Pop() + + cell := clip.Rect(image.Rect(100, 0, 200, 50)).Push(&ops) + pointer.CursorText.Add(&ops) + cell.Pop() + + var r Router + r.Frame(&ops) + r.Queue( + pointer.Event{ + Kind: pointer.Move, + Source: pointer.Mouse, + Position: f32.Pt(25, 25), + }, + pointer.Event{ + Kind: pointer.Scroll, + Source: pointer.Mouse, + Position: f32.Pt(150, 25), + Scroll: f32.Pt(0, 1), + }, + ) + + if got, want := r.Cursor(), pointer.CursorPointer; got != want { + t.Errorf("got %q, want %q (scroll position must not update the cursor; "+ + "the live pointer's last position is what determines it)", got, want) + } +} diff --git a/io/pointer/pointer.go b/io/pointer/pointer.go index a65f840c..489a8909 100644 --- a/io/pointer/pointer.go +++ b/io/pointer/pointer.go @@ -19,7 +19,9 @@ type Event struct { Source Source // PointerID is the id for the pointer and can be used // to track a particular pointer from Press to - // Release. + // Release. Populated for Press, Release, Move, Drag, + // Enter, Leave, and Cancel; Scroll events are not + // bound to a tracked pointer and leave it zero. PointerID ID // Priority is the priority of the receiving handler // for this event.