From 256868a7ee24a9c88b5ca5e10fd0bae6653a4d82 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Tue, 25 Aug 2026 22:45:23 +0800 Subject: [PATCH] refactor: delegate resize ownership to Ebox --- DESIGN.md | 2 +- README.md | 2 +- README.zh-CN.md | 2 +- ebox-playground.el | 123 ++---------------- .../ebox-playground-flex-resize-evaluator.el | 4 +- tests/ebox-playground-tests.el | 38 +++--- 6 files changed, 33 insertions(+), 138 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 5e9a226..4e03462 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -74,7 +74,7 @@ ## Implementation constraints - Framework/styling system: Emacs 29.1+ and public Ebox constructors/properties only. - Design-token constraints: Reuse the Flex reference's restrained color families without introducing a theme package. -- Performance constraints: One synchronous initial render; window resizing uses one bounded idle debounce and Ebox's incremental viewport update path. +- Performance constraints: One synchronous initial render; window resizing delegates to Ebox's single bounded viewport controller and incremental update path. - Compatibility constraints: No ETAF dependency and no `ebox--*` calls. - Test/screenshot expectations: `make check` passes and a fullscreen one-window screenshot shows complete, unclipped labeled sections. diff --git a/README.md b/README.md index 7af2bd6..0a8ff9f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ECSS 0.1.0 and TP 1.0.0 are independent packages and may be installed in either Standalone gallery entry points default to a definite 720 px canvas so nested examples have stable space in batch renders and one-window demos. Split-window `.ebox` previews instead bind `(viewport)` to the preview window's display-safe width, so viewport-based separators and sections fit the right-hand pane. `etaf-playground` remains the separate higher-level Component and Runtime showcase. -Standalone and batch fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. When `C-c C-c` renders an `.ebox` source buffer, the command keeps the source on the left, opens the preview on the right, and binds `(viewport)` to the preview window's display-safe width. The preview then debounces window-size changes and republishes the width through Ebox's incremental viewport update path. +Standalone and batch fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. When `C-c C-c` renders an `.ebox` source buffer, the command keeps the source on the left, opens the preview on the right, and binds `(viewport)` to Ebox's shared display-safe window width. Continuous window changes are coalesced by Ebox's single viewport controller; the Playground owns no second resize hook or timer. ```elisp (require 'ebox-playground) diff --git a/README.zh-CN.md b/README.zh-CN.md index fd1cfe9..5e4b583 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,7 +6,7 @@ ECSS 0.1.0 与 TP 1.0.0 是互相独立的包,安装顺序任意;两者都 独立画廊入口默认使用明确的 720 px 画布,因此 batch 渲染和单窗口演示里的嵌套示例有稳定空间。分屏 `.ebox` 预览则会把 `(viewport)` 绑定为右侧预览窗口的显示安全宽度,所以依赖 viewport 的分隔条和区块会适配右侧窗格。高层 Component/Runtime 展示仍由独立的 `etaf-playground` 提供。 -独立和 batch fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 `ebox-viewport-width`,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。通过 `C-c C-c` 渲染 `.ebox` 源文件时,命令会把源文件保留在左侧、在右侧打开预览,并将 `(viewport)` 绑定为预览窗口的显示安全像素宽度;预览随后会合并连续的 window-size 变化,并通过 Ebox 已有的 viewport 增量更新路径发布宽度。 +独立和 batch fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 `ebox-viewport-width`,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。通过 `C-c C-c` 渲染 `.ebox` 源文件时,命令会把源文件保留在左侧、在右侧打开预览,并将 `(viewport)` 绑定为 Ebox 统一计算的显示安全宽度;连续 window-size 变化由 Ebox 唯一的 viewport controller 合并,Playground 不再拥有第二套 resize hook 或 timer。 ```elisp (require 'ebox-playground) diff --git a/ebox-playground.el b/ebox-playground.el index 39f119d..6f77ffa 100644 --- a/ebox-playground.el +++ b/ebox-playground.el @@ -20,18 +20,6 @@ (require 'subr-x) (defvar read-eval) -(defvar-local ebox-playground--resize-timer nil - "Pending idle timer for the current preview buffer.") - -(defvar-local ebox-playground--preview-viewport-width nil - "Last viewport width published for the current preview buffer.") - -(defvar-local ebox-playground--preview-auto-resize-p nil - "Non-nil when the current preview follows its displaying window.") - -(defvar ebox-playground--tracked-preview-buffers (make-hash-table :test 'eq) - "Preview buffers whose windows should drive viewport updates.") - (defconst ebox-playground-buffer-name "*Ebox Playground*" "Default buffer name used by `ebox-playground-open'.") @@ -77,7 +65,6 @@ (viewport-width (or bound-width preview-width ebox-playground-default-viewport-width)) - (auto-resize-p (and (null bound-width) (numberp preview-width))) (ebox-viewport-width viewport-width) (source-buffer (current-buffer)) (source-form (ebox-playground--source-form)) @@ -88,9 +75,6 @@ (set-window-buffer source-window source-buffer) (set-window-buffer preview-window buffer) (select-window source-window) - (ebox-playground--track-preview-buffer - buffer viewport-width auto-resize-p - (plist-get node :bgcolor)) (message "Rendered Ebox DSL into %s" name) buffer)) @@ -101,11 +85,6 @@ "Run Ebox DSL examples from source files." :group 'ebox) -(defcustom ebox-playground-resize-idle-delay 0.05 - "Seconds to wait before publishing a settled preview window width." - :type 'number - :group 'ebox-playground) - (defconst ebox-playground-directory (file-name-directory (or load-file-name buffer-file-name)) "Directory containing the Ebox Playground package.") @@ -123,13 +102,8 @@ :group 'ebox-playground) (defun ebox-playground--window-viewport-width (&optional window) - "Return WINDOW's display-safe viewport width in pixels, or nil when unavailable." - (when (window-live-p window) - (let ((width (window-body-width window t))) - (and (numberp width) - (> width 0) - (max 1 (- (floor width) - (max 1 (frame-char-width (window-frame window))))))))) + "Return Ebox's shared display-safe viewport width for WINDOW." + (ebox-viewport-window-width window)) (defun ebox-playground--effective-viewport-width (&optional window) "Return the caller's width, WINDOW's text width, or the compact default." @@ -140,12 +114,6 @@ ((ebox-playground--window-viewport-width window)) (t ebox-playground-default-viewport-width))) -(defun ebox-playground--cancel-resize-timer () - "Cancel the current preview's pending resize publication." - (when (timerp ebox-playground--resize-timer) - (cancel-timer ebox-playground--resize-timer)) - (setq ebox-playground--resize-timer nil)) - (defun ebox-playground--apply-preview-display-settings (&optional canvas-background) "Apply canvas redisplay settings to the current preview buffer. CANVAS-BACKGROUND fills display space outside generated root lines." @@ -205,88 +173,13 @@ CANVAS-BACKGROUND fills display space outside generated root lines." (unless published-p (ebox-playground--restore-preview-display-snapshot snapshot)))))) -(defun ebox-playground--resize-preview (buffer window) - "Publish BUFFER's current WINDOW viewport after a resize settles." - (when (buffer-live-p buffer) - (with-current-buffer buffer - (setq-local ebox-playground--resize-timer nil) - (when (and ebox-playground--preview-auto-resize-p - (window-live-p window) - (eq (window-buffer window) buffer)) - (let ((width (ebox-playground--window-viewport-width window))) - (when (and width - (/= width - (or ebox-playground--preview-viewport-width 0))) - (let ((report (ebox-rerender-buffer-with-context buffer width))) - ;; Keep the last published width as the retry key. A failed - ;; candidate must not make a later identical resize look like a - ;; successful no-op. - (when (plist-get report :runtime-published) - (setq-local ebox-playground--preview-viewport-width width))))))))) - -(defun ebox-playground--window-size-change (frame) - "Schedule a viewport update after a size change in FRAME. -The default `window-size-change-functions' dispatcher passes a frame, so the -preview window must be resolved from that frame before its viewport is read." - (when (frame-live-p frame) - (dolist (window (window-list frame 'no-minibuf)) - (let ((buffer (window-buffer window))) - (when (and (buffer-live-p buffer) - (gethash buffer ebox-playground--tracked-preview-buffers)) - (with-current-buffer buffer - (let ((width (ebox-playground--window-viewport-width window))) - (when (and width - (/= width - (or ebox-playground--preview-viewport-width 0))) - (ebox-playground--cancel-resize-timer) - (setq-local ebox-playground--resize-timer - (run-with-idle-timer - (max 0 ebox-playground-resize-idle-delay) - nil #'ebox-playground--resize-preview - buffer window)))))))))) - -(defun ebox-playground--untrack-preview-buffer (buffer) - "Stop tracking BUFFER and remove the shared window hook when unused." - (remhash buffer ebox-playground--tracked-preview-buffers) - (when (= (hash-table-count ebox-playground--tracked-preview-buffers) 0) - (remove-hook 'window-size-change-functions - #'ebox-playground--window-size-change))) - -(defun ebox-playground--cleanup-preview () - "Release resize state owned by the current preview buffer." - (ebox-playground--cancel-resize-timer) - (ebox-playground--untrack-preview-buffer (current-buffer))) - -(defun ebox-playground--track-preview-buffer - (buffer viewport-width &optional auto-resize-p canvas-background) - "Track BUFFER at VIEWPORT-WIDTH and optionally follow its display window. -AUTO-RESIZE-P enables following the display window. CANVAS-BACKGROUND fills -display space outside generated root lines." - (ebox-playground--untrack-preview-buffer buffer) - (with-current-buffer buffer - (ebox-playground--cancel-resize-timer) - (ebox-playground--apply-preview-display-settings canvas-background) - (setq-local ebox-playground--preview-viewport-width viewport-width) - (setq-local ebox-playground--preview-auto-resize-p auto-resize-p) - (add-hook 'kill-buffer-hook #'ebox-playground--cleanup-preview nil t)) - (when auto-resize-p - (puthash buffer t ebox-playground--tracked-preview-buffers) - (add-hook 'window-size-change-functions - #'ebox-playground--window-size-change)) - buffer) - -(defun ebox-playground--render-node-to-buffer - (buffer node viewport-width &optional auto-resize-p) +(defun ebox-playground--render-node-to-buffer (buffer node viewport-width) "Render NODE into BUFFER using VIEWPORT-WIDTH. -AUTO-RESIZE-P enables following the display window." - (let* ((ebox-viewport-width - (or viewport-width - (ebox-playground--effective-viewport-width))) - (target buffer)) - (ebox-playground--track-preview-buffer - (ebox-playground--render-to-preview-buffer target node) - ebox-viewport-width auto-resize-p - (and (consp node) (plist-get node :bgcolor))))) +Visible mounted buffers follow their window through Ebox's viewport controller." + (let ((ebox-viewport-width + (or viewport-width + (ebox-playground--effective-viewport-width)))) + (ebox-playground--render-to-preview-buffer buffer node))) (defun ebox-playground--static-value-p (value) "Return non-nil when VALUE is a literal or explicitly quoted form." diff --git a/scripts/ebox-playground-flex-resize-evaluator.el b/scripts/ebox-playground-flex-resize-evaluator.el index 7280914..bde93f8 100644 --- a/scripts/ebox-playground-flex-resize-evaluator.el +++ b/scripts/ebox-playground-flex-resize-evaluator.el @@ -82,8 +82,6 @@ than charging initial frame settling to resize." (let ((report (ebox-rerender-buffer-with-context preview width))) (unless (plist-get report :runtime-published) (error "flex preview initial viewport was not published")))) - (with-current-buffer preview - (setq-local ebox-playground--preview-viewport-width width)) width)) (defun ebox-playground-flex-resize-evaluator-run () @@ -96,7 +94,7 @@ daemon evaluator does not enable background prewarm, keeping the measured action free of concurrent cache-warming CPU work." (let ((ebox-render-gc-cons-threshold nil) (ebox-render-gc-cons-percentage nil) - (ebox-playground-resize-idle-delay 0.05) + (ebox-viewport-resize-delay 0.05) (source nil) (preview nil) (preview-window nil) diff --git a/tests/ebox-playground-tests.el b/tests/ebox-playground-tests.el index 2f905d3..e86250d 100644 --- a/tests/ebox-playground-tests.el +++ b/tests/ebox-playground-tests.el @@ -214,14 +214,12 @@ (lambda (&optional _frame) 8))) (should (= (ebox-playground--effective-viewport-width (selected-window)) - 979))))) + 971))))) -(ert-deftest ebox-playground-window-viewport-leaves-display-reserve () - "A preview viewport should leave one character column for redisplay." - (cl-letf (((symbol-function 'window-body-width) - (lambda (&optional _window _pixelwise) 987)) - ((symbol-function 'frame-char-width) - (lambda (&optional _frame) 8))) +(ert-deftest ebox-playground-window-viewport-uses-ebox-sampler () + "A preview and the Ebox resize controller share one viewport sample." + (cl-letf (((symbol-function 'ebox-viewport-window-width) + (lambda (_window) 979))) (should (= (ebox-playground--window-viewport-width (selected-window)) 979)))) @@ -242,7 +240,7 @@ (with-current-buffer preview (should (= (ebox-playground-test--max-line-width (buffer-string)) - (- window-width 8)))))) + (- window-width 16)))))) (when (get-buffer preview) (kill-buffer preview))))) @@ -252,12 +250,12 @@ ebox-playground-directory)) (preview (format "*Ebox Preview: %s*" path)) (window-width 699) - (viewport 691)) + (viewport 683)) (unwind-protect (cl-letf (((symbol-function 'window-body-width) (lambda (&optional _window _pixelwise) window-width)) ((symbol-function 'frame-char-width) - (lambda (&optional _frame) (- window-width viewport)))) + (lambda (&optional _frame) 8))) (delete-other-windows) (with-temp-buffer (setq buffer-file-name path) @@ -265,7 +263,6 @@ (ebox-dsl-mode) (ebox-dsl-render) (with-current-buffer preview - (should (= ebox-playground--preview-viewport-width viewport)) (should (cl-every (lambda (width) (<= width viewport)) (ebox-playground-test--line-widths @@ -309,7 +306,7 @@ "A preview resize should publish an incremental Ebox viewport update." (let ((preview "*Ebox Preview: /tmp/ebox-playground-viewport-resize.ebox*") (window-width 987) - (ebox-playground-resize-idle-delay 0)) + (ebox-viewport-resize-delay 0)) (unwind-protect (cl-letf (((symbol-function 'window-body-width) (lambda (&optional _window _pixelwise) window-width)) @@ -323,16 +320,23 @@ (setq window-width 1234) (with-current-buffer preview (let ((window (get-buffer-window (current-buffer)))) - (should (memq #'ebox-playground--window-size-change + (should (memq #'ebox--window-size-change window-size-change-functions)) - (ebox-playground--window-size-change (window-frame window)) - (should (timerp ebox-playground--resize-timer)) + (let ((noninteractive nil)) + (ebox--window-size-change (window-frame window))) ;; Batch Emacs does not service idle timers from `sit-for'. - (ebox-playground--resize-preview (current-buffer) window))) + (let ((pending + (gethash (current-buffer) + ebox--viewport-resize-pending-table))) + (should pending) + (when (timerp (aref pending 4)) + (cancel-timer (aref pending 4))) + (ebox--viewport-resize-apply + (current-buffer) (aref pending 0))))) (with-current-buffer preview (should (= (ebox-playground-test--max-line-width (buffer-string)) - (- window-width 8))) + (- window-width 16))) (let ((report (ebox-buffer-update-report (current-buffer)))) (should (eq (plist-get report :constraint-source) 'viewport)) (should (eq (plist-get report :strategy) 'owner-rerender))))))