diff --git a/src/tui/tcell.go b/src/tui/tcell.go index dd0581b..054c550 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -185,14 +185,48 @@ func (r *FullscreenRenderer) GetChar() Event { // process mouse events: case *tcell.EventMouse: + // mouse down events have zeroed buttons, so we can't use them + // mouse up event consists of two events, 1. (main) event with modifier and other metadata, 2. event with zeroed buttons + // so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons) + // dragging has same structure, it only repeats the middle (main) event appropriately x, y := ev.Position() - button := ev.Buttons() mod := ev.Modifiers() != 0 - if button&tcell.WheelDown != 0 { + + // since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton + prevButton, button := r.prevMouseButton, ev.Buttons() + r.prevMouseButton = button + drag := prevButton == button + + switch { + case button&tcell.WheelDown != 0: return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}} - } else if button&tcell.WheelUp != 0 { + case button&tcell.WheelUp != 0: return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}} - } else if runtime.GOOS != "windows" { + case button&tcell.Button1 != 0 && !drag: + // all potential double click events put their 'line' coordinate in the clickY array + // double click event has two conditions, temporal and spatial, the first is checked here + now := time.Now() + if now.Sub(r.prevDownTime) < doubleClickDuration { + r.clickY = append(r.clickY, y) + } else { + r.clickY = []int{y} + } + r.prevDownTime = now + + // detect double clicks (also check for spatial condition) + n := len(r.clickY) + double := n > 1 && r.clickY[n-2] == r.clickY[n-1] + if double { + // make sure two consecutive double clicks require four clicks + r.clickY = []int{} + } + + // fire single or double click event + return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}} + case button&tcell.Button2 != 0 && !drag: + return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}} + case runtime.GOOS != "windows": + // double and single taps on Windows don't quite work due to // the console acting on the events and not allowing us // to consume them. diff --git a/src/tui/tui.go b/src/tui/tui.go index eb09da4..12359c6 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -5,6 +5,8 @@ import ( "os" "strconv" "time" + + "github.com/gdamore/tcell" ) // Types of user action @@ -397,20 +399,22 @@ type Window interface { } type FullscreenRenderer struct { - theme *ColorTheme - mouse bool - forceBlack bool - prevDownTime time.Time - clickY []int + theme *ColorTheme + mouse bool + forceBlack bool + prevDownTime time.Time + prevMouseButton tcell.ButtonMask + clickY []int } func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer { r := &FullscreenRenderer{ - theme: theme, - mouse: mouse, - forceBlack: forceBlack, - prevDownTime: time.Unix(0, 0), - clickY: []int{}} + theme: theme, + mouse: mouse, + forceBlack: forceBlack, + prevDownTime: time.Unix(0, 0), + prevMouseButton: tcell.ButtonNone, + clickY: []int{}} return r }