diff options
| -rw-r--r-- | modules/cj-window-geometry-lib.el | 17 | ||||
| -rw-r--r-- | modules/cj-window-toggle-lib.el | 45 | ||||
| -rw-r--r-- | modules/music-config.el | 20 | ||||
| -rw-r--r-- | tests/test-cj-window-geometry-lib.el | 29 | ||||
| -rw-r--r-- | tests/test-cj-window-toggle-lib.el | 71 |
5 files changed, 180 insertions, 2 deletions
diff --git a/modules/cj-window-geometry-lib.el b/modules/cj-window-geometry-lib.el index 1c1ab9fd..cc638f76 100644 --- a/modules/cj-window-geometry-lib.el +++ b/modules/cj-window-geometry-lib.el @@ -112,5 +112,22 @@ a fresh half." (not (window-in-direction (nth 1 perp) w)))) (window-list (or frame (selected-frame)) 'never))))) +(defun cj/window-size-fraction (window-size frame-size &optional min-frac max-frac) + "Return WINDOW-SIZE as a fraction of FRAME-SIZE, clamped to [MIN-FRAC, MAX-FRAC]. + +WINDOW-SIZE and FRAME-SIZE are line or column counts. MIN-FRAC and +MAX-FRAC default to 0.05 and 0.95: a side window pinned to either extreme +is almost certainly a mistake, and a 0.0 fraction makes +`display-buffer-in-side-window' unusable. Returns nil when FRAME-SIZE is +not a positive number, or WINDOW-SIZE is not a number, so a caller can +fall back to its own default instead of dividing by zero. + +This is the kernel for remembering a side window's user-resized size: capture +the fraction at toggle-off, replay it on the next toggle-on." + (when (and (numberp window-size) (numberp frame-size) (> frame-size 0)) + (let ((lo (or min-frac 0.05)) + (hi (or max-frac 0.95))) + (max lo (min hi (/ (float window-size) frame-size)))))) + (provide 'cj-window-geometry-lib) ;;; cj-window-geometry-lib.el ends here diff --git a/modules/cj-window-toggle-lib.el b/modules/cj-window-toggle-lib.el index 6021c2eb..ef57e5cf 100644 --- a/modules/cj-window-toggle-lib.el +++ b/modules/cj-window-toggle-lib.el @@ -81,5 +81,50 @@ placement; the remaining alist entries are passed through." filtered))) (display-buffer-in-direction buffer effective))) +;; --------------------------- side-window helpers --------------------------- +;; +;; A second, simpler pattern for `display-buffer-in-side-window' consumers. +;; Side windows are atomic (they can't be split), so there's no direction to +;; capture -- only a size on the side's axis. These helpers remember that +;; size across toggles: capture the user's mouse-resized size at toggle-off, +;; replay it on the next toggle-on. The remembered value is held in the +;; consumer's own state variable (passed by symbol) and is in-memory only. + +(defun cj/side-window-capture-size (window side size-var) + "Write WINDOW's size on SIDE's axis, as a frame fraction, into SIZE-VAR. + +SIDE is a `display-buffer-in-side-window' side symbol: left or right +capture width, top or bottom capture height. SIZE-VAR is the symbol of the +consumer's stored-fraction variable; it receives the captured value via +`set'. Capturing at toggle-off and replaying on the next toggle-on is what +makes a manual mouse-resize stick for the rest of the session. + +No-op when WINDOW is nil or not live, or when the fraction can't be computed +\(see `cj/window-size-fraction') -- SIZE-VAR keeps its prior value so the +caller's default still applies." + (when (window-live-p window) + (let* ((horizontal (memq side '(left right))) + (frame (window-frame window)) + (win-size (if horizontal + (window-total-width window) + (window-total-height window))) + (frame-size (if horizontal (frame-width frame) (frame-height frame))) + (frac (cj/window-size-fraction win-size frame-size))) + (when frac (set size-var frac))))) + +(defun cj/side-window-display (buffer side size-var default-size) + "Display BUFFER in a SIDE side window at the remembered or DEFAULT-SIZE size. + +SIDE is a `display-buffer-in-side-window' side symbol. SIZE-VAR is the +symbol of the consumer's stored-fraction variable; its value is used when +bound and non-nil, otherwise DEFAULT-SIZE. The size lands under +`window-width' for a left/right side and `window-height' for top/bottom. +Returns the displayed window (or nil if display fails)." + (let* ((stored (and (boundp size-var) (symbol-value size-var))) + (size (or stored default-size)) + (size-key (if (memq side '(left right)) 'window-width 'window-height))) + (display-buffer-in-side-window + buffer (list (cons 'side side) (cons size-key size))))) + (provide 'cj-window-toggle-lib) ;;; cj-window-toggle-lib.el ends here diff --git a/modules/music-config.el b/modules/music-config.el index 197c73be..fd619d8c 100644 --- a/modules/music-config.el +++ b/modules/music-config.el @@ -94,6 +94,7 @@ (require 'subr-x) (require 'user-constants) (require 'keybindings) ;; provides cj/custom-keymap +(require 'cj-window-toggle-lib) ;; side-window size memory (F10 toggle) ;;; Settings (no Customize) @@ -513,20 +514,35 @@ Intended for use on `emms-player-finished-hook'." ) +(defvar cj/music-playlist-window-height 0.3 + "Default fraction of frame height for the F10 music playlist side window. +Used until the playlist is resized and toggled off this session; after that, +the toggled-off height is remembered in `cj/--music-playlist-height'.") + +(defvar cj/--music-playlist-height nil + "Last height fraction the playlist side window was toggled off at. +nil means fall back to `cj/music-playlist-window-height'. In-memory only -- +resets each Emacs session.") + (defun cj/music-playlist-toggle () - "Toggle the EMMS playlist buffer in a bottom side window." + "Toggle the EMMS playlist buffer in a bottom side window. +The window opens at `cj/music-playlist-window-height'; if it has been +resized and toggled off this session, it reopens at that remembered height." (interactive) (let* ((buf-name cj/music-playlist-buffer-name) (buffer (get-buffer buf-name)) (win (and buffer (get-buffer-window buffer)))) (if win (progn + (cj/side-window-capture-size win 'bottom 'cj/--music-playlist-height) (delete-window win) (message "Playlist window closed")) (progn (cj/emms--setup) (setq buffer (cj/music--ensure-playlist-buffer)) - (setq win (display-buffer-in-side-window buffer '((side . bottom) (window-height . 0.5)))) + (setq win (cj/side-window-display + buffer 'bottom 'cj/--music-playlist-height + cj/music-playlist-window-height)) (select-window win) (with-current-buffer buffer (if (and (fboundp 'emms-playlist-current-selected-track) diff --git a/tests/test-cj-window-geometry-lib.el b/tests/test-cj-window-geometry-lib.el index 2b417425..05ed9595 100644 --- a/tests/test-cj-window-geometry-lib.el +++ b/tests/test-cj-window-geometry-lib.el @@ -168,5 +168,34 @@ window forms the full-height right half -> nil." (should (null (cj/window-at-edge 'sideways))) (should (null (cj/window-at-edge nil))))) +;; ----------------------------- window-size-fraction ----------------------------- + +(ert-deftest test-cj-window-geometry-size-fraction-normal () + "Normal: a window half the frame returns 0.5." + (should (= (cj/window-size-fraction 20 40) 0.5))) + +(ert-deftest test-cj-window-geometry-size-fraction-clamps-high () + "Boundary: a near-full window is clamped to the 0.95 ceiling." + (should (= (cj/window-size-fraction 40 40) 0.95))) + +(ert-deftest test-cj-window-geometry-size-fraction-clamps-low () + "Boundary: a vanishingly small window is clamped to the 0.05 floor." + (should (= (cj/window-size-fraction 0 40) 0.05))) + +(ert-deftest test-cj-window-geometry-size-fraction-custom-bounds () + "Boundary: explicit MIN-FRAC/MAX-FRAC override the defaults." + (should (= (cj/window-size-fraction 1 40 0.1 0.9) 0.1)) + (should (= (cj/window-size-fraction 39 40 0.1 0.9) 0.9))) + +(ert-deftest test-cj-window-geometry-size-fraction-zero-frame-nil () + "Error: a non-positive frame size returns nil (no divide-by-zero)." + (should (null (cj/window-size-fraction 20 0))) + (should (null (cj/window-size-fraction 20 -5)))) + +(ert-deftest test-cj-window-geometry-size-fraction-non-number-nil () + "Error: non-numeric arguments return nil." + (should (null (cj/window-size-fraction nil 40))) + (should (null (cj/window-size-fraction 20 nil)))) + (provide 'test-cj-window-geometry-lib) ;;; test-cj-window-geometry-lib.el ends here diff --git a/tests/test-cj-window-toggle-lib.el b/tests/test-cj-window-toggle-lib.el index 2c4ea831..ca4b7fef 100644 --- a/tests/test-cj-window-toggle-lib.el +++ b/tests/test-cj-window-toggle-lib.el @@ -184,5 +184,76 @@ (should (eq (cdr (assq 'direction received-alist)) 'bottom)) (should (= (cdr (assq 'window-height received-alist)) 0.4)))) +;; --------------------------- side-window helpers --------------------------- + +(defvar test-cj-side-window--size nil) + +(ert-deftest test-cj-side-window-capture-records-bottom-height () + "Normal: a bottom side window writes a height fraction into the size var." + (save-window-excursion + (delete-other-windows) + (let* ((buf (get-buffer-create "*cj-side-test*")) + (win (display-buffer-in-side-window + buf '((side . bottom) (window-height . 0.5)))) + (test-cj-side-window--size nil)) + (unwind-protect + (progn + (cj/side-window-capture-size win 'bottom 'test-cj-side-window--size) + (should (floatp test-cj-side-window--size)) + (should (<= 0.05 test-cj-side-window--size 0.95))) + (when (window-live-p win) (delete-window win)) + (kill-buffer buf))))) + +(ert-deftest test-cj-side-window-capture-noop-on-nil-window () + "Error: nil window leaves the size var unchanged." + (let ((test-cj-side-window--size 0.42)) + (cj/side-window-capture-size nil 'bottom 'test-cj-side-window--size) + (should (= test-cj-side-window--size 0.42)))) + +(ert-deftest test-cj-side-window-capture-noop-on-deleted-window () + "Error: a deleted window leaves the size var unchanged." + (let ((test-cj-side-window--size 0.42) + (dead (save-window-excursion + (delete-other-windows) + (let* ((buf (get-buffer-create "*cj-side-dead*")) + (w (display-buffer-in-side-window + buf '((side . bottom) (window-height . 0.5))))) + (delete-window w) + (kill-buffer buf) + w)))) + (cj/side-window-capture-size dead 'bottom 'test-cj-side-window--size) + (should (= test-cj-side-window--size 0.42)))) + +(ert-deftest test-cj-side-window-display-uses-default-when-state-nil () + "Normal: nil state -> default size under window-height for a bottom side." + (let (received-alist + (test-cj-side-window--size nil)) + (cl-letf (((symbol-function 'display-buffer-in-side-window) + (lambda (_b a) (setq received-alist a) 'fake-window))) + (cj/side-window-display 'fake-buf 'bottom 'test-cj-side-window--size 0.3)) + (should (eq (cdr (assq 'side received-alist)) 'bottom)) + (should (= (cdr (assq 'window-height received-alist)) 0.3)) + (should-not (assq 'window-width received-alist)))) + +(ert-deftest test-cj-side-window-display-uses-stored-size () + "Normal: a stored fraction overrides the default." + (let (received-alist + (test-cj-side-window--size 0.55)) + (cl-letf (((symbol-function 'display-buffer-in-side-window) + (lambda (_b a) (setq received-alist a) 'fake-window))) + (cj/side-window-display 'fake-buf 'bottom 'test-cj-side-window--size 0.3)) + (should (= (cdr (assq 'window-height received-alist)) 0.55)))) + +(ert-deftest test-cj-side-window-display-left-uses-window-width () + "Boundary: a left side puts the size under window-width, not window-height." + (let (received-alist + (test-cj-side-window--size nil)) + (cl-letf (((symbol-function 'display-buffer-in-side-window) + (lambda (_b a) (setq received-alist a) 'fake-window))) + (cj/side-window-display 'fake-buf 'left 'test-cj-side-window--size 0.25)) + (should (eq (cdr (assq 'side received-alist)) 'left)) + (should (= (cdr (assq 'window-width received-alist)) 0.25)) + (should-not (assq 'window-height received-alist)))) + (provide 'test-cj-window-toggle-lib) ;;; test-cj-window-toggle-lib.el ends here |
