diff --git a/internal/action/actions.go b/internal/action/actions.go index 491199f76f..b2b05ef897 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -57,6 +57,32 @@ func (h *BufPane) ScrollReachedEnd() bool { // MousePress is the event that should happen when a normal click happens // This is almost always bound to left click func (h *BufPane) MousePress(e *tcell.EventMouse) bool { + // Scrollbar hit-test + // Intercept clicks on the scrollbar before any buffer/cursor handling. + if bw, ok := h.BWindow.(*display.BufWindow); ok { + mx, my := e.Position() + sbX := bw.ScrollBarX() + if sbX >= 0 && mx == sbX { + trackStart, trackEnd := bw.ScrollBarTrackBounds() + if my >= trackStart && my <= trackEnd { + thumbStart, thumbEnd := bw.ScrollBarBounds() + if my >= thumbStart && my <= thumbEnd { + // Click lands on the thumb: start a drag. + bw.BeginScrollDrag(my, thumbStart) + } else { + // Click on the track outside the thumb: page scroll. + if my < thumbStart { + h.ScrollUp(h.BufView().Height) + } else { + h.ScrollDown(h.BufView().Height) + h.ScrollAdjust() + } + } + return true + } + } + } + b := h.Buf mx, my := e.Position() // ignore click on the status line @@ -109,6 +135,18 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { } func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool { + if bw, ok := h.BWindow.(*display.BufWindow); ok && bw.IsScrollDragging() { + _, my := e.Position() + // Subtract the intra-thumb offset + effectiveY := my - bw.ScrollDragOffset() + targetLine := bw.ScrollPosFromMouse(effectiveY) + v := h.GetView() + v.StartLine = display.SLoc{targetLine, 0} + h.SetView(v) + h.ScrollAdjust() + return true + } + mx, my := e.Position() // ignore drag on the status line if my >= h.BufView().Y+h.BufView().Height { @@ -130,6 +168,11 @@ func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool { } func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool { + if bw, ok := h.BWindow.(*display.BufWindow); ok && bw.IsScrollDragging() { + bw.EndScrollDrag() + return true + } + // 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 diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index ddbb044c7b..68c9b63d4d 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -29,6 +29,10 @@ type BufWindow struct { hasMessage bool maxLineNumLength int drawDivider bool + + scrollDragging bool + // Y delta between the mouse click position and the top of the thumb. + scrollDragOffset int } // NewBufWindow creates a new window at a location in the screen with a width and height @@ -893,6 +897,72 @@ func (w *BufWindow) displayScrollBar() { } } +func (w *BufWindow) ScrollBarX() int { + if !w.Buf.Settings["scrollbar"].(bool) || w.Buf.LinesNum() <= w.Height { + return -1 + } + return w.X + w.Width - 1 +} + +// returns the (inclusive) screen Y range [thumbStart, thumbEnd] +// of the scrollbar thumb, or (-1,-1) when the scrollbar is not visible. +func (w *BufWindow) ScrollBarBounds() (int, int) { + if !w.Buf.Settings["scrollbar"].(bool) || w.Buf.LinesNum() <= w.Height { + return -1, -1 + } + barsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height)) + if barsize < 1 { + barsize = 1 + } + barstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height)) + barend := util.Min(barstart+barsize, w.Y+w.bufHeight) - 1 + return barstart, barend +} + +// returns the screen Y range [start, end] of the full +// scrollbar track column (excluding the status line). +func (w *BufWindow) ScrollBarTrackBounds() (int, int) { + if !w.Buf.Settings["scrollbar"].(bool) || w.Buf.LinesNum() <= w.Height { + return -1, -1 + } + return w.Y, w.Y + w.bufHeight - 1 +} + +func (w *BufWindow) ScrollPosFromMouse(screenY int) int { + trackStart, trackEnd := w.ScrollBarTrackBounds() + trackH := trackEnd - trackStart + 1 + if trackH <= 0 { + return 0 + } + ratio := float64(screenY-trackStart) / float64(trackH) + line := int(ratio * float64(w.Buf.LinesNum())) + if line < 0 { + line = 0 + } + if line >= w.Buf.LinesNum() { + line = w.Buf.LinesNum() - 1 + } + return line +} + +func (w *BufWindow) BeginScrollDrag(dragY, thumbStart int) { + w.scrollDragging = true + w.scrollDragOffset = dragY - thumbStart +} + +func (w *BufWindow) EndScrollDrag() { + w.scrollDragging = false + w.scrollDragOffset = 0 +} + +func (w *BufWindow) IsScrollDragging() bool { + return w.scrollDragging +} + +func (w *BufWindow) ScrollDragOffset() int { + return w.scrollDragOffset +} + // Display displays the buffer and the statusline func (w *BufWindow) Display() { w.updateDisplayInfo()