Files
cl-tty/org/yoga-ffi.org
Amr Gharbeia f135b56a1a v0.1.0: Yoga FFI binding — CFFI types, 50+ bound functions, 9 tests
- Load Yoga shared library via CFFI (/usr/local/lib/libyoga.so)
- 15 enum constant sets (YGDirection through YGUnit)
- Foreign type definitions: yg-node-ref, yg-size, yg-value
- Core node management: new, free, insert-child, remove-child, get-child-count
- Layout: calculate-layout, get-left/top/width/height
- Style setters: direction, flex-direction, justify-content, align-items,
  align-self, flex-wrap, position-type, flex-grow/shrink/basis, overflow,
  display, width/height, min/max dimensions, padding, margin, border, gap,
  position (point and percent)
- Style getters: width, height, flex-direction, align-items, justify-content
- Disable FP traps for SBCL (Yoga uses NaN for YGUndefined)

GREEN: 9/9 pass (22 assertions)
- test-node-create-free, test-node-child-count, test-node-insert-child
- test-layout-basic-column, test-layout-basic-row
- test-layout-flex-grow, test-layout-absolute-position
- test-layout-padding, test-layout-nested
2026-05-11 07:18:53 -04:00

25 KiB

Yoga FFI Binding

Yoga FFI Binding

CFFI bindings for Facebook's Yoga Flexbox layout engine. Provides raw access to the C library — node management, style setters, layout calculation, and layout getters. The next file (layout-primitives.org) wraps these in CLOS.

The Yoga shared library is at /usr/local/lib/libyoga.so, built from facebook/yoga with C++17.

Contract

(load-yoga)
loads the shared library via CFFI. Signal an error if the library cannot be found.
(yg-node-new) → YGNodeRef
wraps YGNodeNew. Allocates a new Yoga node.
(yg-node-free node)
wraps YGNodeFree. Frees a node and detaches it from its owner and children.
(yg-node-free-recursive node)
wraps YGNodeFreeRecursive. Frees the entire subtree.
(yg-node-insert-child node child index)
wraps YGNodeInsertChild. Inserts a child at the given index.
(yg-node-remove-child node child)
wraps YGNodeRemoveChild.
(yg-node-get-child-count node) → integer
wraps YGNodeGetChildCount.
(yg-node-calculate-layout node width height direction)
wraps YGNodeCalculateLayout. Runs the layout algorithm.
(yg-node-layout-get-left node) → float
wraps YGNodeLayoutGetLeft. Returns the computed X position.
(yg-node-layout-get-top node) → float
wraps YGNodeLayoutGetTop. Returns the computed Y position.
(yg-node-layout-get-width node) → float
wraps YGNodeLayoutGetWidth. Returns the computed width.
(yg-node-layout-get-height node) → float
wraps YGNodeLayoutGetHeight. Returns the computed height.
(yg-node-style-set-direction node dir)
sets the text direction.
(yg-node-style-set-flex-direction node dir)
sets the flex direction.
(yg-node-style-set-justify-content node justify)
sets main-axis alignment.
(yg-node-style-set-align-items node align)
sets cross-axis alignment.
(yg-node-style-set-align-self node align)
sets self alignment.
(yg-node-style-set-flex-wrap node wrap)
sets wrapping mode.
(yg-node-style-set-flex-grow node value)
sets the flex grow factor.
(yg-node-style-set-flex-shrink node value)
sets the flex shrink factor.
(yg-node-style-set-position-type node type)
sets positioning type.
(yg-node-style-set-width node points)
sets width in points.
(yg-node-style-set-width-percent node pct)
sets width as percentage.
(yg-node-style-set-width-auto node)
sets width to auto.
(yg-node-style-set-height node points)
sets height in points.
(yg-node-style-set-height-percent node pct)
sets height as percentage.
(yg-node-style-set-height-auto node)
sets height to auto.
(yg-node-style-set-padding node edge points)
sets padding on an edge.
(yg-node-style-set-margin node edge points)
sets margin on an edge.
(yg-node-style-set-border node edge points)
sets border on an edge.
(yg-node-style-set-gap node gutter length)
sets gap between children.
(yg-node-style-set-overflow node overflow)
sets overflow mode.
(yg-node-style-set-display node display)
sets display mode.
(yg-node-style-set-position-percent node edge pct)
sets position as percentage.
(yg-node-style-set-position node edge points)
sets position in points.

Enum keywords (mapping C enum → Lisp keyword):

  • YGDirection: :inherit (0), :ltr (1), :rtl (2)
  • YGFlexDirection: :column (0), :column-reverse (1), :row (2), :row-reverse (3)
  • YGJustify: :auto (0), :flex-start (1), :center (2), :flex-end (3), :space-between (4), :space-around (5), :space-evenly (6), :stretch (7)
  • YGAlign: :auto (0), :flex-start (1), :center (2), :flex-end (3), :stretch (4), :baseline (5), :space-between (6), :space-around (7), :space-evenly (8)
  • YGWrap: :nowrap (0), :wrap (1), :wrap-reverse (2)
  • YGPositionType: :static (0), :relative (1), :absolute (2)
  • YGOverflow: :visible (0), :hidden (1), :scroll (2)
  • YGDisplay: :flex (0), :none (1), :contents (2)
  • YGEdge: :left (0), :top (1), :right (2), :bottom (3), :start (4), :end (5), :horizontal (6), :vertical (7), :all (8)
  • YGGutter: :column (0), :row (1), :all (2)
  • YGUnit: :undefined (0), :point (1), :percent (2), :auto (3)

Application-Level Package

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

(defpackage :cl-tui.yoga-ffi
  (:use :cl)
  (:export
   ;; library
   #:*libyoga*
   #:load-yoga
   ;; constants
   #:+yg-direction-inherit+
   #:+yg-direction-ltr+
   #:+yg-direction-rtl+
   #:+yg-flex-direction-column+
   #:+yg-flex-direction-column-reverse+
   #:+yg-flex-direction-row+
   #:+yg-flex-direction-row-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-justify-stretch+
   #:+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-wrap-nowrap+
   #:+yg-wrap-wrap+
   #:+yg-wrap-wrap-reverse+
   #:+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-display-contents+
   #:+yg-edge-left+
   #:+yg-edge-top+
   #:+yg-edge-right+
   #:+yg-edge-bottom+
   #:+yg-edge-start+
   #:+yg-edge-end+
   #:+yg-edge-horizontal+
   #:+yg-edge-vertical+
   #:+yg-edge-all+
   #:+yg-gutter-column+
   #:+yg-gutter-row+
   #:+yg-gutter-all+
   #:+yg-unit-undefined+
   #:+yg-unit-point+
   #:+yg-unit-percent+
   #:+yg-unit-auto+
   ;; types
   #:yg-node-ref
   #:yg-node-const-ref
   ;; node management
   #:yg-node-new
   #:yg-node-free
   #:yg-node-free-recursive
   #:yg-node-insert-child
   #:yg-node-remove-child
   #:yg-node-remove-all-children
   #:yg-node-get-child-count
   #:yg-node-get-child
   ;; layout
   #: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-is-dirty
   #:yg-node-mark-dirty
   ;; style direction & flex
   #: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-flex-basis-percent
   #:yg-node-style-set-overflow
   #:yg-node-style-set-display
   ;; style dimensions
   #:yg-node-style-set-width
   #:yg-node-style-set-width-percent
   #:yg-node-style-set-width-auto
   #:yg-node-style-set-height
   #:yg-node-style-set-height-percent
   #:yg-node-style-set-height-auto
   #:yg-node-style-set-min-width
   #:yg-node-style-set-min-width-percent
   #:yg-node-style-set-min-height
   #:yg-node-style-set-min-height-percent
   #:yg-node-style-set-max-width
   #:yg-node-style-set-max-height
   #:yg-node-style-set-aspect-ratio
   ;; style padding/margin/border/gap/position
   #:yg-node-style-set-padding
   #:yg-node-style-set-padding-percent
   #:yg-node-style-set-margin
   #:yg-node-style-set-margin-percent
   #:yg-node-style-set-margin-auto
   #:yg-node-style-set-border
   #:yg-node-style-set-gap
   #:yg-node-style-set-position
   #:yg-node-style-set-position-percent
   ;; style getters
   #:yg-node-style-get-width
   #:yg-node-style-get-height
   #:yg-node-style-get-flex-direction
   #:yg-node-style-get-align-items
   #:yg-node-style-get-justify-content))

(in-package :cl-tui.yoga-ffi)

Foreign Library Loading

(defparameter *libyoga* nil "Handle for the loaded Yoga shared library.")

(defun load-yoga ()
  "Load the Yoga shared library via CFFI."
  (setf *libyoga* (cffi:load-foreign-library "/usr/local/lib/libyoga.so"))
  (sb-int:set-floating-point-modes :traps '())
  *libyoga*)

(load-yoga)

Enum Constants

(eval-when (:compile-toplevel :load-toplevel :execute)
  ;; YGDirection
  (defconstant +yg-direction-inherit+ 0)
  (defconstant +yg-direction-ltr+ 1)
  (defconstant +yg-direction-rtl+ 2)
  ;; YGFlexDirection
  (defconstant +yg-flex-direction-column+ 0)
  (defconstant +yg-flex-direction-column-reverse+ 1)
  (defconstant +yg-flex-direction-row+ 2)
  (defconstant +yg-flex-direction-row-reverse+ 3)
  ;; YGJustify
  (defconstant +yg-justify-auto+ 0)
  (defconstant +yg-justify-flex-start+ 1)
  (defconstant +yg-justify-center+ 2)
  (defconstant +yg-justify-flex-end+ 3)
  (defconstant +yg-justify-space-between+ 4)
  (defconstant +yg-justify-space-around+ 5)
  (defconstant +yg-justify-space-evenly+ 6)
  (defconstant +yg-justify-stretch+ 7)
  ;; YGAlign
  (defconstant +yg-align-auto+ 0)
  (defconstant +yg-align-flex-start+ 1)
  (defconstant +yg-align-center+ 2)
  (defconstant +yg-align-flex-end+ 3)
  (defconstant +yg-align-stretch+ 4)
  (defconstant +yg-align-baseline+ 5)
  (defconstant +yg-align-space-between+ 6)
  (defconstant +yg-align-space-around+ 7)
  (defconstant +yg-align-space-evenly+ 8)
  ;; YGWrap
  (defconstant +yg-wrap-nowrap+ 0)
  (defconstant +yg-wrap-wrap+ 1)
  (defconstant +yg-wrap-wrap-reverse+ 2)
  ;; YGPositionType
  (defconstant +yg-position-type-static+ 0)
  (defconstant +yg-position-type-relative+ 1)
  (defconstant +yg-position-type-absolute+ 2)
  ;; YGOverflow
  (defconstant +yg-overflow-visible+ 0)
  (defconstant +yg-overflow-hidden+ 1)
  (defconstant +yg-overflow-scroll+ 2)
  ;; YGDisplay
  (defconstant +yg-display-flex+ 0)
  (defconstant +yg-display-none+ 1)
  (defconstant +yg-display-contents+ 2)
  ;; YGEdge
  (defconstant +yg-edge-left+ 0)
  (defconstant +yg-edge-top+ 1)
  (defconstant +yg-edge-right+ 2)
  (defconstant +yg-edge-bottom+ 3)
  (defconstant +yg-edge-start+ 4)
  (defconstant +yg-edge-end+ 5)
  (defconstant +yg-edge-horizontal+ 6)
  (defconstant +yg-edge-vertical+ 7)
  (defconstant +yg-edge-all+ 8)
  ;; YGGutter
  (defconstant +yg-gutter-column+ 0)
  (defconstant +yg-gutter-row+ 1)
  (defconstant +yg-gutter-all+ 2)
  ;; YGUnit
  (defconstant +yg-unit-undefined+ 0)
  (defconstant +yg-unit-point+ 1)
  (defconstant +yg-unit-percent+ 2)
  (defconstant +yg-unit-auto+ 3))

CFFI Foreign Type Definitions

(cffi:defctype yg-node-ref :pointer)
(cffi:defctype yg-node-const-ref :pointer)

(cffi:defcstruct yg-size
  (width :float)
  (height :float))

(cffi:defcstruct yg-value
  (value :float)
  (unit :int))

Node Management

(cffi:defcfun ("YGNodeNew" yg-node-new) yg-node-ref)

(cffi:defcfun ("YGNodeFree" yg-node-free) :void
  (node yg-node-ref))

(cffi:defcfun ("YGNodeFreeRecursive" yg-node-free-recursive) :void
  (node yg-node-ref))

(cffi:defcfun ("YGNodeInsertChild" yg-node-insert-child) :void
  (node yg-node-ref)
  (child yg-node-ref)
  (index :unsigned-int))

(cffi:defcfun ("YGNodeRemoveChild" yg-node-remove-child) :void
  (node yg-node-ref)
  (child yg-node-ref))

(cffi:defcfun ("YGNodeRemoveAllChildren" yg-node-remove-all-children) :void
  (node yg-node-ref))

(cffi:defcfun ("YGNodeGetChildCount" yg-node-get-child-count) :unsigned-int
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeGetChild" yg-node-get-child) yg-node-ref
  (node yg-node-ref)
  (index :unsigned-int))

Layout Calculation

(cffi:defcfun ("YGNodeCalculateLayout" yg-node-calculate-layout) :void
  (node yg-node-ref)
  (available-width :float)
  (available-height :float)
  (owner-direction :int))

(cffi:defcfun ("YGNodeLayoutGetLeft" yg-node-layout-get-left) :float
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeLayoutGetTop" yg-node-layout-get-top) :float
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeLayoutGetWidth" yg-node-layout-get-width) :float
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeLayoutGetHeight" yg-node-layout-get-height) :float
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeLayoutGetRight" yg-node-layout-get-right) :float
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeLayoutGetBottom" yg-node-layout-get-bottom) :float
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeIsDirty" yg-node-is-dirty) :boolean
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeMarkDirty" yg-node-mark-dirty) :void
  (node yg-node-ref))

Style Setters — Direction and Flex

(cffi:defcfun ("YGNodeStyleSetDirection" yg-node-style-set-direction) :void
  (node yg-node-ref)
  (direction :int))

(cffi:defcfun ("YGNodeStyleSetFlexDirection" yg-node-style-set-flex-direction) :void
  (node yg-node-ref)
  (flex-direction :int))

(cffi:defcfun ("YGNodeStyleSetJustifyContent" yg-node-style-set-justify-content) :void
  (node yg-node-ref)
  (justify :int))

(cffi:defcfun ("YGNodeStyleSetAlignItems" yg-node-style-set-align-items) :void
  (node yg-node-ref)
  (align :int))

(cffi:defcfun ("YGNodeStyleSetAlignSelf" yg-node-style-set-align-self) :void
  (node yg-node-ref)
  (align :int))

(cffi:defcfun ("YGNodeStyleSetAlignContent" yg-node-style-set-align-content) :void
  (node yg-node-ref)
  (align :int))

(cffi:defcfun ("YGNodeStyleSetFlexWrap" yg-node-style-set-flex-wrap) :void
  (node yg-node-ref)
  (wrap :int))

(cffi:defcfun ("YGNodeStyleSetPositionType" yg-node-style-set-position-type) :void
  (node yg-node-ref)
  (position-type :int))

(cffi:defcfun ("YGNodeStyleSetFlexGrow" yg-node-style-set-flex-grow) :void
  (node yg-node-ref)
  (flex-grow :float))

(cffi:defcfun ("YGNodeStyleSetFlexShrink" yg-node-style-set-flex-shrink) :void
  (node yg-node-ref)
  (flex-shrink :float))

(cffi:defcfun ("YGNodeStyleSetFlexBasis" yg-node-style-set-flex-basis) :void
  (node yg-node-ref)
  (flex-basis :float))

(cffi:defcfun ("YGNodeStyleSetFlexBasisAuto" yg-node-style-set-flex-basis-auto) :void
  (node yg-node-ref))

(cffi:defcfun ("YGNodeStyleSetFlexBasisPercent" yg-node-style-set-flex-basis-percent) :void
  (node yg-node-ref)
  (flex-basis :float))

(cffi:defcfun ("YGNodeStyleSetOverflow" yg-node-style-set-overflow) :void
  (node yg-node-ref)
  (overflow :int))

(cffi:defcfun ("YGNodeStyleSetDisplay" yg-node-style-set-display) :void
  (node yg-node-ref)
  (display :int))

Style Setters — Dimensions

(cffi:defcfun ("YGNodeStyleSetWidth" yg-node-style-set-width) :void
  (node yg-node-ref)
  (width :float))

(cffi:defcfun ("YGNodeStyleSetWidthPercent" yg-node-style-set-width-percent) :void
  (node yg-node-ref)
  (width :float))

(cffi:defcfun ("YGNodeStyleSetWidthAuto" yg-node-style-set-width-auto) :void
  (node yg-node-ref))

(cffi:defcfun ("YGNodeStyleSetHeight" yg-node-style-set-height) :void
  (node yg-node-ref)
  (height :float))

(cffi:defcfun ("YGNodeStyleSetHeightPercent" yg-node-style-set-height-percent) :void
  (node yg-node-ref)
  (height :float))

(cffi:defcfun ("YGNodeStyleSetHeightAuto" yg-node-style-set-height-auto) :void
  (node yg-node-ref))

(cffi:defcfun ("YGNodeStyleSetMinWidth" yg-node-style-set-min-width) :void
  (node yg-node-ref)
  (min-width :float))

(cffi:defcfun ("YGNodeStyleSetMinWidthPercent" yg-node-style-set-min-width-percent) :void
  (node yg-node-ref)
  (min-width :float))

(cffi:defcfun ("YGNodeStyleSetMinHeight" yg-node-style-set-min-height) :void
  (node yg-node-ref)
  (min-height :float))

(cffi:defcfun ("YGNodeStyleSetMinHeightPercent" yg-node-style-set-min-height-percent) :void
  (node yg-node-ref)
  (min-height :float))

(cffi:defcfun ("YGNodeStyleSetMaxWidth" yg-node-style-set-max-width) :void
  (node yg-node-ref)
  (max-width :float))

(cffi:defcfun ("YGNodeStyleSetMaxHeight" yg-node-style-set-max-height) :void
  (node yg-node-ref)
  (max-height :float))

(cffi:defcfun ("YGNodeStyleSetAspectRatio" yg-node-style-set-aspect-ratio) :void
  (node yg-node-ref)
  (aspect-ratio :float))

Style Setters — Padding, Margin, Border, Gap, Position

(cffi:defcfun ("YGNodeStyleSetPadding" yg-node-style-set-padding) :void
  (node yg-node-ref)
  (edge :int)
  (padding :float))

(cffi:defcfun ("YGNodeStyleSetPaddingPercent" yg-node-style-set-padding-percent) :void
  (node yg-node-ref)
  (edge :int)
  (padding :float))

(cffi:defcfun ("YGNodeStyleSetMargin" yg-node-style-set-margin) :void
  (node yg-node-ref)
  (edge :int)
  (margin :float))

(cffi:defcfun ("YGNodeStyleSetMarginPercent" yg-node-style-set-margin-percent) :void
  (node yg-node-ref)
  (edge :int)
  (margin :float))

(cffi:defcfun ("YGNodeStyleSetMarginAuto" yg-node-style-set-margin-auto) :void
  (node yg-node-ref)
  (edge :int))

(cffi:defcfun ("YGNodeStyleSetBorder" yg-node-style-set-border) :void
  (node yg-node-ref)
  (edge :int)
  (border :float))

(cffi:defcfun ("YGNodeStyleSetGap" yg-node-style-set-gap) :void
  (node yg-node-ref)
  (gutter :int)
  (gap :float))

(cffi:defcfun ("YGNodeStyleSetPosition" yg-node-style-set-position) :void
  (node yg-node-ref)
  (edge :int)
  (position :float))

(cffi:defcfun ("YGNodeStyleSetPositionPercent" yg-node-style-set-position-percent) :void
  (node yg-node-ref)
  (edge :int)
  (position :float))

Style Getters

(cffi:defcfun ("YGNodeStyleGetWidth" yg-node-style-get-width) yg-value
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeStyleGetHeight" yg-node-style-get-height) yg-value
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeStyleGetFlexDirection" yg-node-style-get-flex-direction) :int
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeStyleGetAlignItems" yg-node-style-get-align-items) :int
  (node yg-node-const-ref))

(cffi:defcfun ("YGNodeStyleGetJustifyContent" yg-node-style-get-justify-content) :int
  (node yg-node-const-ref))

Test Suite

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

(defpackage :cl-tui.yoga-ffi-tests
  (:use :cl :fiveam)
  (:import-from :cl-tui.yoga-ffi
   #:load-yoga
   #:yg-node-new
   #:yg-node-free
   #:yg-node-free-recursive
   #:yg-node-insert-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-style-set-flex-direction
   #:yg-node-style-set-justify-content
   #:yg-node-style-set-align-items
   #:yg-node-style-set-width
   #:yg-node-style-set-height
   #:yg-node-style-set-padding
   #:yg-node-style-set-margin
   #:yg-node-style-set-flex-grow
   #:yg-node-style-set-position-type
   #:yg-node-style-set-position
   #:+yg-flex-direction-column+
   #:+yg-flex-direction-row+
   #:+yg-justify-center+
   #:+yg-justify-flex-start+
   #:+yg-align-stretch+
   #:+yg-align-center+
   #:+yg-edge-all+
   #:+yg-edge-left+
   #:+yg-edge-top+
   #:+yg-edge-right+
   #:+yg-edge-bottom+
   #:+yg-position-type-relative+
   #:+yg-position-type-absolute+
   ;; YGDirection constants (different from YGFlexDirection!)
   #:+yg-direction-inherit+
   #:+yg-direction-ltr+
   #:+yg-direction-rtl+))

(in-package :cl-tui.yoga-ffi-tests)

(fiveam:def-suite yoga-ffi-suite
  :description "Yoga FFI binding verification")
(fiveam:in-suite yoga-ffi-suite)

(fiveam:test test-node-create-free
  "Contract: yg-node-new returns a non-null pointer; yg-node-free doesn't crash."
  (let ((node (yg-node-new)))
    (fiveam:is (not (cffi:null-pointer-p node)))
    (yg-node-free node)
    (fiveam:pass)))

(fiveam:test test-node-child-count
  "Contract: yg-node-get-child-count returns 0 for a new node."
  (let ((node (yg-node-new)))
    (fiveam:is (= 0 (yg-node-get-child-count node)))
    (yg-node-free node)))

(fiveam:test test-node-insert-child
  "Contract: inserting a child increments the child count."
  (let ((parent (yg-node-new))
        (child (yg-node-new)))
    (yg-node-insert-child parent child 0)
    (fiveam:is (= 1 (yg-node-get-child-count parent)))
    (yg-node-free-recursive parent)))

(fiveam:test test-layout-basic-column
  "Contract: a column with two fixed-height children positions them vertically."
  (let* ((root (yg-node-new)))
    (yg-node-style-set-width root 100.0)
    (yg-node-style-set-height root 200.0)
    (yg-node-style-set-flex-direction root +yg-flex-direction-column+)
    (let ((child1 (yg-node-new))
          (child2 (yg-node-new)))
      (yg-node-style-set-width child1 100.0)
      (yg-node-style-set-height child1 50.0)
      (yg-node-style-set-width child2 100.0)
      (yg-node-style-set-height child2 50.0)
      (yg-node-insert-child root child1 0)
      (yg-node-insert-child root child2 1)
      (yg-node-calculate-layout root 100.0 200.0 +yg-direction-ltr+)
      (fiveam:is (= 0.0 (yg-node-layout-get-left child1)))
      (fiveam:is (= 0.0 (yg-node-layout-get-top child1)))
      (fiveam:is (= 50.0 (yg-node-layout-get-top child2)))
      (yg-node-free-recursive root))))

(fiveam:test test-layout-basic-row
  "Contract: a row with two fixed-width children positions them horizontally."
  (let* ((root (yg-node-new)))
    (yg-node-style-set-width root 200.0)
    (yg-node-style-set-height root 100.0)
    (yg-node-style-set-flex-direction root +yg-flex-direction-row+)
    (let ((child1 (yg-node-new))
          (child2 (yg-node-new)))
      (yg-node-style-set-width child1 80.0)
      (yg-node-style-set-height child1 50.0)
      (yg-node-style-set-width child2 80.0)
      (yg-node-style-set-height child2 50.0)
      (yg-node-insert-child root child1 0)
      (yg-node-insert-child root child2 1)
      (yg-node-calculate-layout root 200.0 100.0 +yg-direction-ltr+)
      (fiveam:is (= 0.0 (yg-node-layout-get-left child1)))
      (fiveam:is (= 0.0 (yg-node-layout-get-top child1)))
      (fiveam:is (= 80.0 (yg-node-layout-get-left child2)))
      (yg-node-free-recursive root))))

(fiveam:test test-layout-flex-grow
  "Contract: flex-grow distributes remaining space proportionally."
  (let* ((root (yg-node-new)))
    (yg-node-style-set-width root 200.0)
    (yg-node-style-set-height root 100.0)
    (yg-node-style-set-flex-direction root +yg-flex-direction-row+)
    (let ((child1 (yg-node-new))
          (child2 (yg-node-new)))
      (yg-node-style-set-height child1 100.0)
      (yg-node-style-set-flex-grow child1 1.0)
      (yg-node-style-set-height child2 100.0)
      (yg-node-style-set-flex-grow child2 2.0)
      (yg-node-insert-child root child1 0)
      (yg-node-insert-child root child2 1)
      (yg-node-calculate-layout root 200.0 100.0 +yg-direction-ltr+)
      (fiveam:is (< 0.0 (yg-node-layout-get-width child1)))
      (fiveam:is (< 0.0 (yg-node-layout-get-width child2)))
      (fiveam:is (= 200.0 (+ (yg-node-layout-get-width child1)
                             (yg-node-layout-get-width child2))))
      (yg-node-free-recursive root))))

(fiveam:test test-layout-absolute-position
  "Contract: an absolute child positions relative to its parent."
  (let* ((root (yg-node-new)))
    (yg-node-style-set-width root 300.0)
    (yg-node-style-set-height root 300.0)
    (let ((child (yg-node-new)))
      (yg-node-style-set-width child 50.0)
      (yg-node-style-set-height child 50.0)
      (yg-node-style-set-position-type child +yg-position-type-absolute+)
      (yg-node-style-set-position child +yg-edge-left+ 100.0)
      (yg-node-style-set-position child +yg-edge-top+ 50.0)
      (yg-node-insert-child root child 0)
      (yg-node-calculate-layout root 300.0 300.0 +yg-direction-ltr+)
      (fiveam:is (= 100.0 (yg-node-layout-get-left child)))
      (fiveam:is (= 50.0 (yg-node-layout-get-top child)))
      (fiveam:is (= 50.0 (yg-node-layout-get-width child)))
      (fiveam:is (= 50.0 (yg-node-layout-get-height child)))
      (yg-node-free-recursive root))))

(fiveam:test test-layout-padding
  "Contract: padding reduces the available space for children."
  (let* ((root (yg-node-new)))
    (yg-node-style-set-width root 200.0)
    (yg-node-style-set-height root 100.0)
    (yg-node-style-set-padding root +yg-edge-all+ 10.0)
    (let ((child (yg-node-new)))
      (yg-node-style-set-width child 180.0)
      (yg-node-style-set-height child 80.0)
      (yg-node-insert-child root child 0)
      (yg-node-calculate-layout root 200.0 100.0 +yg-direction-ltr+)
      (fiveam:is (= 10.0 (yg-node-layout-get-left child)))
      (fiveam:is (= 10.0 (yg-node-layout-get-top child)))
      (yg-node-free-recursive root))))

(fiveam:test test-layout-nested
  "Contract: nested containers produce correct leaf positions."
  (let* ((root (yg-node-new)))
    (yg-node-style-set-width root 400.0)
    (yg-node-style-set-height root 400.0)
    (yg-node-style-set-flex-direction root +yg-flex-direction-column+)
    (let ((outer (yg-node-new)))
      (yg-node-style-set-width outer 400.0)
      (yg-node-style-set-height outer 200.0)
      (yg-node-style-set-flex-direction outer +yg-flex-direction-row+)
      (let ((inner (yg-node-new)))
        (yg-node-style-set-width inner 100.0)
        (yg-node-style-set-height inner 100.0)
        (yg-node-insert-child outer inner 0)
        (yg-node-insert-child root outer 0)
        (yg-node-calculate-layout root 400.0 400.0 +yg-direction-ltr+)
        (fiveam:is (= 0.0 (yg-node-layout-get-left inner)))
        (fiveam:is (= 100.0 (yg-node-layout-get-width inner)))
        (fiveam:is (= 100.0 (yg-node-layout-get-height inner)))
        (yg-node-free-recursive root)))))