refactor: delegate resize ownership to Ebox

This commit is contained in:
Kinneyzhang 2026-08-25 22:45:23 +08:00
parent 24b69a64c5
commit 256868a7ee
6 changed files with 33 additions and 138 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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."

View File

@ -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)

View File

@ -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))))))