feat: drive default GridConfig through canonical Box
This commit is contained in:
parent
e7dcb55d61
commit
31838e4e02
@ -587,7 +587,7 @@ style-rule functions immediately below are public module-level style APIs.
|
||||
|
||||
| Function | Use |
|
||||
| --- | --- |
|
||||
| `ebox-text-create`, `ebox-normal-layout-create`, `ebox-row-layout-create`, `ebox-column-layout-create`, `ebox-flex-layout-create`, `ebox-box-create` | Framework-integration port for typed TextNode, Normal/Row/Column/Flex LayoutConfig, and BoxNode values. |
|
||||
| `ebox-text-create`, `ebox-normal-layout-create`, `ebox-row-layout-create`, `ebox-column-layout-create`, `ebox-flex-layout-create`, `ebox-grid-layout-create`, `ebox-box-create` | Framework-integration port for typed TextNode, Normal/Row/Column/Flex/Grid LayoutConfig, and BoxNode values. |
|
||||
| `ebox-create`, `ebox-build` | Create a legacy node or compile the `.ebox` list DSL during migration. |
|
||||
| `ebox-concat`, `ebox-stack`, `ebox-row`, `ebox-column`, `ebox-spacer` | Compose simple horizontal/vertical layouts and blank boxes. |
|
||||
| `ebox-flex`, `ebox-flex-item` | Build flex containers and item metadata. |
|
||||
|
||||
@ -533,7 +533,7 @@ style API。
|
||||
|
||||
| 函数 | 用途 |
|
||||
| --- | --- |
|
||||
| `ebox-text-create`、`ebox-normal-layout-create`、`ebox-row-layout-create`、`ebox-column-layout-create`、`ebox-flex-layout-create`、`ebox-box-create` | 框架集成使用的 typed TextNode、Normal/Row/Column/Flex LayoutConfig 与 BoxNode 端口。 |
|
||||
| `ebox-text-create`、`ebox-normal-layout-create`、`ebox-row-layout-create`、`ebox-column-layout-create`、`ebox-flex-layout-create`、`ebox-grid-layout-create`、`ebox-box-create` | 框架集成使用的 typed TextNode、Normal/Row/Column/Flex/Grid LayoutConfig 与 BoxNode 端口。 |
|
||||
| `ebox-create`、`ebox-build` | 迁移期间创建旧节点或编译 `.ebox` list DSL。 |
|
||||
| `ebox-concat`、`ebox-stack`、`ebox-row`、`ebox-column`、`ebox-spacer` | 组合简单横向/纵向布局与空白 box。 |
|
||||
| `ebox-flex`、`ebox-flex-item` | 构建 Flex 容器与 item metadata。 |
|
||||
|
||||
@ -178,7 +178,8 @@ child. Row and Column layouts accept any number of children."
|
||||
children)
|
||||
(error "Ebox Box children must be canonical Text/Box nodes: %S"
|
||||
children))
|
||||
(unless (memq (ebox-layout-config-kind layout) '(normal row column flex))
|
||||
(unless (memq (ebox-layout-config-kind layout)
|
||||
'(normal row column flex grid))
|
||||
(error "Ebox Box Layout is not implemented: %S"
|
||||
(ebox-layout-config-kind layout)))
|
||||
(when (and (eq (ebox-layout-config-kind layout) 'normal)
|
||||
|
||||
89
ebox-grid.el
89
ebox-grid.el
@ -12,6 +12,7 @@
|
||||
(require 'cl-lib)
|
||||
(require 'seq)
|
||||
(require 'subr-x)
|
||||
(require 'ebox-layout-config)
|
||||
(require 'ebox-layout)
|
||||
(require 'ebox-flex)
|
||||
|
||||
@ -31,7 +32,7 @@
|
||||
(defconst ebox--grid-layout-prop-keys
|
||||
'(:grid-template-columns :grid-template-rows
|
||||
:grid-auto-columns :grid-auto-rows :grid-auto-flow
|
||||
:grid-column-gap :grid-row-gap :gap
|
||||
:column-gap :row-gap :grid-column-gap :grid-row-gap :gap
|
||||
:justify-items :align-items :justify-content :align-content)
|
||||
"Properties owned by the grid formatting context.")
|
||||
|
||||
@ -123,15 +124,39 @@
|
||||
(defun ebox-grid--gap-pair (props)
|
||||
"Return normalized (ROW-GAP . COLUMN-GAP) from grid PROPS."
|
||||
(let* ((pair (ebox--flex-gap-pair (plist-get props :gap)))
|
||||
(row (if (plist-member props :grid-row-gap)
|
||||
(plist-get props :grid-row-gap)
|
||||
(car pair)))
|
||||
(column (if (plist-member props :grid-column-gap)
|
||||
(plist-get props :grid-column-gap)
|
||||
(cdr pair))))
|
||||
(row (cond
|
||||
((plist-member props :row-gap) (plist-get props :row-gap))
|
||||
((plist-member props :grid-row-gap)
|
||||
(plist-get props :grid-row-gap))
|
||||
(t (car pair))))
|
||||
(column (cond
|
||||
((plist-member props :column-gap)
|
||||
(plist-get props :column-gap))
|
||||
((plist-member props :grid-column-gap)
|
||||
(plist-get props :grid-column-gap))
|
||||
(t (cdr pair)))))
|
||||
(cons (or (ebox--flex-line-value row 0) 0)
|
||||
(or (ebox--nonnegative-horizontal-size-pixels column 0) 0))))
|
||||
|
||||
(defconst ebox--grid-default-config-props
|
||||
'(:grid-template-columns nil :grid-template-rows nil
|
||||
:grid-auto-columns nil :grid-auto-rows nil :grid-auto-flow row
|
||||
:row-gap 0 :column-gap 0
|
||||
:justify-items stretch :align-items stretch
|
||||
:justify-content start :align-content start)
|
||||
"Validated property set for the first default GridConfig slice.")
|
||||
|
||||
(defun ebox-grid-layout-config-props-p (props)
|
||||
"Return non-nil when PROPS are the validated default GridConfig."
|
||||
(equal props ebox--grid-default-config-props))
|
||||
|
||||
;;;###autoload
|
||||
(defun ebox-grid-layout-create ()
|
||||
"Return the canonical Grid layout config with computed defaults."
|
||||
(ebox-layout-config--create
|
||||
:kind 'grid
|
||||
:props (copy-sequence ebox--grid-default-config-props)))
|
||||
|
||||
(defun ebox-grid--visual-props-p (props)
|
||||
"Return non-nil when PROPS contains visible box behavior."
|
||||
(cl-loop for (key _value) on props by #'cddr
|
||||
@ -618,10 +643,25 @@ size for start/center/end alignment unless it would overflow its track."
|
||||
(puthash region-id box ebox--region-box-table)))
|
||||
rendered))
|
||||
|
||||
(defun ebox--render-grid (node)
|
||||
"Render GRID NODE to a propertized string."
|
||||
(let* ((props (plist-get node :raw-props))
|
||||
(children (ebox-tree-layout-children node))
|
||||
(defun ebox-grid--legacy-constraints (props wrapper)
|
||||
"Return private GridConstraints from legacy PROPS and WRAPPER."
|
||||
(let ((width (ebox--nonnegative-horizontal-size-pixels
|
||||
(plist-get props :width)
|
||||
(ebox--viewport-pixel-width nil)))
|
||||
(height (ebox--flex-line-value (plist-get props :height) nil)))
|
||||
(when wrapper
|
||||
(setq width (ebox--box-sizing-content-pixel wrapper width)))
|
||||
(list :width width :height height)))
|
||||
|
||||
(defun ebox-grid--box-constraints (box)
|
||||
"Project BOX content dimensions to private GridConstraints."
|
||||
(list :width (ebox--wrapper-content-viewport-pixel box)
|
||||
:height (ebox--resolve-size-content-height
|
||||
box (ebox-get box :height) nil)))
|
||||
|
||||
(defun ebox--render-grid-children (config constraints children)
|
||||
"Render Grid CONFIG under frame CONSTRAINTS over flat CHILDREN."
|
||||
(let* ((props config)
|
||||
(columns (ebox-grid--normalize-tracks
|
||||
(plist-get props :grid-template-columns) 'columns))
|
||||
(rows (ebox-grid--normalize-tracks
|
||||
@ -639,14 +679,9 @@ size for start/center/end alignment unless it would overflow its track."
|
||||
(matrix (nth 1 placed))
|
||||
(row-count (nth 2 placed))
|
||||
(column-count (nth 3 placed))
|
||||
(available (ebox--nonnegative-horizontal-size-pixels
|
||||
(plist-get props :width)
|
||||
(ebox--viewport-pixel-width nil)))
|
||||
(wrapper (plist-get node :box))
|
||||
(height (ebox--flex-line-value (plist-get props :height) nil))
|
||||
(available (plist-get constraints :width))
|
||||
(height (plist-get constraints :height))
|
||||
(rendered (make-hash-table :test 'eq)))
|
||||
(when wrapper
|
||||
(setq available (ebox--box-sizing-content-pixel wrapper available)))
|
||||
(unless (memq flow '(row column))
|
||||
(error "ebox-grid: :grid-auto-flow must be `row' or `column'"))
|
||||
(dolist (entry entries)
|
||||
@ -674,7 +709,23 @@ size for start/center/end alignment unless it would overflow its track."
|
||||
(or (plist-get props :align-content) 'start)))
|
||||
(body (ebox-grid--render entries matrix column-layout row-layout
|
||||
props sized-rendered)))
|
||||
(ebox--render-grid-box node body))))
|
||||
body)))
|
||||
|
||||
(defun ebox--render-grid-box-children (box config children)
|
||||
"Render canonical BOX Grid CONFIG over flat CHILDREN."
|
||||
(ebox--render-grid-children
|
||||
config (ebox-grid--box-constraints box) children))
|
||||
|
||||
(defun ebox--render-grid (node)
|
||||
"Render legacy GRID NODE to a propertized string."
|
||||
(let* ((props (plist-get node :raw-props))
|
||||
(wrapper (plist-get node :box)))
|
||||
(ebox--render-grid-box
|
||||
node
|
||||
(ebox--render-grid-children
|
||||
(ebox--plist-keep-keys props ebox--grid-layout-prop-keys)
|
||||
(ebox-grid--legacy-constraints props wrapper)
|
||||
(ebox-tree-layout-children node)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ebox-grid (&rest items)
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
|
||||
(declare-function ebox-flex-layout-config-props-p
|
||||
"ebox-flex" (props))
|
||||
(declare-function ebox-grid-layout-config-props-p
|
||||
"ebox-grid" (props))
|
||||
|
||||
(cl-defstruct
|
||||
(ebox-layout-config
|
||||
@ -31,6 +33,9 @@
|
||||
('flex
|
||||
(and (fboundp 'ebox-flex-layout-config-props-p)
|
||||
(ebox-flex-layout-config-props-p props)))
|
||||
('grid
|
||||
(and (fboundp 'ebox-grid-layout-config-props-p)
|
||||
(ebox-grid-layout-config-props-p props)))
|
||||
(_ nil))))
|
||||
(unless valid-p
|
||||
(error "Invalid %S LayoutConfig properties: %S"
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
(require 'ebox-buffer-backend)
|
||||
|
||||
(declare-function ebox--render-grid "ebox-grid" (node))
|
||||
(declare-function ebox--render-grid-box-children
|
||||
"ebox-grid" (box props children))
|
||||
(declare-function ebox--render-flex "ebox-flex" (node))
|
||||
(declare-function ebox--render-flex-box-children
|
||||
"ebox-flex" (box props children))
|
||||
@ -965,9 +967,15 @@ FALLBACK is used for nil, auto, or unavailable viewport-height values."
|
||||
(ebox-layout-config-props
|
||||
(plist-get box :ebox-layout-config))
|
||||
children))
|
||||
('grid
|
||||
(ebox--render-grid-box-children
|
||||
box
|
||||
(ebox-layout-config-props
|
||||
(plist-get box :ebox-layout-config))
|
||||
children))
|
||||
(_ (error "Ebox Box has unsupported LayoutConfig: %S" kind)))))))
|
||||
(ebox--record-box-content-width-exact
|
||||
box content (memq kind '(row column flex)))
|
||||
box content (memq kind '(row column flex grid)))
|
||||
content))
|
||||
|
||||
(defun ebox--box-content (box)
|
||||
|
||||
1
ebox.el
1
ebox.el
@ -4607,6 +4607,7 @@ through dirty-set and patch-set execution before falling back to root rerender."
|
||||
ebox-flex-item
|
||||
ebox-grid
|
||||
ebox-grid-fr
|
||||
ebox-grid-layout-create
|
||||
ebox-grid-item
|
||||
ebox-host-ref-bounds
|
||||
ebox-host-ref-position
|
||||
|
||||
@ -312,6 +312,97 @@
|
||||
(should (= (ebox--string-pixel-width typed-rendered)
|
||||
(ebox--string-pixel-width legacy-rendered))))))
|
||||
|
||||
(ert-deftest ebox-canonical-grid-box-uses-one-box-identity-and-child-list ()
|
||||
"A GridConfig should select the Box algorithm without a Grid runtime node."
|
||||
(let* ((layout (ebox-grid-layout-create))
|
||||
(box (ebox-box-create
|
||||
:layout layout
|
||||
:children (list (ebox-text-create :value "A")
|
||||
(ebox-text-create :value "B"))
|
||||
:width '(120)))
|
||||
(rendered (ebox-render box)))
|
||||
(should (equal (ebox-layout-config-props layout)
|
||||
ebox--grid-default-config-props))
|
||||
(should-not (plist-member (ebox-layout-config-props layout) :width))
|
||||
(should-not (plist-member (ebox-layout-config-props layout) :height))
|
||||
(should (ebox-box-node-p box))
|
||||
(should (eq (ebox-layout-config-kind (ebox-box-node-layout box)) 'grid))
|
||||
(should (eq (ebox-tree-node-children box)
|
||||
(ebox-box-node-children box)))
|
||||
(should (= (ebox-dsl-test--tree-count box (lambda (_node) t)) 3))
|
||||
(should (= (ebox-dsl-test--tree-count box #'ebox-node-kind) 3))
|
||||
(should
|
||||
(= (ebox-dsl-test--tree-count
|
||||
box (lambda (node) (eq (plist-get node :ebox-type) 'grid)))
|
||||
0))
|
||||
(should-not (plist-member box :ebox-content-node))
|
||||
(should (equal (ebox--computed-display box) '(block grid)))
|
||||
(should (string=
|
||||
(replace-regexp-in-string
|
||||
"[[:space:]]" "" (substring-no-properties rendered))
|
||||
"AB"))
|
||||
(should (equal (mapcar #'ebox--string-pixel-width
|
||||
(ebox-string-lines rendered))
|
||||
'(120 120 120)))))
|
||||
|
||||
(ert-deftest ebox-canonical-grid-consumes-direct-child-placement ()
|
||||
"Grid placement should stay on child Boxes without grid-item wrappers."
|
||||
(let* ((later
|
||||
(ebox-box-create :layout (ebox-normal-layout-create)
|
||||
:children (list (ebox-text-create :value "A"))
|
||||
:grid-row 2))
|
||||
(earlier
|
||||
(ebox-box-create :layout (ebox-normal-layout-create)
|
||||
:children (list (ebox-text-create :value "B"))
|
||||
:grid-row 1))
|
||||
(box (ebox-box-create :layout (ebox-grid-layout-create)
|
||||
:children (list later earlier)))
|
||||
(rendered (substring-no-properties (ebox-render box))))
|
||||
(should (string=
|
||||
(replace-regexp-in-string "[[:space:]]" "" rendered)
|
||||
"BA"))
|
||||
(should (= (ebox-dsl-test--tree-count box (lambda (_node) t)) 5))
|
||||
(should (= (ebox-dsl-test--tree-count box #'ebox-node-kind) 5))))
|
||||
|
||||
(ert-deftest ebox-canonical-grid-revalidates-the-typed-config-boundary ()
|
||||
"GridConfig must reject frame properties and cross-kind property sets."
|
||||
(let ((extra-frame-prop (ebox-grid-layout-create))
|
||||
(cross-kind-props (ebox-grid-layout-create)))
|
||||
(setf (ebox-layout-config-props extra-frame-prop)
|
||||
(append (ebox-layout-config-props extra-frame-prop)
|
||||
'(:width 999)))
|
||||
(setf (ebox-layout-config-kind cross-kind-props) 'column)
|
||||
(should-error
|
||||
(ebox-box-create :layout extra-frame-prop :children nil)
|
||||
:type 'error)
|
||||
(should-error
|
||||
(ebox-box-create :layout cross-kind-props :children nil)
|
||||
:type 'error)))
|
||||
|
||||
(ert-deftest ebox-canonical-grid-projects-one-box-frame-constraint ()
|
||||
"Typed and legacy Grid should derive the same content size from BoxFrame."
|
||||
(dolist (sizing '(border-box content-box))
|
||||
(let* ((typed
|
||||
(ebox-box-create
|
||||
:layout (ebox-grid-layout-create)
|
||||
:children (list (ebox-text-create :value "A")
|
||||
(ebox-text-create :value "B"))
|
||||
:width '(140) :padding '(0 (10)) :box-sizing sizing))
|
||||
(legacy
|
||||
(ebox-grid
|
||||
:width '(140) :padding '(0 (10)) :box-sizing sizing
|
||||
(ebox-text-create :value "A")
|
||||
(ebox-text-create :value "B")))
|
||||
(typed-rendered (ebox-render typed))
|
||||
(legacy-rendered (ebox-render legacy)))
|
||||
(should (string= (substring-no-properties typed-rendered)
|
||||
(substring-no-properties legacy-rendered)))
|
||||
(should
|
||||
(equal (mapcar #'ebox--string-pixel-width
|
||||
(ebox-string-lines typed-rendered))
|
||||
(mapcar #'ebox--string-pixel-width
|
||||
(ebox-string-lines legacy-rendered)))))))
|
||||
|
||||
(ert-deftest ebox-canonical-flex-consumes-direct-child-participation ()
|
||||
"Flex participation should stay on child Boxes without item wrappers."
|
||||
(let* ((left-text (ebox-text-create :value "A"))
|
||||
@ -355,7 +446,8 @@
|
||||
"Empty layout Boxes should not synthesize layout identities."
|
||||
(dolist (layout (list (ebox-row-layout-create)
|
||||
(ebox-column-layout-create)
|
||||
(ebox-flex-layout-create)))
|
||||
(ebox-flex-layout-create)
|
||||
(ebox-grid-layout-create)))
|
||||
(let ((box (ebox-box-create :layout layout :children nil)))
|
||||
(should (ebox-box-node-p box))
|
||||
(should-not (ebox-tree-node-children box))
|
||||
|
||||
@ -172,6 +172,7 @@
|
||||
("ebox-grid-fr" . "defun")
|
||||
("ebox-grid-item" . "defun")
|
||||
("ebox-grid" . "defun")
|
||||
("ebox-grid-layout-create" . "defun")
|
||||
("ebox-normal-layout-create" . "defun")
|
||||
("ebox-row-layout-create" . "defun")
|
||||
("ebox-column-layout-create" . "defun")
|
||||
@ -228,6 +229,7 @@
|
||||
ebox-flex-item
|
||||
ebox-grid
|
||||
ebox-grid-fr
|
||||
ebox-grid-layout-create
|
||||
ebox-grid-item
|
||||
ebox-host-ref-bounds
|
||||
ebox-host-ref-position
|
||||
|
||||
Loading…
Reference in New Issue
Block a user