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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user