literate: convert org/layout-engine.org from doc-only to tangle source

Now tangles to layout.lisp + layout/tests.lisp.
Deleted hand-written originals and regenerated — GREEN.
This commit is contained in:
Hermes Agent
2026-05-12 17:18:27 +00:00
parent f50d0e61d1
commit d3bc6c748a
3 changed files with 304 additions and 483 deletions

View File

@@ -1,5 +1,3 @@
;;; layout — Pure CL Flexbox layout engine
(defpackage :cl-tty.layout
(:use :cl)
(:export
@@ -15,7 +13,6 @@
#:layout-node-parent #:layout-node-fixed-width
#:layout-node-fixed-height #:normalize-box
#:box-edge))
(in-package :cl-tty.layout)
(defun normalize-box (spec)
@@ -70,14 +67,7 @@
(delete child (layout-node-children parent)))
child)
;; ── Solver ─────────────────────────────────────────────────────
(defun distribute-sizes (children avail gap horizontal)
"Compute child sizes given available space and gap.
HORIZONTAL is non-nil when distributing width (row layout).
Each child starts from its fixed size (if any). Remaining space
is distributed by grow ratio; overflow is reduced by shrink ratio.
Rounding errors are amortized across the first N children."
(let* ((n (length children))
(gap-total (* gap (max 0 (1- n))))
(base (mapcar (lambda (c)
@@ -98,10 +88,6 @@ Rounding errors are amortized across the first N children."
(decf sz (round (* (abs remaining) (/ (layout-node-shrink c) shrink-total)))))
(max 1 sz)))
children base)))
;; Distribute rounding remainder to first N children so that
;; the total of sizes exactly fills avail minus gap-total.
;; Only correct when grow or shrink was actually applied —
;; otherwise children keep their fixed sizes and may not fill space.
(when (or (and (plusp remaining) (plusp grow-total))
(and (minusp remaining) (plusp shrink-total)))
(let ((delta (- avail gap-total (reduce #'+ sizes))))
@@ -111,8 +97,6 @@ Rounding errors are amortized across the first N children."
sizes)))
(defun compute-layout (root available-width available-height)
"Layout all children of ROOT within the given dimensions.
Recursively computes position and size for every node."
(labels ((place-children (node x y max-w max-h)
(let* ((children (layout-node-children node))
(is-row (eql (layout-node-direction node) :row))
@@ -124,10 +108,8 @@ Recursively computes position and size for every node."
(ch (max 0 (- max-h pt pb)))
(gap (layout-node-gap node))
(sizes (distribute-sizes children (if is-row cw ch) gap is-row)))
;; Position the node (content area starts at padding inset)
(setf (layout-node-x node) (+ x pl)
(layout-node-y node) (+ y pt))
;; Place each child sequentially
(loop :with pos = 0
:for child :in children
:for size :in sizes
@@ -146,7 +128,6 @@ Recursively computes position and size for every node."
(if is-row size cw)
(if is-row ch size))
(incf pos (+ size gap)))
;; Compute own size from children
(let ((last-child (car (last children))))
(if is-row
(setf (layout-node-width node)
@@ -170,8 +151,6 @@ Recursively computes position and size for every node."
(place-children root 0 0 available-width available-height)
root))
;; ── Macros ─────────────────────────────────────────────────────
(defmacro vbox ((&key grow shrink padding margin gap width height) &body children)
(let ((n (gensym)))
`(let ((,n (make-layout-node :direction :column

View File

@@ -122,14 +122,12 @@
;; ── Edge Cases ────────────────────────────────────────────────
(test empty-container-does-not-crash
"compute-layout on a node with no children should not error"
(let ((r (make-layout-node)))
(compute-layout r 20 20)
(is (integerp (layout-node-width r)))
(is (integerp (layout-node-height r)))))
(test single-child-in-column
"A column with one child places it correctly"
(let* ((r (make-layout-node :direction :column :width 10 :height 20))
(c (make-layout-node :height 5)))
(layout-node-add-child r c)
@@ -138,7 +136,6 @@
(is (= (layout-node-height c) 5))))
(test zero-size-container
"compute-layout with zero available space should not error"
(let* ((r (make-layout-node :direction :column))
(c (make-layout-node :height 5)))
(layout-node-add-child r c)
@@ -147,17 +144,15 @@
(is (integerp (layout-node-y c)))))
(test deep-nesting-three-levels
"Three-level deep nesting produces correct leaf positions"
(let* ((out (vbox () ; outer box
(vbox (:grow 1) ; middle box
(make-layout-node :height 2)))) ; leaf
(let* ((out (vbox ()
(vbox (:grow 1)
(make-layout-node :height 2))))
(leaf (elt (layout-node-children
(elt (layout-node-children out) 0)) 0)))
(compute-layout out 20 20)
(is (= (layout-node-y leaf) 0))))
(test large-padding-leaves-room
"Large padding reduces content area but doesn't crash"
(let* ((r (make-layout-node :direction :column
:padding '(:top 5 :left 5 :bottom 5 :right 5)))
(c (make-layout-node :height 3)))
@@ -167,7 +162,6 @@
(is (= (layout-node-y c) 5))))
(test negative-grow-is-clamped
"Grow values are adjusted but still compute"
(let* ((r (make-layout-node :direction :row :width 10))
(c (make-layout-node :width 5 :grow -1)))
(layout-node-add-child r c)