From f135b56a1a49a2b2a7d751d59df53ac123499e28 Mon Sep 17 00:00:00 2001 From: Amr Gharbeia Date: Mon, 11 May 2026 07:18:53 -0400 Subject: [PATCH] =?UTF-8?q?v0.1.0:=20Yoga=20FFI=20binding=20=E2=80=94=20CF?= =?UTF-8?q?FI=20types,=2050+=20bound=20functions,=209=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- cl-tui.asd | 9 + lisp/yoga-ffi.lisp | 631 +++++++++++++++++++++++++++++++++++++ org/yoga-ffi.org | 755 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1395 insertions(+) create mode 100644 cl-tui.asd create mode 100644 lisp/yoga-ffi.lisp create mode 100644 org/yoga-ffi.org diff --git a/cl-tui.asd b/cl-tui.asd new file mode 100644 index 0000000..3484e26 --- /dev/null +++ b/cl-tui.asd @@ -0,0 +1,9 @@ +(defsystem :cl-tui + :name "cl-tui" + :author "memex" + :version "0.1.0" + :license "AGPLv3" + :description "Reusable Common Lisp Terminal UI Framework" + :depends-on (:cffi :croatoan) + :serial t + :components ((:file "lisp/yoga-ffi"))) diff --git a/lisp/yoga-ffi.lisp b/lisp/yoga-ffi.lisp new file mode 100644 index 0000000..a683413 --- /dev/null +++ b/lisp/yoga-ffi.lisp @@ -0,0 +1,631 @@ +(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) + +(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) + +(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: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)) + +(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)) + +(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)) + +(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)) + +(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)) + +(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)) + +(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)) + +(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))))) diff --git a/org/yoga-ffi.org b/org/yoga-ffi.org new file mode 100644 index 0000000..62208c9 --- /dev/null +++ b/org/yoga-ffi.org @@ -0,0 +1,755 @@ +#+TITLE: Yoga FFI Binding +#+STARTUP: content +#+FILETAGS: :cl-tui:yoga-ffi:v010: + +* 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 + +#+begin_src lisp +(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) +#+end_src + + +* Foreign Library Loading + +#+begin_src lisp +(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) +#+end_src + +* Enum Constants + +#+begin_src lisp +(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)) +#+end_src + +* CFFI Foreign Type Definitions + +#+begin_src lisp +(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)) +#+end_src + +* Node Management + +#+begin_src lisp +(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)) +#+end_src + +* Layout Calculation + +#+begin_src lisp +(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)) +#+end_src + +* Style Setters — Direction and Flex + +#+begin_src lisp +(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)) +#+end_src + +* Style Setters — Dimensions + +#+begin_src lisp +(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)) +#+end_src + +* Style Setters — Padding, Margin, Border, Gap, Position + +#+begin_src lisp +(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)) +#+end_src + +* Style Getters + +#+begin_src lisp +(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)) +#+end_src + +* Test Suite + +#+begin_src lisp +(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))))) +#+end_src