Preserve the first mouse movement of a drag

When the left button is pressed and the pointer then moves, the event
that made the MAYBE_DRAGGING -> DRAGGING transition fell through the
switch without being assigned a key or modifier, so the first cell of
every drag arrived at handlers as a MouseRelease event without the
motion modifier and was effectively lost. Give it the same
MouseLeft/ModMotion identity as all subsequent drag events.

Held-button motion events that stay within the pressed cell carry no
information at all; swallow them instead of letting them through as
further release-shaped events (which used to clobber the double-click
state when the pointer jittered within a cell between two clicks).
This commit is contained in:
Stefan Haller
2026-07-31 08:22:35 +02:00
parent c5fe27dfa5
commit ff53a3ed8c
2 changed files with 51 additions and 2 deletions
+18 -2
View File
@@ -366,7 +366,9 @@ func gocuiEventFromTcellEvent(tev tcell.Event) GocuiEvent {
// process button events (not wheel events)
button &= tcell.ButtonMask(0xff)
newButtonPress := false
if button != tcell.ButtonNone && lastMouseKey == tcell.ButtonNone {
newButtonPress = true
lastMouseKey = button
lastMouseMod = tev.Modifiers()
switch button {
@@ -410,9 +412,23 @@ func gocuiEventFromTcellEvent(tev tcell.Event) GocuiEvent {
}
// if we haven't released the left mouse button and we've moved the cursor then we're dragging
case MAYBE_DRAGGING:
if x != lastX || y != lastY {
dragState = DRAGGING
if x == lastX && y == lastY {
// Deliver the button press itself, but swallow held-button
// motion events within the same cell: they carry no new
// information, and if they fell through they would be
// delivered with the default MouseRelease key.
if !newButtonPress {
return GocuiEvent{Type: eventNone}
}
break
}
// The first movement is already part of the drag; give it the
// same key and modifier as the DRAGGING events below so it
// reaches drag bindings instead of being delivered with the
// default MouseRelease key.
dragState = DRAGGING
mouseMod = ModMotion
mouseKey = MouseLeft
case DRAGGING:
mouseMod = ModMotion
mouseKey = MouseLeft
+33
View File
@@ -0,0 +1,33 @@
package gocui
import (
"testing"
"github.com/gdamore/tcell/v3"
"github.com/stretchr/testify/assert"
)
func TestFirstMouseMovementAfterPressIsDragEvent(t *testing.T) {
t.Cleanup(resetMouseState)
resetMouseState()
pressEvent := gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 2, tcell.ButtonPrimary, tcell.ModNone))
unchangedHeldEvent := gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 2, tcell.ButtonPrimary, tcell.ModNone))
dragEvent := gocuiEventFromTcellEvent(tcell.NewEventMouse(1, 3, tcell.ButtonPrimary, tcell.ModNone))
assert.Equal(t, eventMouse, pressEvent.Type)
assert.Equal(t, MouseLeft, pressEvent.Key.KeyName())
assert.Equal(t, ModNone, pressEvent.Key.Mod())
assert.Equal(t, eventNone, unchangedHeldEvent.Type)
assert.Equal(t, eventMouse, dragEvent.Type)
assert.Equal(t, MouseLeft, dragEvent.Key.KeyName())
assert.Equal(t, ModMotion, dragEvent.Key.Mod())
}
func resetMouseState() {
lastMouseKey = tcell.ButtonNone
lastMouseMod = tcell.ModNone
dragState = NOT_DRAGGING
lastX = 0
lastY = 0
}