From 5bfc11a766bc7379d68e426646094d95b37e066d Mon Sep 17 00:00:00 2001 From: Daneel Date: Sun, 22 Feb 2026 15:37:48 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20comprehensive=20screen=20magnifier=20?= =?UTF-8?q?=E2=80=94=20text-scale=20+=20which-key=20fix=20+=20minibuffer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.el | 127 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 113 insertions(+), 14 deletions(-) diff --git a/config.el b/config.el index 1f09d52..3e7e1e5 100644 --- a/config.el +++ b/config.el @@ -701,11 +701,11 @@ Skips past the TODO keyword and optional priority indicator [#A]." ;; True screen magnifier: scales the global `default' face (all buffers, ;; help windows, doom menus, org-agenda, magit — everything). ;; -;; Modeline + minibuffer are PINNED to base size after every zoom so they -;; stay fully visible and usable regardless of zoom level. -;; which-key shows in the minibuffer area → also stays readable. +;; UI chrome (modeline, header-line) is PINNED to base size. +;; Intermediate layers (which-key, minibuffer/vertico) scale at a +;; reduced ratio so they remain readable at high zoom levels. ;; -;; Step: ×1.5 per step (multiplicative, not additive). From 14pt base: +;; Step: ×1.5 per step (multiplicative). From 14pt base: ;; +1 ≈ 21pt +2 ≈ 32pt +3 ≈ 47pt ;; +4 ≈ 71pt +5 ≈ 106pt +6 ≈ 159pt ;; @@ -714,10 +714,10 @@ Skips past the TODO keyword and optional priority indicator [#A]." ;; SPC z 0 reset to default (saves level for restore) ;; SPC z z restore zoom before last reset -;; Base height: captured once at startup (reflects doom-font :size). -;; Fallback to 140 = 14pt if face-attribute returns non-integer. +;; --------------- state variables --------------- + (defvar my/zoom-base-height 140 - "Default face height before any zoom. Captured at Doom init.") + "Default face height before any zoom. Captured at Doom init (1/10 pt).") (defvar my/zoom-steps 0 "Current zoom step count. 0 = default.") @@ -725,13 +725,23 @@ Skips past the TODO keyword and optional priority indicator [#A]." (defvar my/zoom-saved-steps nil "Step count saved before last `my/zoom-reset', for `my/zoom-restore'.") -;; Faces that must stay at base height (do not scale with text). -;; These form the fixed UI chrome: status bar, minibuffer, tab bar. +;; --------------- tuning knobs --------------- + +(defvar my/zoom-which-key-ratio 0.3 + "Which-key font height as fraction of current default height. +At 110pt default this gives ~33pt — readable but compact.") + +(defvar my/zoom-minibuffer-ratio 0.4 + "Minibuffer font height as fraction of current default height. +At 110pt default this gives ~44pt — readable for vertico candidates.") + +;; --------------- pinned UI faces (fixed at base) --------------- + (defvar my/zoom-pinned-faces '(mode-line mode-line-inactive mode-line-active - minibuffer-prompt header-line - tab-bar tab-bar-tab tab-bar-tab-inactive) - "Faces pinned to `my/zoom-base-height' after every zoom operation.") + header-line tab-bar tab-bar-tab tab-bar-tab-inactive) + "Faces pinned to `my/zoom-base-height' after every zoom operation. +Note: minibuffer-prompt is NOT here — it gets intermediate scaling.") (defun my/zoom-pin-ui () "Set pinned UI faces to base height so they don't scale with text." @@ -739,15 +749,100 @@ Skips past the TODO keyword and optional priority indicator [#A]." (when (facep face) (set-face-attribute face nil :height my/zoom-base-height)))) +;; --------------- which-key intermediate scaling --------------- + +(defvar my/zoom--which-key-cookie nil + "Face remap cookie for which-key buffer, so we can remove old remap.") + +(defun my/zoom--apply-which-key (new-height) + "Set which-key buffer face to intermediate size derived from NEW-HEIGHT." + (let* ((intermediate (max my/zoom-base-height + (round (* new-height my/zoom-which-key-ratio)))) + ;; Express as a ratio relative to the global default (new-height) + ;; because face-remap works relative to the buffer's inherited face. + (ratio (/ (float intermediate) new-height))) + (when-let ((buf (get-buffer " *which-key*"))) + (with-current-buffer buf + (when my/zoom--which-key-cookie + (face-remap-remove-relative my/zoom--which-key-cookie)) + (setq-local my/zoom--which-key-cookie + (face-remap-add-relative 'default :height ratio)))))) + +;; Hook: apply face remap whenever which-key creates/refreshes its buffer. +(defun my/zoom--which-key-buffer-setup () + "Apply intermediate font scaling when which-key buffer is initialized." + (when (and (string-prefix-p " *which-key*" (buffer-name)) + (/= my/zoom-steps 0)) + (let* ((current-h (face-attribute 'default :height nil t)) + (intermediate (max my/zoom-base-height + (round (* current-h my/zoom-which-key-ratio)))) + (ratio (/ (float intermediate) current-h))) + (setq-local my/zoom--which-key-cookie + (face-remap-add-relative 'default :height ratio))))) + +;; which-key-init-buffer-hook runs when the *which-key* buffer is set up +(add-hook 'which-key-init-buffer-hook #'my/zoom--which-key-buffer-setup) + +;; --------------- minibuffer intermediate scaling --------------- + +(defun my/zoom-update-minibuffer () + "Set minibuffer and echo area buffers to intermediate font size." + (let* ((current-h (face-attribute 'default :height nil t)) + (intermediate (max my/zoom-base-height + (round (* current-h my/zoom-minibuffer-ratio)))) + (ratio (/ (float intermediate) current-h))) + (dolist (buf-name '(" *Minibuf-0*" " *Minibuf-1*" + " *Echo Area 0*" " *Echo Area 1*")) + (when-let ((buf (get-buffer buf-name))) + (with-current-buffer buf + (setq-local face-remapping-alist + `((default (:height ,ratio) default)))))))) + +;; Hook: apply on every minibuffer entry (covers vertico, M-x, find-file). +(defun my/zoom--minibuffer-setup () + "Apply intermediate scaling to current minibuffer session." + (when (/= my/zoom-steps 0) + (let* ((current-h (face-attribute 'default :height nil t)) + (intermediate (max my/zoom-base-height + (round (* current-h my/zoom-minibuffer-ratio)))) + (ratio (/ (float intermediate) current-h))) + (setq-local face-remapping-alist + `((default (:height ,ratio) default)))))) + +(add-hook 'minibuffer-setup-hook #'my/zoom--minibuffer-setup) + +;; --------------- corfu popup refresh --------------- + +(defun my/zoom--invalidate-corfu () + "Hide corfu popup so it re-creates with new font on next completion." + (when (and (fboundp 'corfu-quit) (fboundp 'corfu--popup-hide)) + (ignore-errors (corfu--popup-hide)))) + +;; --------------- core zoom engine --------------- + (defun my/zoom--apply (steps) - "Set global default face to base × 1.5^STEPS and re-pin UI faces." + "Set global default face to base × 1.5^STEPS and update all UI layers." (let ((new-h (max 80 (round (* my/zoom-base-height (expt 1.5 steps)))))) + ;; 1. Global default face — affects all buffer content (set-face-attribute 'default nil :height new-h) + + ;; 2. Pin modeline/header-line at base size (my/zoom-pin-ui) + + ;; 3. Which-key — intermediate font + (my/zoom--apply-which-key new-h) + + ;; 4. Minibuffer/echo area — intermediate font + (my/zoom-update-minibuffer) + + ;; 5. Corfu — force re-create on next completion + (my/zoom--invalidate-corfu) + (message "Zoom %+d ×%.2f ≈%dpt" steps (expt 1.5 steps) (/ new-h 10)))) -;; Capture base height after Doom finishes font setup. +;; --------------- init: capture base height --------------- + (add-hook 'doom-after-init-hook (lambda () (let ((h (face-attribute 'default :height nil t))) @@ -757,6 +852,8 @@ Skips past the TODO keyword and optional priority indicator [#A]." ;; Re-pin UI faces after any theme reload (Doom resets faces on theme change). (add-hook 'doom-load-theme-hook #'my/zoom-pin-ui) +;; --------------- interactive commands --------------- + (defun my/zoom-in () "Zoom in one step (×1.5) — all buffers, help, menus, everything." (interactive) @@ -788,6 +885,8 @@ Skips past the TODO keyword and optional priority indicator [#A]." (setq my/zoom-steps my/zoom-saved-steps my/zoom-saved-steps nil))) +;; --------------- keybindings --------------- + (map! :leader (:prefix ("z" . "zoom") :desc "Zoom in (×1.5)" "+" #'my/zoom-in