From 34ac83b5942673c6ae48f4ed0abec96bc60d4f49 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Tue, 25 Oct 2022 23:31:50 +0200 Subject: [PATCH 1/4] Introduce mouse release and mouse drag events Introduce separate mouse release and mouse drag (move while pressed) events: MouseLeftRelease, MouseLeftDrag, MouseRightRelease etc, to allow binding them to actions independently from mouse press events (MouseLeft, MouseRight etc). This change: - Makes it possible to handle mouse release and drag for arbitrary mouse events and actions (including Lua actions), not just for MouseLeft as in the current code. - Fixes issue #2599 with PastePrimary and MouseMultiCursor actions: selection is pasted not only when pressing MouseMiddle but also when moving mouse with MouseMiddle pressed; similarly, a new multicursor is added not only when pressing Ctrl-MouseLeft but also when moving mouse with Ctrl-MouseLeft pressed. My initial approach was not to introduce new events for mouse release and mouse drag but to pass "mouse released" info to action functions in addition to *tcell.EventMouse to let the action functions do the necessary checks (similarly to what MousePress is already doing). But then I realized it was a bad idea, since we still want to be able also to bind mouse events to regular key actions (such as PastePrimary) which don't care about mouse event info. --- internal/action/actions.go | 117 +++++++++++++++++++---------- internal/action/bindings.go | 13 +++- internal/action/bufpane.go | 86 +++++++++------------ internal/action/defaults_darwin.go | 22 +++--- internal/action/defaults_other.go | 22 +++--- internal/action/events.go | 23 +++++- runtime/help/keybindings.md | 28 ++++--- 7 files changed, 190 insertions(+), 121 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index dd92a364..bd37decd 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -51,53 +51,47 @@ func (h *BufPane) ScrollAdjust() { func (h *BufPane) MousePress(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() + // ignore click on the status line + if my >= h.BufView().Y+h.BufView().Height { + return false + } mouseLoc := h.LocFromVisual(buffer.Loc{mx, my}) h.Cursor.Loc = mouseLoc - if h.mouseReleased { - if b.NumCursors() > 1 { - b.ClearCursors() - h.Relocate() - h.Cursor = h.Buf.GetActiveCursor() - h.Cursor.Loc = mouseLoc - } - if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) { - if h.doubleClick { - // Triple click - h.lastClickTime = time.Now() - h.tripleClick = true - h.doubleClick = false - - h.Cursor.SelectLine() - h.Cursor.CopySelection(clipboard.PrimaryReg) - } else { - // Double click - h.lastClickTime = time.Now() - - h.doubleClick = true - h.tripleClick = false - - h.Cursor.SelectWord() - h.Cursor.CopySelection(clipboard.PrimaryReg) - } - } else { - h.doubleClick = false - h.tripleClick = false + if b.NumCursors() > 1 { + b.ClearCursors() + h.Relocate() + h.Cursor = h.Buf.GetActiveCursor() + h.Cursor.Loc = mouseLoc + } + if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) { + if h.doubleClick { + // Triple click h.lastClickTime = time.Now() - h.Cursor.OrigSelection[0] = h.Cursor.Loc - h.Cursor.CurSelection[0] = h.Cursor.Loc - h.Cursor.CurSelection[1] = h.Cursor.Loc - } - h.mouseReleased = false - } else if !h.mouseReleased { - if h.tripleClick { - h.Cursor.AddLineToSelection() - } else if h.doubleClick { - h.Cursor.AddWordToSelection() + h.tripleClick = true + h.doubleClick = false + + h.Cursor.SelectLine() + h.Cursor.CopySelection(clipboard.PrimaryReg) } else { - h.Cursor.SetSelectionEnd(h.Cursor.Loc) + // Double click + h.lastClickTime = time.Now() + + h.doubleClick = true + h.tripleClick = false + + h.Cursor.SelectWord() + h.Cursor.CopySelection(clipboard.PrimaryReg) } + } else { + h.doubleClick = false + h.tripleClick = false + h.lastClickTime = time.Now() + + h.Cursor.OrigSelection[0] = h.Cursor.Loc + h.Cursor.CurSelection[0] = h.Cursor.Loc + h.Cursor.CurSelection[1] = h.Cursor.Loc } h.Cursor.StoreVisualX() @@ -106,6 +100,45 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { return true } +func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool { + mx, my := e.Position() + // ignore drag on the status line + if my >= h.BufView().Y+h.BufView().Height { + return false + } + h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my}) + + if h.tripleClick { + h.Cursor.AddLineToSelection() + } else if h.doubleClick { + h.Cursor.AddWordToSelection() + } else { + h.Cursor.SetSelectionEnd(h.Cursor.Loc) + } + + h.Cursor.StoreVisualX() + h.Relocate() + return true +} + +func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool { + // We could finish the selection based on the release location as in the + // commented out code below, to allow text selections even in a terminal + // that doesn't support mouse motion events. But when the mouse click is + // within the scroll margin, that would cause a scroll and selection + // even for a simple mouse click, which is not good. + // if !h.doubleClick && !h.tripleClick { + // mx, my := e.Position() + // h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my}) + // h.Cursor.SetSelectionEnd(h.Cursor.Loc) + // } + + if h.Cursor.HasSelection() { + h.Cursor.CopySelection(clipboard.PrimaryReg) + } + return true +} + // ScrollUpAction scrolls the view up func (h *BufPane) ScrollUpAction() bool { h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"])) @@ -1794,6 +1827,10 @@ func (h *BufPane) SpawnMultiCursorSelect() bool { func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() + // ignore click on the status line + if my >= h.BufView().Y+h.BufView().Height { + return false + } mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) c := buffer.NewCursor(b, mouseLoc) b.AddCursor(c) diff --git a/internal/action/bindings.go b/internal/action/bindings.go index 846e3d11..e2bf10a0 100644 --- a/internal/action/bindings.go +++ b/internal/action/bindings.go @@ -201,11 +201,20 @@ modSearch: }, true } + var mstate MouseState = MousePress + if strings.HasSuffix(k, "Drag") { + k = k[:len(k)-4] + mstate = MouseDrag + } else if strings.HasSuffix(k, "Release") { + k = k[:len(k)-7] + mstate = MouseRelease + } // See if we can find the key in bindingMouse if code, ok := mouseEvents[k]; ok { return MouseEvent{ - btn: code, - mod: modifiers, + btn: code, + mod: modifiers, + state: mstate, }, true } diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 847f5641..c6d527a1 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -8,7 +8,6 @@ import ( lua "github.com/yuin/gopher-lua" "github.com/zyedidia/micro/v2/internal/buffer" - "github.com/zyedidia/micro/v2/internal/clipboard" "github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/display" ulua "github.com/zyedidia/micro/v2/internal/lua" @@ -200,11 +199,15 @@ type BufPane struct { // Cursor is the currently active buffer cursor Cursor *buffer.Cursor - // Since tcell doesn't differentiate between a mouse release event - // and a mouse move event with no keys pressed, we need to keep - // track of whether or not the mouse was pressed (or not released) last event to determine - // mouse release events - mouseReleased bool + // Since tcell doesn't differentiate between a mouse press event + // and a mouse move event with button pressed (nor between a mouse + // release event and a mouse move event with no buttons pressed), + // we need to keep track of whether or not the mouse was previously + // pressed, to determine mouse release and mouse drag events. + // Moreover, since in case of a release event tcell doesn't tell us + // which button was released, we need to keep track of which + // (possibly multiple) buttons were pressed previously. + mousePressed map[MouseEvent]bool // We need to keep track of insert key press toggle isOverwriteMode bool @@ -250,7 +253,7 @@ func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { h.tab = tab h.Cursor = h.Buf.GetActiveCursor() - h.mouseReleased = true + h.mousePressed = make(map[MouseEvent]bool) return h } @@ -332,7 +335,9 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.initialRelocate() // Set mouseReleased to true because we assume the mouse is not being // pressed when the editor is opened - h.mouseReleased = true + for me := range h.mousePressed { + delete(h.mousePressed, me) + } // Set isOverwriteMode to false, because we assume we are in the default // mode when editor is opened h.isOverwriteMode = false @@ -432,50 +437,31 @@ func (h *BufPane) HandleEvent(event tcell.Event) { h.DoRuneInsert(e.Rune()) } case *tcell.EventMouse: - cancel := false - switch e.Buttons() { - case tcell.Button1: - _, my := e.Position() - if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 { - cancel = true - } - case tcell.ButtonNone: - // Mouse event with no click - if !h.mouseReleased { - // Mouse was just released - - // mx, my := e.Position() - // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) - - // we could finish the selection based on the release location as described - // below but when the mouse click is within the scroll margin this will - // cause a scroll and selection even for a simple mouse click which is - // not good - // for terminals that don't support mouse motion events, selection via - // the mouse won't work but this is ok - - // Relocating here isn't really necessary because the cursor will - // be in the right place from the last mouse event - // However, if we are running in a terminal that doesn't support mouse motion - // events, this still allows the user to make selections, except only after they - // release the mouse - - // if !h.doubleClick && !h.tripleClick { - // h.Cursor.SetSelectionEnd(h.Cursor.Loc) - // } - if h.Cursor.HasSelection() { - h.Cursor.CopySelection(clipboard.PrimaryReg) - } - h.mouseReleased = true - } - } - - if !cancel { + if e.Buttons() != tcell.ButtonNone { me := MouseEvent{ - btn: e.Buttons(), - mod: metaToAlt(e.Modifiers()), + btn: e.Buttons(), + mod: metaToAlt(e.Modifiers()), + state: MousePress, } + if len(h.mousePressed) > 0 { + me.state = MouseDrag + } + + if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone { + h.mousePressed[me] = true + } + h.DoMouseEvent(me, e) + } else { + // Mouse event with no click - mouse was just released. + // If there were multiple mouse buttons pressed, we don't know which one + // was actually released, so we assume they all were released. + for me := range h.mousePressed { + delete(h.mousePressed, me) + + me.state = MouseRelease + h.DoMouseEvent(me, e) + } } } h.Buf.MergeCursors() @@ -788,6 +774,8 @@ var BufKeyActions = map[string]BufKeyAction{ // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute var BufMouseActions = map[string]BufMouseAction{ "MousePress": (*BufPane).MousePress, + "MouseDrag": (*BufPane).MouseDrag, + "MouseRelease": (*BufPane).MouseRelease, "MouseMultiCursor": (*BufPane).MouseMultiCursor, } diff --git a/internal/action/defaults_darwin.go b/internal/action/defaults_darwin.go index bae69910..558ca888 100644 --- a/internal/action/defaults_darwin.go +++ b/internal/action/defaults_darwin.go @@ -90,11 +90,13 @@ var bufdefaults = map[string]string{ "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", // Mouse bindings - "MouseWheelUp": "ScrollUp", - "MouseWheelDown": "ScrollDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", - "Ctrl-MouseLeft": "MouseMultiCursor", + "MouseWheelUp": "ScrollUp", + "MouseWheelDown": "ScrollDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", + "Ctrl-MouseLeft": "MouseMultiCursor", "Alt-n": "SpawnMultiCursor", "AltShiftUp": "SpawnMultiCursorUp", @@ -173,8 +175,10 @@ var infodefaults = map[string]string{ "Esc": "AbortCommand", // Mouse bindings - "MouseWheelUp": "HistoryUp", - "MouseWheelDown": "HistoryDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", + "MouseWheelUp": "HistoryUp", + "MouseWheelDown": "HistoryDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", } diff --git a/internal/action/defaults_other.go b/internal/action/defaults_other.go index 39a9e0e3..d6ce27ea 100644 --- a/internal/action/defaults_other.go +++ b/internal/action/defaults_other.go @@ -92,11 +92,13 @@ var bufdefaults = map[string]string{ "Esc": "Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch", // Mouse bindings - "MouseWheelUp": "ScrollUp", - "MouseWheelDown": "ScrollDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", - "Ctrl-MouseLeft": "MouseMultiCursor", + "MouseWheelUp": "ScrollUp", + "MouseWheelDown": "ScrollDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", + "Ctrl-MouseLeft": "MouseMultiCursor", "Alt-n": "SpawnMultiCursor", "Alt-m": "SpawnMultiCursorSelect", @@ -175,8 +177,10 @@ var infodefaults = map[string]string{ "Esc": "AbortCommand", // Mouse bindings - "MouseWheelUp": "HistoryUp", - "MouseWheelDown": "HistoryDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", + "MouseWheelUp": "HistoryUp", + "MouseWheelDown": "HistoryDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", } diff --git a/internal/action/events.go b/internal/action/events.go index 3a4834f6..4addf1b5 100644 --- a/internal/action/events.go +++ b/internal/action/events.go @@ -100,11 +100,20 @@ func (k KeySequenceEvent) Name() string { return buf.String() } +type MouseState int + +const ( + MousePress = iota + MouseDrag + MouseRelease +) + // MouseEvent is a mouse event with a mouse button and // any possible key modifiers type MouseEvent struct { - btn tcell.ButtonMask - mod tcell.ModMask + btn tcell.ButtonMask + mod tcell.ModMask + state MouseState } func (m MouseEvent) Name() string { @@ -122,9 +131,17 @@ func (m MouseEvent) Name() string { mod = "Ctrl-" } + state := "" + switch m.state { + case MouseDrag: + state = "Drag" + case MouseRelease: + state = "Release" + } + for k, v := range mouseEvents { if v == m.btn { - return fmt.Sprintf("%s%s", mod, k) + return fmt.Sprintf("%s%s%s", mod, k, state) } } return "" diff --git a/runtime/help/keybindings.md b/runtime/help/keybindings.md index 8d14da7d..753e6fdf 100644 --- a/runtime/help/keybindings.md +++ b/runtime/help/keybindings.md @@ -407,8 +407,14 @@ mouse actions) ``` MouseLeft +MouseLeftDrag +MouseLeftRelease MouseMiddle +MouseMiddleDrag +MouseMiddleRelease MouseRight +MouseRightDrag +MouseRightRelease MouseWheelUp MouseWheelDown MouseWheelLeft @@ -520,11 +526,13 @@ conventions for text editing defaults. "Esc": "Escape", // Mouse bindings - "MouseWheelUp": "ScrollUp", - "MouseWheelDown": "ScrollDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary", - "Ctrl-MouseLeft": "MouseMultiCursor", + "MouseWheelUp": "ScrollUp", + "MouseWheelDown": "ScrollDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary", + "Ctrl-MouseLeft": "MouseMultiCursor", "Alt-n": "SpawnMultiCursor", "AltShiftUp": "SpawnMultiCursorUp", @@ -629,10 +637,12 @@ are given below: "Esc": "AbortCommand", // Mouse bindings - "MouseWheelUp": "HistoryUp", - "MouseWheelDown": "HistoryDown", - "MouseLeft": "MousePress", - "MouseMiddle": "PastePrimary" + "MouseWheelUp": "HistoryUp", + "MouseWheelDown": "HistoryDown", + "MouseLeft": "MousePress", + "MouseLeftDrag": "MouseDrag", + "MouseLeftRelease": "MouseRelease", + "MouseMiddle": "PastePrimary" } } ``` From 124fa9e2e7b9c7501a32cb528f28a52354de3840 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Thu, 3 Nov 2022 00:14:26 +0100 Subject: [PATCH 2/4] Fix up double release event after drag If we press mouse, drag and then release, the release event is generated twice, since both mouse press and mouse drag events have been saved in mousePressed map. To fix that, ensure that we only store mouse press events in it. --- internal/action/bufpane.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index c6d527a1..f40efa7e 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -443,14 +443,15 @@ func (h *BufPane) HandleEvent(event tcell.Event) { mod: metaToAlt(e.Modifiers()), state: MousePress, } - if len(h.mousePressed) > 0 { - me.state = MouseDrag - } + isDrag := len(h.mousePressed) > 0 if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone { h.mousePressed[me] = true } + if isDrag { + me.state = MouseDrag + } h.DoMouseEvent(me, e) } else { // Mouse event with no click - mouse was just released. From e5093892fd92c0721dad1e7f0c8d135f1f09e0b6 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Sat, 5 Nov 2022 11:41:04 +0100 Subject: [PATCH 3/4] Reset mouse release state after restarting the screen When we temporarily disable the screen (e.g. during TermMessage or RunInteractiveShell), if the mouse is pressed when the screen is still active and then released when the screen is already stopped, we aren't able to catch this mouse release event, so we erroneously think that the mouse is still pressed after the screen is restarted. This results in wrong behavior due to a mouse press event treated as a mouse move event, e.g. upon the left button click we see an unexpected text selection. So need to reset the mouse release state to "released" after restarting the screen, assuming it is always released when the screen is restarted. --- internal/action/bufpane.go | 10 +++++++--- internal/action/tab.go | 15 +++++++++++++++ internal/screen/screen.go | 8 ++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index f40efa7e..9cb3808b 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -325,6 +325,12 @@ func (h *BufPane) PluginCBRune(cb string, r rune) bool { return b } +func (h *BufPane) resetMouse() { + for me := range h.mousePressed { + delete(h.mousePressed, me) + } +} + // OpenBuffer opens the given buffer in this pane. func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.Buf.Close() @@ -335,9 +341,7 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.initialRelocate() // Set mouseReleased to true because we assume the mouse is not being // pressed when the editor is opened - for me := range h.mousePressed { - delete(h.mousePressed, me) - } + h.resetMouse() // Set isOverwriteMode to false, because we assume we are in the default // mode when editor is opened h.isOverwriteMode = false diff --git a/internal/action/tab.go b/internal/action/tab.go index 07a866ce..0967df3a 100644 --- a/internal/action/tab.go +++ b/internal/action/tab.go @@ -161,6 +161,21 @@ func InitTabs(bufs []*buffer.Buffer) { } } } + + screen.RestartCallback = func() { + // The mouse could be released after the screen was stopped, so that + // we couldn't catch the mouse release event and would erroneously think + // that it is still pressed. So need to reset the mouse release state + // after the screen is restarted. + for _, t := range Tabs.List { + t.release = true + for _, p := range t.Panes { + if bp, ok := p.(*BufPane); ok { + bp.resetMouse() + } + } + } + } } func MainTab() *Tab { diff --git a/internal/screen/screen.go b/internal/screen/screen.go index 339e69aa..16c011e6 100644 --- a/internal/screen/screen.go +++ b/internal/screen/screen.go @@ -22,6 +22,10 @@ var Screen tcell.Screen // Events is the channel of tcell events var Events chan (tcell.Event) +// RestartCallback is called when the screen is restarted after it was +// temporarily shut down +var RestartCallback func() + // The lock is necessary since the screen is polled on a separate thread var lock sync.Mutex @@ -134,6 +138,10 @@ func TempStart(screenWasNil bool) { if !screenWasNil { Init() Unlock() + + if RestartCallback != nil { + RestartCallback() + } } } From 2d95064ff61e19941c516d3d819c09e57c5076f0 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Thu, 1 Dec 2022 22:53:38 +0100 Subject: [PATCH 4/4] Make a pane active whenever any mouse button is pressed on it Since now bufpane handles mouse move and release events generically and separately from mouse press events, that creates a mess when we dispatch a mouse press event to an inactive pane without making it active. For example: 1. Click the right button on an inactive pane. It remains inactive. 2. Then click the left button on it. It becomes active, and an unexpected text selection appears. The reason is that the release event for the first click was dispatched to a wrong pane - the (then) active one, so the (then) inactive pane didn't get the release event and treats the second click not as a mouse press but as a mouse move. The simplest way to fix it is to avoid this scenario entirely, i.e. always activate the pane when clicking any mouse button on it, not just the left button. For mouse wheel motion events we keep the existing behavior: the pane gets the event but doesn't become active. Mouse wheel motion events are not affected by the described issue, as they have no paired "release" events. --- internal/action/tab.go | 51 +++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/internal/action/tab.go b/internal/action/tab.go index 0967df3a..4227547f 100644 --- a/internal/action/tab.go +++ b/internal/action/tab.go @@ -226,34 +226,40 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab { // HandleEvent takes a tcell event and usually dispatches it to the current // active pane. However if the event is a resize or a mouse event where the user // is interacting with the UI (resizing splits) then the event is consumed here -// If the event is a mouse event in a pane, that pane will become active and get -// the event +// If the event is a mouse press event in a pane, that pane will become active +// and get the event func (t *Tab) HandleEvent(event tcell.Event) { switch e := event.(type) { case *tcell.EventMouse: mx, my := e.Position() - switch e.Buttons() { - case tcell.Button1: + btn := e.Buttons() + switch { + case btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone: + // button press or drag wasReleased := t.release t.release = false - if t.resizing != nil { - var size int - if t.resizing.Kind == views.STVert { - size = mx - t.resizing.X - } else { - size = my - t.resizing.Y + 1 + + if btn == tcell.Button1 { + if t.resizing != nil { + var size int + if t.resizing.Kind == views.STVert { + size = mx - t.resizing.X + } else { + size = my - t.resizing.Y + 1 + } + t.resizing.ResizeSplit(size) + t.Resize() + return + } + if wasReleased { + t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my}) + if t.resizing != nil { + return + } } - t.resizing.ResizeSplit(size) - t.Resize() - return } if wasReleased { - t.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my}) - if t.resizing != nil { - return - } - for i, p := range t.Panes { v := p.GetView() inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height @@ -263,10 +269,15 @@ func (t *Tab) HandleEvent(event tcell.Event) { } } } - case tcell.ButtonNone: - t.resizing = nil + case btn == tcell.ButtonNone: + // button release t.release = true + if t.resizing != nil { + t.resizing = nil + return + } default: + // wheel move for _, p := range t.Panes { v := p.GetView() inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height