fix: keep grid borders aligned across multiline items
Measure auto-width children intrinsically, constrain them to resolved tracks, account for border-box wrapper content, and align every rendered line before painting the wrapper border.
This commit is contained in:
parent
ab9efe62a7
commit
d4dc572c2d
45
ebox-grid.el
45
ebox-grid.el
@ -439,6 +439,28 @@ Supported properties are `:grid-column', `:grid-row',
|
|||||||
(extra (* gap (max 0 (1- (plist-get entry :column-span))))))
|
(extra (* gap (max 0 (1- (plist-get entry :column-span))))))
|
||||||
(+ base extra)))
|
(+ base extra)))
|
||||||
|
|
||||||
|
(defun ebox-grid--auto-width-node-p (node)
|
||||||
|
"Return non-nil when NODE derives its width from the active viewport."
|
||||||
|
(let ((width (plist-get node :width)))
|
||||||
|
(or (null width) (eq width 'auto))))
|
||||||
|
|
||||||
|
(defun ebox-grid--entry-source (entry rendered width props)
|
||||||
|
"Return ENTRY's content rendered within WIDTH when it is auto-sized.
|
||||||
|
|
||||||
|
Stretching is the default grid item alignment. Re-render an auto-sized
|
||||||
|
child at its assigned width for that alignment, while preserving its natural
|
||||||
|
size for start/center/end alignment unless it would overflow its track."
|
||||||
|
(let* ((node (plist-get entry :node))
|
||||||
|
(source (gethash node rendered))
|
||||||
|
(auto-width-p (ebox-grid--auto-width-node-p node))
|
||||||
|
(justify (or (plist-get props :justify-items) 'stretch))
|
||||||
|
(stretch-p (memq justify '(normal stretch)))
|
||||||
|
(overflow-p (> (ebox--string-max-pixel-width source) width)))
|
||||||
|
(if (and auto-width-p (or stretch-p overflow-p))
|
||||||
|
(let ((ebox-viewport-width width))
|
||||||
|
(ebox--render-with-cache node))
|
||||||
|
source)))
|
||||||
|
|
||||||
(defun ebox-grid--row-entry-size (entry sizes gap)
|
(defun ebox-grid--row-entry-size (entry sizes gap)
|
||||||
"Return the target line size for ENTRY across row SIZES."
|
"Return the target line size for ENTRY across row SIZES."
|
||||||
(let* ((start (1- (plist-get entry :row)))
|
(let* ((start (1- (plist-get entry :row)))
|
||||||
@ -450,11 +472,12 @@ Supported properties are `:grid-column', `:grid-row',
|
|||||||
|
|
||||||
(defun ebox-grid--align (string width height justify align)
|
(defun ebox-grid--align (string width height justify align)
|
||||||
"Align STRING to WIDTH and HEIGHT using JUSTIFY and ALIGN."
|
"Align STRING to WIDTH and HEIGHT using JUSTIFY and ALIGN."
|
||||||
(let ((result (ebox--pixel-reach string width
|
(let ((result (ebox--lines-justify
|
||||||
(pcase justify
|
string width
|
||||||
((or 'end 'right) 'right)
|
(pcase justify
|
||||||
('center 'center)
|
((or 'end 'right) 'right)
|
||||||
(_ 'left)))))
|
('center 'center)
|
||||||
|
(_ 'left)))))
|
||||||
(ebox--lines-align-vertical
|
(ebox--lines-align-vertical
|
||||||
result height
|
result height
|
||||||
(pcase align
|
(pcase align
|
||||||
@ -464,10 +487,9 @@ Supported properties are `:grid-column', `:grid-row',
|
|||||||
|
|
||||||
(defun ebox-grid--entry-string (entry rendered widths heights col-gap row-gap props)
|
(defun ebox-grid--entry-string (entry rendered widths heights col-gap row-gap props)
|
||||||
"Render and align ENTRY within its grid rectangle."
|
"Render and align ENTRY within its grid rectangle."
|
||||||
(let* ((node (plist-get entry :node))
|
(let* ((width (ebox-grid--entry-size entry widths col-gap))
|
||||||
(source (gethash node rendered))
|
|
||||||
(width (ebox-grid--entry-size entry widths col-gap))
|
|
||||||
(height (ebox-grid--row-entry-size entry heights row-gap))
|
(height (ebox-grid--row-entry-size entry heights row-gap))
|
||||||
|
(source (ebox-grid--entry-source entry rendered width props))
|
||||||
(justify (or (plist-get props :justify-items) 'stretch))
|
(justify (or (plist-get props :justify-items) 'stretch))
|
||||||
(align (or (plist-get props :align-items) 'stretch)))
|
(align (or (plist-get props :align-items) 'stretch)))
|
||||||
(ebox-grid--align source width height justify align)))
|
(ebox-grid--align source width height justify align)))
|
||||||
@ -594,13 +616,18 @@ Supported properties are `:grid-column', `:grid-row',
|
|||||||
(available (ebox--nonnegative-horizontal-size-pixels
|
(available (ebox--nonnegative-horizontal-size-pixels
|
||||||
(plist-get props :width)
|
(plist-get props :width)
|
||||||
(ebox--viewport-pixel-width nil)))
|
(ebox--viewport-pixel-width nil)))
|
||||||
|
(wrapper (plist-get node :box))
|
||||||
(height (ebox--flex-line-value (plist-get props :height) nil))
|
(height (ebox--flex-line-value (plist-get props :height) nil))
|
||||||
(rendered (make-hash-table :test 'eq)))
|
(rendered (make-hash-table :test 'eq)))
|
||||||
|
(when wrapper
|
||||||
|
(setq available (ebox--box-sizing-content-pixel wrapper available)))
|
||||||
(unless (memq flow '(row column))
|
(unless (memq flow '(row column))
|
||||||
(error "ebox-grid: :grid-auto-flow must be `row' or `column'"))
|
(error "ebox-grid: :grid-auto-flow must be `row' or `column'"))
|
||||||
(dolist (entry entries)
|
(dolist (entry entries)
|
||||||
(let ((child (plist-get entry :node)))
|
(let ((child (plist-get entry :node)))
|
||||||
(puthash child (ebox--render-with-cache child) rendered)))
|
(let ((ebox-viewport-width nil)
|
||||||
|
(ebox--intrinsic-layout-measurement t))
|
||||||
|
(puthash child (ebox--render-with-cache child) rendered))))
|
||||||
(let* ((widths (ebox-grid--resolve-sizes columns column-count 'columns
|
(let* ((widths (ebox-grid--resolve-sizes columns column-count 'columns
|
||||||
entries rendered (cdr gaps)
|
entries rendered (cdr gaps)
|
||||||
available auto-columns))
|
available auto-columns))
|
||||||
|
|||||||
@ -37,6 +37,36 @@
|
|||||||
(should (= (length lines) 1))
|
(should (= (length lines) 1))
|
||||||
(should (= (ebox--string-pixel-width (car lines)) 120))))
|
(should (= (ebox--string-pixel-width (car lines)) 120))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-grid-constrains-auto-width-children-to-track-size ()
|
||||||
|
"Auto-width grid children should render within their assigned track."
|
||||||
|
(let* ((ebox-viewport-width 120)
|
||||||
|
(node (ebox-grid
|
||||||
|
:width '(120)
|
||||||
|
:grid-template-columns '((50) (50))
|
||||||
|
:gap '(0 (10))
|
||||||
|
:border "#334155"
|
||||||
|
(ebox-create :content "A\nA-long" :padding '(0 (4)))
|
||||||
|
(ebox-create :content "B\nB-long" :padding '(0 (4)))))
|
||||||
|
(lines (ebox-string-lines (ebox-render node)))
|
||||||
|
(widths (mapcar #'ebox--string-pixel-width lines)))
|
||||||
|
(should (equal widths (make-list (length widths) 120)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-grid-aligns-every-line-before-border-render ()
|
||||||
|
"Grid wrapper borders should stay on one right edge across child lines."
|
||||||
|
(let* ((node (ebox-grid
|
||||||
|
:width '(120)
|
||||||
|
:grid-template-columns '((30) auto 1fr)
|
||||||
|
:grid-template-rows '(2)
|
||||||
|
:gap '(1 (4))
|
||||||
|
:justify-content 'space-between
|
||||||
|
:border "#334155"
|
||||||
|
(ebox-create :content "FIXED\n30 PX")
|
||||||
|
(ebox-create :content "AUTO\nINTRINSIC")
|
||||||
|
(ebox-create :content "1FR\nREMAINDER")))
|
||||||
|
(lines (ebox-string-lines (ebox-render node)))
|
||||||
|
(widths (mapcar #'ebox--string-pixel-width lines)))
|
||||||
|
(should (equal widths (make-list (length widths) 120)))))
|
||||||
|
|
||||||
(ert-deftest ebox-grid-keeps-padded-items-inside-column-width ()
|
(ert-deftest ebox-grid-keeps-padded-items-inside-column-width ()
|
||||||
"Stack padding should use the widest Grid line instead of its first line."
|
"Stack padding should use the widest Grid line instead of its first line."
|
||||||
(let* ((header (ebox-create :content "Header" :width '(720)))
|
(let* ((header (ebox-create :content "Header" :width '(720)))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user