Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Cursor hiding #11

Open
minad opened this issue Jan 10, 2025 · 5 comments · May be fixed by #15
Open

Feature request: Cursor hiding #11

minad opened this issue Jan 10, 2025 · 5 comments · May be fixed by #15

Comments

@minad
Copy link
Contributor

minad commented Jan 10, 2025

I find the jumping cursor at the window top or bottom slightly annoying when scrolling. Have you considered hiding the cursor (and also the hl-line overlay) as soon as the cursor hits the top/bottom of the window, until scrolling is finished?

@minad
Copy link
Contributor Author

minad commented Jan 10, 2025

;;; hide-cursor.el --- Hide cursor when scrolling -*- lexical-binding: t -*-
;;; Commentary:
;;; Code:

(defvar-local hide-cursor--timer nil)
(defvar-local hide-cursor--undo nil)

(defun hide-cursor--undo (buf)
  (when (buffer-live-p buf)
    (with-current-buffer buf
      (when-let ((win (get-buffer-window buf)))
        (with-selected-window win
          (goto-char (/ (+ (window-start) (window-end nil t)) 2))
          (beginning-of-line)))
      (mapc #'funcall hide-cursor--undo)
      (kill-local-variable 'hide-cursor--timer)
      (kill-local-variable 'hide-cursor--undo))))

(defun hide-cursor (&rest _)
  (unless hide-cursor--timer
    (setq hide-cursor--timer (timer-create))
    (timer-set-function hide-cursor--timer #'hide-cursor--undo (list (current-buffer))))
  (cancel-timer hide-cursor--timer)
  (timer-set-time hide-cursor--timer (timer-relative-time nil 0.3))
  (timer-activate hide-cursor--timer)
  (when (and (not hide-cursor--undo)
             (or (save-excursion (forward-line -1) (<= (point) (window-start)))
                 (save-excursion (forward-line 2) (>= (point) (window-end nil t)))))
    (when-let ((ov (bound-and-true-p hl-line-overlay)))
      (overlay-put ov 'face nil)
      (push 'hl-line-highlight hide-cursor--undo)
      (push 'hl-line-unhighlight hide-cursor--undo))
    (push (if (local-variable-p 'cursor-type)
              (let ((orig cursor-type))
                (lambda () (setq-local cursor-type orig)))
            (lambda () (kill-local-variable 'cursor-type)))
          hide-cursor--undo)
    (setq-local cursor-type nil)))

(defun hide-cursor--window (event)
  (with-selected-window (mwheel-event-window event)
    (hide-cursor)))

;;;###autoload
(defun hide-cursor-setup ()
  (advice-add #'pixel-scroll-precision-scroll-down-page :after #'hide-cursor)
  (advice-add #'pixel-scroll-precision-scroll-up-page :after #'hide-cursor)
  (advice-add 'ultra-scroll :after #'hide-cursor--window))

(provide 'hide-cursor)
;;; hide-cursor.el ends here

@jdtsmith
Copy link
Owner

I think that should be possible. After all the cursor position is just a detail (an important detail for image scrolling). I think I can do this with either touch-end or momentum :began events, which would leave NS out in the cold, since it gets neither.

I worry a bit about an idle timer not running and your cursor "going missing" after a scroll. I'm already using an idle timer to lift GC percentage, but that's not user-visible.

Thoughts?

minad added a commit to minad/ultra-scroll that referenced this issue Jan 10, 2025
@minad minad linked a pull request Jan 10, 2025 that will close this issue
@minad
Copy link
Contributor Author

minad commented Jan 10, 2025

I think that should be possible. After all the cursor position is just a detail (an important detail for image scrolling). I think I can do this with either touch-end or momentum :began events, which would leave NS out in the cold, since it gets neither.

I think using a timer is even nicer than immediately showing the cursor and hl-line again, since this leads to some debouncing effect. The cursor will only appear when you're really finished with scrolling. I quite like the effect. Please try it. The whole feature can also be disabled via a customization variable, which configures the delay.

I worry a bit about an idle timer not running and your cursor "going missing" after a scroll. I'm already using an idle timer to lift GC percentage, but that's not user-visible.

I use a normal timer so there is no risk that this will go missing. I have a check in place which make sure that the buffer is still alive. See my PR #15.

minad added a commit to minad/ultra-scroll that referenced this issue Jan 10, 2025
minad added a commit to minad/ultra-scroll that referenced this issue Jan 10, 2025
@minad
Copy link
Contributor Author

minad commented Jan 10, 2025

I wonder if we should even add the hide cursor functionality here. Maybe it is just better as a simple mode which could even be added upstream, augmenting pixel-scroll-precision-mode, given that the functionality is quite decoupled from the actual scrolling code.

;;; hide-cursor.el --- Hide cursor when scrolling -*- lexical-binding: t -*-

;; Copyright (C) 2025 Daniel Mendler

;; Author: Daniel Mendler <[email protected]>
;; Version: 0.1
;; Package-Requires: ((emacs "29.1"))
;; SPDX-License-Identifier: GPL-3.0-or-later
;; URL: https://github.com/minad

;;; Commentary:

;; Provides the small mode `hide-cursor-mode' which hides the cursor when
;; scrolling.  Can be used together with `pixel-scroll-precision-mode'.

;;; Code:

(defvar-local hide-cursor--timer nil)
(defvar-local hide-cursor--undo nil)
(defvar-local hide-cursor--start nil)

(defun hide-cursor--undo (buf)
  "Undo cursor hiding in BUF."
  (when (buffer-live-p buf)
    (with-current-buffer buf
      (when-let ((win (get-buffer-window buf)))
        (with-selected-window win
          ;; Check for tall image at point, do not recenter.
          (unless (get-char-property (point) 'display)
            (goto-char (/ (+ (window-start) (window-end nil t)) 2))
            (beginning-of-line))))
      (mapc #'funcall hide-cursor--undo)
      (kill-local-variable 'hide-cursor--start)
      (kill-local-variable 'hide-cursor--timer)
      (kill-local-variable 'hide-cursor--undo))))

(defun hide-cursor--adv (&rest _)
  "Hide cursor in after scrolling advice."
  (unless hide-cursor--timer
    (setq hide-cursor--start (point)
          hide-cursor--timer (timer-create))
    (timer-set-function hide-cursor--timer #'hide-cursor--undo (list (current-buffer))))
  (cancel-timer hide-cursor--timer)
  (timer-set-time hide-cursor--timer (timer-relative-time nil 0.3))
  (timer-activate hide-cursor--timer)
  (unless (or hide-cursor--undo (equal hide-cursor--start (point)))
    (when (bound-and-true-p hl-line-mode)
      (hl-line-mode -1)
      (push 'hl-line-mode hide-cursor--undo))
    (when cursor-type
      (push (if (local-variable-p 'cursor-type)
                (let ((orig cursor-type))
                  (lambda () (setq-local cursor-type orig)))
              (lambda () (kill-local-variable 'cursor-type)))
            hide-cursor--undo)
      (setq-local cursor-type nil))))

;;;###autoload
(define-minor-mode hide-cursor-mode
  "Hide cursor while scrolling."
  :global t :group 'scrolling
  (cond
   (hide-cursor-mode
    (advice-add #'pixel-scroll-precision-scroll-down-page :after #'hide-cursor--adv)
    (advice-add #'pixel-scroll-precision-scroll-up-page :after #'hide-cursor--adv))
   (t
    (advice-remove #'pixel-scroll-precision-scroll-down-page #'hide-cursor--adv)
    (advice-remove #'pixel-scroll-precision-scroll-up-page #'hide-cursor--adv))))

(provide 'hide-cursor)
;;; hide-cursor.el ends here

@jdtsmith
Copy link
Owner

jdtsmith commented Jan 10, 2025

I could see definitely the argument to keep this as a separate mode (which of course I'd be happy to link to). I hate long-lived advice, but after-advice is pretty darn safe. I don't know if the logic of "cache point, and if it moves, start hiding" will work for all (touch)scrolling packages, but it might!

Now that you've planted to seed, I think I may prefer it as a separate mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants