Update: made this into a more comprehensive scroll-on-drag package.
Here is a simple implementation of modal scrolling.
(defvar modal-scroll--timer nil)
(defvar modal-scroll--delay 0.01)
(defun modal-scroll--up-update (y-init)
(let ((delta (- (cdr (cdr (mouse-position))) y-init)))
(ignore-errors (scroll-up delta)))
(setq modal-scroll--timer
(run-with-timer modal-scroll--delay nil 'modal-scroll--up-update y-init)))
(defun modal-scroll--down-update (y-init)
(let ((delta (- y-init (cdr (cdr (mouse-position))))))
(ignore-errors (scroll-down delta)))
(setq modal-scroll--timer
(run-with-timer modal-scroll--delay nil 'modal-scroll--down-update y-init)))
(defun modal-scroll-stop--internal ()
(when modal-scroll--timer
(cancel-timer modal-scroll--timer)
(setq modal-scroll--timer nil)
)
)
(defun modal-scroll-drag () ""
(interactive)
(when modal-scroll--timer
(modal-scroll-stop--internal))
(let
(
(y-init (cdr (cdr (mouse-position))))
(delta-prev 0)
)
(track-mouse
(while
(progn (setq event (read-event))
(or
(mouse-movement-p event)
(memq (car-safe event) '(switch-frame select-window)))
)
(unless (memq (car-safe event) '(switch-frame select-window))
(let ((delta (- (cdr (cdr (mouse-position))) y-init)))
(cond
((> delta 0)
(unless (> delta delta-prev 0)
(modal-scroll-stop--internal)
(setq modal-scroll--timer
(run-with-timer 0.0 nil 'modal-scroll--up-update y-init))))
((< delta 0)
(unless (< delta-prev 0)
(modal-scroll-stop--internal)
(setq modal-scroll--timer
(run-with-timer 0.0 nil 'modal-scroll--down-update y-init))))
((eq delta 0)
(modal-scroll-stop--internal))
)
(setq delta-prev delta)
)
)
)
(modal-scroll-stop--internal)
)
)
)
(global-set-key [mouse-2] 'modal-scroll-drag)
Note that I didn't figure out how to make this co-exist with click events (so click events can be triggered as well as drag). See How to handle & differentiate both click & drag mouse button events?