mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
gesture: refresh PointerID on Press and Enter
Click and Hover both stored the first PointerID they observed in their internal pid field and only updated it when not currently hovered/entered. Once the gesture became hovered, any later event under a different PointerID was effectively ignored: Click.Press fell through 'c.pid != e.PointerID' and was silently dropped, and Hover could never reset entered when the matching Leave arrived under a new ID. The Windows backend enables EnableMouseInPointer (app/os_windows.go), under which Windows reassigns the same physical mouse's PointerID across focus changes, window leave/re-enter, and similar events. Once a widget had been hovered, every subsequent press on it failed to register, including widget.Editor's internal clicker that positions the caret on press. Multi-line editors silently refused to move the caret on click after the window had received any focus event. Always take the latest PointerID on Hover.Enter and Click.Press. The Press/Release handshake still works because Press now records the press's own PointerID and Release continues to gate on 'c.pid != e.PointerID' so an unrelated pointer's release can't end the press tracking. Signed-off-by: Eugene <eugenebosyakov@gmail.com>
This commit is contained in:
+3
-12
@@ -61,12 +61,8 @@ func (h *Hover) Update(q input.Source) bool {
|
|||||||
h.entered = false
|
h.entered = false
|
||||||
}
|
}
|
||||||
case pointer.Enter:
|
case pointer.Enter:
|
||||||
if !h.entered {
|
h.pid = e.PointerID
|
||||||
h.pid = e.PointerID
|
h.entered = true
|
||||||
}
|
|
||||||
if h.pid == e.PointerID {
|
|
||||||
h.entered = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return h.entered
|
return h.entered
|
||||||
@@ -222,12 +218,7 @@ func (c *Click) Update(q input.Source) (ClickEvent, bool) {
|
|||||||
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
if e.Source == pointer.Mouse && e.Buttons != pointer.ButtonPrimary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !c.hovered {
|
c.pid = e.PointerID
|
||||||
c.pid = e.PointerID
|
|
||||||
}
|
|
||||||
if c.pid != e.PointerID {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.pressed = true
|
c.pressed = true
|
||||||
if e.Time-c.clickedAt < doubleClickDuration {
|
if e.Time-c.clickedAt < doubleClickDuration {
|
||||||
c.clicks++
|
c.clicks++
|
||||||
|
|||||||
@@ -100,6 +100,78 @@ func TestMouseClicks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClickPointerIDReassignment(t *testing.T) {
|
||||||
|
// A Click must accept a Press from a PointerID that differs from the
|
||||||
|
// one its hovered state was previously associated with. Some backends
|
||||||
|
// reassign a single physical pointer's ID over its lifetime — e.g. the
|
||||||
|
// Windows pointer API across focus changes — and locking the gesture
|
||||||
|
// to the first observed ID would silently drop every subsequent press.
|
||||||
|
//
|
||||||
|
// The sequence below puts the gesture into the buggy state through
|
||||||
|
// public events alone: a press under PointerID 1 starts an active
|
||||||
|
// press cycle, a Move under PointerID 2 arrives mid-press (which the
|
||||||
|
// router routes as an Enter for PID 2 but the gesture's Enter handler
|
||||||
|
// is a no-op for pid while pressed), then PID 1 releases. After this,
|
||||||
|
// the router has the gesture entered for PID 2 (so the next event
|
||||||
|
// under PID 2 won't trigger another Enter) but the gesture itself
|
||||||
|
// still has pid=1.
|
||||||
|
var click Click
|
||||||
|
var ops op.Ops
|
||||||
|
rect := image.Rect(0, 0, 100, 100)
|
||||||
|
stack := clip.Rect(rect).Push(&ops)
|
||||||
|
click.Add(&ops)
|
||||||
|
stack.Pop()
|
||||||
|
|
||||||
|
var r input.Router
|
||||||
|
click.Update(r.Source())
|
||||||
|
r.Frame(&ops)
|
||||||
|
|
||||||
|
drain := func() {
|
||||||
|
for {
|
||||||
|
if _, ok := click.Update(r.Source()); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Press under PointerID 1.
|
||||||
|
r.Queue(
|
||||||
|
pointer.Event{Kind: pointer.Move, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 1},
|
||||||
|
pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Buttons: pointer.ButtonPrimary, Position: f32.Pt(50, 50), PointerID: 1},
|
||||||
|
)
|
||||||
|
drain()
|
||||||
|
|
||||||
|
// Move under PointerID 2 while PointerID 1 is still pressed. The
|
||||||
|
// router records the gesture as entered for PointerID 2 but the
|
||||||
|
// gesture's Enter handler is a no-op for pid because c.pressed.
|
||||||
|
r.Queue(pointer.Event{Kind: pointer.Move, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 2})
|
||||||
|
drain()
|
||||||
|
|
||||||
|
// Release PointerID 1. PointerID 1's press tracking ends; the
|
||||||
|
// gesture's recorded pid stays at 1.
|
||||||
|
r.Queue(pointer.Event{Kind: pointer.Release, Source: pointer.Mouse, Position: f32.Pt(50, 50), PointerID: 1})
|
||||||
|
drain()
|
||||||
|
|
||||||
|
// Press under PointerID 2. The router won't refire Enter for PID 2
|
||||||
|
// (the gesture is already in PID 2's entered set), so the gesture's
|
||||||
|
// only chance to refresh its pid is the Press handler itself.
|
||||||
|
r.Queue(pointer.Event{Kind: pointer.Press, Source: pointer.Mouse, Buttons: pointer.ButtonPrimary, Position: f32.Pt(50, 50), PointerID: 2})
|
||||||
|
|
||||||
|
var sawPress bool
|
||||||
|
for {
|
||||||
|
ev, ok := click.Update(r.Source())
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ev.Kind == KindPress {
|
||||||
|
sawPress = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sawPress {
|
||||||
|
t.Fatal("expected KindPress for press under reassigned PointerID; gesture dropped the press because of stale recorded pid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mouseClickEvents(times ...time.Duration) []event.Event {
|
func mouseClickEvents(times ...time.Duration) []event.Event {
|
||||||
press := pointer.Event{
|
press := pointer.Event{
|
||||||
Kind: pointer.Press,
|
Kind: pointer.Press,
|
||||||
|
|||||||
Reference in New Issue
Block a user