Files
cl-tty/org/layout-primitives.org
Amr Gharbeia 56682d0cc2 v0.1.0: Layout primitives + composable API — 23 new tests
Layout primitives (org/layout-primitives.org, ~290 lines):
- CLOS layout-node class wrapping YGNodeRef with tg:finalize for GC cleanup
- 12 setter functions with keyword→integer enum translation
- layout-calculate: runs Yoga layout, returns root
- 14 tests: dimension, direction, flex-grow, align, justify,
  padding, margin, absolute position, wrap, gap, nested layout

Composable API (org/layout-composable.org, ~200 lines):
- vbox/hbox macros: declarative container creation with style props
- overlay macro: absolute-positioned child over relative base
- spacer function: flex-grow filler
- make-props-list helper: extracts plist minus :children
- 6 tests: vbox stacking, hbox layout, spacer flex, overlay position,
  align/justify via vbox, padding offset

GREEN: 29/29 pass (59 assertions)
- yoga-ffi: 9 tests (22 assertions)
- layout-primitives: 14 tests (24 assertions)
- layout-composable: 6 tests (13 assertions)
2026-05-11 08:08:15 -04:00

22 KiB

Layout Primitives

Layout Primitives

CLOS wrappers around the raw Yoga FFI bindings. Each layout-node wraps a YGNodeRef with automatic finalization. Setter functions translate Lisp keywords to Yoga enum integers, providing a safe, idiomatic Common Lisp API.

This file depends on yoga-ffi.lisp (the CFFI bindings). The composable API (vbox, hbox, overlay, spacer) is in layout-composable.org.

Contract

(make-layout-node) → layout-node
allocates a new Yoga node, wraps it in a CLOS instance. The YGNodeRef is freed when the layout-node is garbage collected (via trivial-garbage).
(layout-node-ptr node) → YGNodeRef
returns the raw C pointer for use with raw FFI functions.
(layout-node-add-child parent child)
inserts child at the end of parent's children list. Throws if child is nil.
(layout-node-set-dimension node width height)
sets fixed width and height in points.
(layout-node-set-flex node &key grow shrink basis)
sets flex-grow, flex-shrink, flex-basis. Unspecified keys are left unchanged.
(layout-node-set-direction node direction)
sets flex-direction. direction is one of: :column :column-reverse :row :row-reverse.
(layout-node-set-wrap node wrap)
sets flex-wrap. wrap is one of: :nowrap :wrap :wrap-reverse.
(layout-node-set-align node &key items self content)
sets align-items, align-self, align-content. Values from: :auto, :flex-start, :center, :flex-end, :stretch, :baseline, :space-between, :space-around, :space-evenly.
(layout-node-set-justify node justify)
sets justify-content. Values from: :auto :flex-start :center :flex-end :space-between :space-around :space-evenly.
(layout-node-set-padding node &key all top right bottom left x y)
sets padding in points on specified edges. :all sets all 4 edges.
(layout-node-set-margin node &key all top right bottom left x y)
sets margin in points.
(layout-node-set-gap node &key row column)
sets gap between children.
(layout-node-set-position node type &key top right bottom left)
sets position type (:static :relative :absolute) and offsets.
(layout-node-set-border node width &key top right bottom left all)
sets border width on edges.
(layout-node-set-overflow node overflow)
sets overflow mode. Values: :visible :hidden :scroll.
(layout-node-set-display node display)
sets display mode. Values: :flex :none.
(layout-node-set-aspect-ratio node ratio)
sets aspect ratio.
(layout-calculate root width height &optional (direction :ltr))
runs Yoga's calculateLayout, populating each node's computed x/y/w/h.

Package and Dependencies

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload :cffi :silent t)
  (ql:quickload :trivial-garbage :silent t))

(defpackage :cl-tui.layout-primitives
  (:use :cl)
  (:import-from :cl-tui.yoga-ffi
   #:load-yoga
   #:yg-node-new
   #:yg-node-free
   #:yg-node-insert-child
   #:yg-node-remove-child
   #:yg-node-get-child-count
   #:yg-node-calculate-layout
   #:yg-node-layout-get-left
   #:yg-node-layout-get-top
   #:yg-node-layout-get-width
   #:yg-node-layout-get-height
   #:yg-node-layout-get-right
   #:yg-node-layout-get-bottom
   #:yg-node-style-set-direction
   #:yg-node-style-set-flex-direction
   #:yg-node-style-set-justify-content
   #:yg-node-style-set-align-items
   #:yg-node-style-set-align-self
   #:yg-node-style-set-align-content
   #:yg-node-style-set-flex-wrap
   #:yg-node-style-set-position-type
   #:yg-node-style-set-flex-grow
   #:yg-node-style-set-flex-shrink
   #:yg-node-style-set-flex-basis
   #:yg-node-style-set-flex-basis-auto
   #:yg-node-style-set-overflow
   #:yg-node-style-set-display
   #:yg-node-style-set-width
   #:yg-node-style-set-width-auto
   #:yg-node-style-set-height
   #:yg-node-style-set-height-auto
   #:yg-node-style-set-min-width
   #:yg-node-style-set-min-height
   #:yg-node-style-set-max-width
   #:yg-node-style-set-max-height
   #:yg-node-style-set-aspect-ratio
   #:yg-node-style-set-padding
   #:yg-node-style-set-margin
   #:yg-node-style-set-margin-auto
   #:yg-node-style-set-border
   #:yg-node-style-set-gap
   #:yg-node-style-set-position
   ;; enum constants
   #:+yg-flex-direction-column+
   #:+yg-flex-direction-column-reverse+
   #:+yg-flex-direction-row+
   #:+yg-flex-direction-row-reverse+
   #:+yg-wrap-nowrap+
   #:+yg-wrap-wrap+
   #:+yg-wrap-wrap-reverse+
   #:+yg-justify-auto+
   #:+yg-justify-flex-start+
   #:+yg-justify-center+
   #:+yg-justify-flex-end+
   #:+yg-justify-space-between+
   #:+yg-justify-space-around+
   #:+yg-justify-space-evenly+
   #:+yg-align-auto+
   #:+yg-align-flex-start+
   #:+yg-align-center+
   #:+yg-align-flex-end+
   #:+yg-align-stretch+
   #:+yg-align-baseline+
   #:+yg-align-space-between+
   #:+yg-align-space-around+
   #:+yg-align-space-evenly+
   #:+yg-position-type-static+
   #:+yg-position-type-relative+
   #:+yg-position-type-absolute+
   #:+yg-overflow-visible+
   #:+yg-overflow-hidden+
   #:+yg-overflow-scroll+
   #:+yg-display-flex+
   #:+yg-display-none+
   #:+yg-edge-left+
   #:+yg-edge-top+
   #:+yg-edge-right+
   #:+yg-edge-bottom+
   #:+yg-edge-all+
   #:+yg-edge-start+
   #:+yg-edge-end+
   #:+yg-edge-horizontal+
   #:+yg-edge-vertical+
   #:+yg-gutter-column+
   #:+yg-gutter-row+
   #:+yg-gutter-all+
   #:+yg-direction-inherit+
   #:+yg-direction-ltr+
   #:+yg-direction-rtl+)
  (:export
   #:layout-node
   #:layout-node-ptr
   #:make-layout-node
   #:layout-node-add-child
   #:layout-node-set-dimension
   #:layout-node-set-flex
   #:layout-node-set-direction
   #:layout-node-set-wrap
   #:layout-node-set-align
   #:layout-node-set-justify
   #:layout-node-set-padding
   #:layout-node-set-margin
   #:layout-node-set-gap
   #:layout-node-set-position
   #:layout-node-set-border
   #:layout-node-set-overflow
   #:layout-node-set-display
   #:layout-node-set-aspect-ratio
   #:layout-calculate))

(in-package :cl-tui.layout-primitives)

Enum Translation Tables

;; Keyword → integer translation tables.  Used by setter functions
;; so callers use (:flex-start) instead of (+yg-justify-flex-start+).

(defparameter *flex-direction-map*
  '((:column . 0) (:column-reverse . 1) (:row . 2) (:row-reverse . 3)))

(defparameter *wrap-map*
  '((:nowrap . 0) (:wrap . 1) (:wrap-reverse . 2)))

(defparameter *justify-map*
  '((:auto . 0) (:flex-start . 1) (:center . 2) (:flex-end . 3)
    (:space-between . 4) (:space-around . 5) (:space-evenly . 6)))

(defparameter *align-map*
  '((:auto . 0) (:flex-start . 1) (:center . 2) (:flex-end . 3)
    (:stretch . 4) (:baseline . 5) (:space-between . 6) (:space-around . 7)
    (:space-evenly . 8)))

(defparameter *position-type-map*
  '((:static . 0) (:relative . 1) (:absolute . 2)))

(defparameter *overflow-map*
  '((:visible . 0) (:hidden . 1) (:scroll . 2)))

(defparameter *display-map*
  '((:flex . 0) (:none . 1)))

(defparameter *edge-map*
  '((:left . 0) (:top . 1) (:right . 2) (:bottom . 3)
    (:start . 4) (:end . 5) (:horizontal . 6) (:vertical . 7) (:all . 8)))

(defparameter *direction-map*
  '((:inherit . 0) (:ltr . 1) (:rtl . 2)))

(defun resolve-enum (map keyword)
  "Look up KEYWORD in MAP (an alist). Throws if not found."
  (or (cdr (assoc keyword map))
      (error "Unknown enum keyword ~a" keyword)))

Layout Node Class

(defclass layout-node ()
  ((ptr :initarg :ptr :reader layout-node-ptr
        :documentation "Raw YGNodeRef pointer")))

(defmethod print-object ((node layout-node) stream)
  (print-unreadable-object (node stream :type t)
    (format stream "~a" (layout-node-ptr node))))

(defun make-layout-node ()
  "Allocate a new Yoga node and wrap it in a layout-node."
  (let ((node (make-instance 'layout-node :ptr (yg-node-new))))
    (tg:finalize node (lambda () (yg-node-free (layout-node-ptr node))))
    node))

(defun layout-node-add-child (parent child)
  "Insert CHILD at the end of PARENT's children list."
  (let ((count (yg-node-get-child-count (layout-node-ptr parent))))
    (yg-node-insert-child (layout-node-ptr parent) (layout-node-ptr child) count)))

Dimension Setters

(defun layout-node-set-dimension (node width height)
  "Set fixed width and height in points."
  (yg-node-style-set-width (layout-node-ptr node) (coerce width 'single-float))
  (yg-node-style-set-height (layout-node-ptr node) (coerce height 'single-float)))

(defun layout-node-set-flex (node &key grow shrink basis)
  "Set flex properties. Unspecified keys are left unchanged."
  (let ((p (layout-node-ptr node)))
    (when grow (yg-node-style-set-flex-grow p (coerce grow 'single-float)))
    (when shrink (yg-node-style-set-flex-shrink p (coerce shrink 'single-float)))
    (when basis (yg-node-style-set-flex-basis p (coerce basis 'single-float)))))

(defun layout-node-set-aspect-ratio (node ratio)
  "Set aspect ratio (width/height)."
  (yg-node-style-set-aspect-ratio (layout-node-ptr node) (coerce ratio 'single-float)))

Layout Direction and Wrapping

(defun layout-node-set-direction (node direction)
  "Set flex-direction. DIRECTION is :column, :column-reverse, :row, or :row-reverse."
  (yg-node-style-set-flex-direction
   (layout-node-ptr node)
   (resolve-enum *flex-direction-map* direction)))

(defun layout-node-set-wrap (node wrap)
  "Set flex-wrap. WRAP is :nowrap, :wrap, or :wrap-reverse."
  (yg-node-style-set-flex-wrap
   (layout-node-ptr node)
   (resolve-enum *wrap-map* wrap)))

Alignment and Justification

(defun layout-node-set-align (node &key items self content)
  "Set align-items, align-self, align-content. Values are keywords like :flex-start."
  (let ((p (layout-node-ptr node)))
    (when items (yg-node-style-set-align-items p (resolve-enum *align-map* items)))
    (when self  (yg-node-style-set-align-self  p (resolve-enum *align-map* self)))
    (when content (yg-node-style-set-align-content p (resolve-enum *align-map* content)))))

(defun layout-node-set-justify (node justify)
  "Set justify-content. JUSTIFY is :flex-start, :center, :flex-end, :space-between, etc."
  (yg-node-style-set-justify-content
   (layout-node-ptr node)
   (resolve-enum *justify-map* justify)))

Position Type and Offsets

(defun layout-node-set-position (node type &key top right bottom left)
  "Set position type and offsets. TYPE is :static, :relative, or :absolute."
  (let ((p (layout-node-ptr node)))
    (yg-node-style-set-position-type p (resolve-enum *position-type-map* type))
    (when left   (yg-node-style-set-position p +yg-edge-left+   (coerce left 'single-float)))
    (when top    (yg-node-style-set-position p +yg-edge-top+    (coerce top 'single-float)))
    (when right  (yg-node-style-set-position p +yg-edge-right+  (coerce right 'single-float)))
    (when bottom (yg-node-style-set-position p +yg-edge-bottom+ (coerce bottom 'single-float)))))

Padding, Margin, Border, Gap

(defun set-edges (p fn all top right bottom left x y)
  "Helper: call FN on each specified edge. FN is (fn ptr edge value)."
  (flet ((s (edge val) (funcall fn p edge (coerce val 'single-float))))
    (when all (dolist (e (list +yg-edge-left+ +yg-edge-top+ +yg-edge-right+ +yg-edge-bottom+))
                (s e all)))
    (when top (s +yg-edge-top+ top))
    (when right (s +yg-edge-right+ right))
    (when bottom (s +yg-edge-bottom+ bottom))
    (when left (s +yg-edge-left+ left))
    (when x (s +yg-edge-horizontal+ x))
    (when y (s +yg-edge-vertical+ y))))

(defun layout-node-set-padding (node &key all top right bottom left x y)
  "Set padding on specified edges in points."
  (set-edges (layout-node-ptr node) #'yg-node-style-set-padding all top right bottom left x y))

(defun layout-node-set-margin (node &key all top right bottom left x y)
  "Set margin on specified edges in points."
  (set-edges (layout-node-ptr node) #'yg-node-style-set-margin all top right bottom left x y))

(defun layout-node-set-border (node width &key all top right bottom left x y)
  "Set border width on specified edges."
  (let ((p (layout-node-ptr node)))
    (flet ((s (edge val) (yg-node-style-set-border p edge (coerce val 'single-float))))
      (when all (dolist (e (list +yg-edge-left+ +yg-edge-top+ +yg-edge-right+ +yg-edge-bottom+))
                  (s e all)))
      (when top (s +yg-edge-top+ top))
      (when right (s +yg-edge-right+ right))
      (when bottom (s +yg-edge-bottom+ bottom))
      (when left (s +yg-edge-left+ left))
      (when x (s +yg-edge-horizontal+ x))
      (when y (s +yg-edge-vertical+ y)))))

(defun layout-node-set-gap (node &key row column)
  "Set gap between children."
  (let ((p (layout-node-ptr node)))
    (when row    (yg-node-style-set-gap p +yg-gutter-row+    (coerce row 'single-float)))
    (when column (yg-node-style-set-gap p +yg-gutter-column+ (coerce column 'single-float)))))

Overflow and Display

(defun layout-node-set-overflow (node overflow)
  "Set overflow mode. OVERFLOW is :visible, :hidden, or :scroll."
  (yg-node-style-set-overflow
   (layout-node-ptr node)
   (resolve-enum *overflow-map* overflow)))

(defun layout-node-set-display (node display)
  "Set display mode. DISPLAY is :flex or :none."
  (yg-node-style-set-display
   (layout-node-ptr node)
   (resolve-enum *display-map* display)))

Layout Calculation

(defun layout-calculate (root width height &optional (direction :ltr))
  "Run Yoga layout on the tree rooted at ROOT.
Returns ROOT (for chaining). Each node's computed position is available via
the raw FFI layout getter functions (yg-node-layout-get-left etc.)."
  (yg-node-calculate-layout
   (layout-node-ptr root)
   (coerce width 'single-float)
   (coerce height 'single-float)
   (resolve-enum *direction-map* direction))
  root)

Test Suite

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload :fiveam :silent t))

(defpackage :cl-tui.layout-primitives-tests
  (:use :cl :fiveam)
  (:import-from :cl-tui.layout-primitives
   #:make-layout-node
   #:layout-node-add-child
   #:layout-node-set-dimension
   #:layout-node-set-flex
   #:layout-node-set-direction
   #:layout-node-set-wrap
   #:layout-node-set-align
   #:layout-node-set-justify
   #:layout-node-set-padding
   #:layout-node-set-margin
   #:layout-node-set-gap
   #:layout-node-set-position
   #:layout-node-set-border
   #:layout-node-set-overflow
   #:layout-node-set-display
   #:layout-node-set-aspect-ratio
   #:layout-calculate
   #:layout-node-ptr)
  (:import-from :cl-tui.yoga-ffi
   #:yg-node-layout-get-left
   #:yg-node-layout-get-top
   #:yg-node-layout-get-width
   #:yg-node-layout-get-height))

(in-package :cl-tui.layout-primitives-tests)

(fiveam:def-suite layout-primitives-suite
  :description "Layout primitive CLOS wrappers verification")
(fiveam:in-suite layout-primitives-suite)

(defun node-x (node) (yg-node-layout-get-left   (layout-node-ptr node)))
(defun node-y (node) (yg-node-layout-get-top    (layout-node-ptr node)))
(defun node-w (node) (yg-node-layout-get-width  (layout-node-ptr node)))
(defun node-h (node) (yg-node-layout-get-height (layout-node-ptr node)))

(fiveam:test test-make-layout-node
  "Contract: make-layout-node returns a live node."
  (let ((n (make-layout-node)))
    (fiveam:is (not (cffi:null-pointer-p (layout-node-ptr n))))))

(fiveam:test test-layout-node-add-child
  "Contract: adding a child makes it appear in the tree."
  (let* ((parent (make-layout-node))
         (child  (make-layout-node)))
    (layout-node-add-child parent child)
    (layout-node-set-dimension parent 100 100)
    (layout-node-set-dimension child 50 50)
    (layout-calculate parent 100 100)
    (fiveam:is (= 50.0 (node-w child)))
    (fiveam:is (= 50.0 (node-h child)))))

(fiveam:test test-set-dimension
  "Contract: layout-node-set-dimension sets width and height."
  (let ((n (make-layout-node)))
    (layout-node-set-dimension n 200 100)
    (layout-calculate n 200 100)
    (fiveam:is (= 200.0 (node-w n)))
    (fiveam:is (= 100.0 (node-h n)))))

(fiveam:test test-set-direction-column
  "Contract: column direction stacks children vertically."
  (let* ((root (make-layout-node))
         (a (make-layout-node))
         (b (make-layout-node)))
    (layout-node-set-dimension root 100 200)
    (layout-node-set-dimension a 100 50)
    (layout-node-set-dimension b 100 50)
    (layout-node-add-child root a)
    (layout-node-add-child root b)
    (layout-node-set-direction root :column)
    (layout-calculate root 100 200)
    (fiveam:is (= 0.0 (node-y a)))
    (fiveam:is (= 50.0 (node-y b)))))

(fiveam:test test-set-direction-row
  "Contract: row direction places children horizontally."
  (let* ((root (make-layout-node))
         (a (make-layout-node))
         (b (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension a 80 50)
    (layout-node-set-dimension b 80 50)
    (layout-node-add-child root a)
    (layout-node-add-child root b)
    (layout-node-set-direction root :row)
    (layout-calculate root 200 100)
    (fiveam:is (= 0.0 (node-x a)))
    (fiveam:is (= 80.0 (node-x b)))))

(fiveam:test test-set-flex-grow
  "Contract: flex-grow distributes remaining space."
  (let* ((root (make-layout-node))
         (a (make-layout-node))
         (b (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension a 0 100)
    (layout-node-set-dimension b 0 100)
    (layout-node-set-flex a :grow 1)
    (layout-node-set-flex b :grow 2)
    (layout-node-add-child root a)
    (layout-node-add-child root b)
    (layout-node-set-direction root :row)
    (layout-calculate root 200 100)
    (fiveam:is (< 0.0 (node-w a)))
    (fiveam:is (< 0.0 (node-w b)))
    (fiveam:is (= 200.0 (+ (node-w a) (node-w b))))))

(fiveam:test test-set-align-center
  "Contract: align-items :center centers children on the cross axis."
  (let* ((root (make-layout-node))
         (child (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension child 50 50)
    (layout-node-set-direction root :row)
    (layout-node-add-child root child)
    (layout-node-set-align root :items :center)
    (layout-calculate root 200 100)
    (fiveam:is (= 25.0 (node-y child)))))

(fiveam:test test-set-justify
  "Contract: justify-content :center centers children on the main axis."
  (let* ((root (make-layout-node))
         (child (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension child 50 50)
    (layout-node-set-direction root :row)
    (layout-node-add-child root child)
    (layout-node-set-justify root :center)
    (layout-calculate root 200 100)
    (fiveam:is (= 75.0 (node-x child)))))

(fiveam:test test-set-padding
  "Contract: padding offsets children from the parent edges."
  (let* ((root (make-layout-node))
         (child (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension child 100 50)
    (layout-node-add-child root child)
    (layout-node-set-padding root :all 10)
    (layout-calculate root 200 100)
    (fiveam:is (= 10.0 (node-x child)))
    (fiveam:is (= 10.0 (node-y child)))))

(fiveam:test test-set-margin
  "Contract: margin offsets the child from its siblings/parent."
  (let* ((root (make-layout-node))
         (child (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension child 80 50)
    (layout-node-add-child root child)
    (layout-node-set-margin child :left 20)
    (layout-calculate root 200 100)
    (fiveam:is (= 20.0 (node-x child)))))

(fiveam:test test-set-position-absolute
  "Contract: absolute positioning places a child at exact coordinates."
  (let* ((root (make-layout-node))
         (child (make-layout-node)))
    (layout-node-set-dimension root 300 300)
    (layout-node-set-dimension child 50 50)
    (layout-node-add-child root child)
    (layout-node-set-position child :absolute :left 100 :top 50)
    (layout-calculate root 300 300)
    (fiveam:is (= 100.0 (node-x child)))
    (fiveam:is (= 50.0 (node-y child)))))

(fiveam:test test-set-wrap
  "Contract: flex-wrap :wrap allows children to wrap to next line."
  (let* ((root (make-layout-node))
         (a (make-layout-node))
         (b (make-layout-node))
         (c (make-layout-node)))
    (layout-node-set-dimension root 100 200)
    (layout-node-set-dimension a 60 50)
    (layout-node-set-dimension b 60 50)
    (layout-node-set-dimension c 60 50)
    (layout-node-add-child root a)
    (layout-node-add-child root b)
    (layout-node-add-child root c)
    (layout-node-set-direction root :row)
    (layout-node-set-wrap root :wrap)
    (layout-calculate root 100 200)
    (fiveam:is (< 0 (node-h a)))
    ;; Second child (b) should wrap to next row since 60+60 > 100
    (fiveam:is (> (node-y b) (node-y a)))))

(fiveam:test test-set-gap
  "Contract: gap adds spacing between children."
  (let* ((root (make-layout-node))
         (a (make-layout-node))
         (b (make-layout-node)))
    (layout-node-set-dimension root 200 100)
    (layout-node-set-dimension a 50 50)
    (layout-node-set-dimension b 50 50)
    (layout-node-add-child root a)
    (layout-node-add-child root b)
    (layout-node-set-direction root :column)
    (layout-node-set-gap root :row 20)
    (layout-calculate root 200 100)
    (fiveam:is (= 70.0 (node-y b)))))

(fiveam:test test-nested-layout
  "Contract: nested containers produce correct leaf positions."
  (let* ((root (make-layout-node))
         (outer (make-layout-node))
         (inner (make-layout-node)))
    (layout-node-set-dimension root 400 400)
    (layout-node-set-dimension outer 400 200)
    (layout-node-set-dimension inner 100 100)
    (layout-node-add-child outer inner)
    (layout-node-add-child root outer)
    (layout-node-set-direction root :column)
    (layout-calculate root 400 400)
    (fiveam:is (= 0.0 (node-x inner)))
    (fiveam:is (= 100.0 (node-w inner)))
    (fiveam:is (= 100.0 (node-h inner)))))