/ ink / layout / yoga.ts
yoga.ts
  1  import Yoga, {
  2    Align,
  3    Direction,
  4    Display,
  5    Edge,
  6    FlexDirection,
  7    Gutter,
  8    Justify,
  9    MeasureMode,
 10    Overflow,
 11    PositionType,
 12    Wrap,
 13    type Node as YogaNode,
 14  } from 'src/native-ts/yoga-layout/index.js'
 15  import {
 16    type LayoutAlign,
 17    LayoutDisplay,
 18    type LayoutEdge,
 19    type LayoutFlexDirection,
 20    type LayoutGutter,
 21    type LayoutJustify,
 22    type LayoutMeasureFunc,
 23    LayoutMeasureMode,
 24    type LayoutNode,
 25    type LayoutOverflow,
 26    type LayoutPositionType,
 27    type LayoutWrap,
 28  } from './node.js'
 29  
 30  // --
 31  // Edge/Gutter mapping
 32  
 33  const EDGE_MAP: Record<LayoutEdge, Edge> = {
 34    all: Edge.All,
 35    horizontal: Edge.Horizontal,
 36    vertical: Edge.Vertical,
 37    left: Edge.Left,
 38    right: Edge.Right,
 39    top: Edge.Top,
 40    bottom: Edge.Bottom,
 41    start: Edge.Start,
 42    end: Edge.End,
 43  }
 44  
 45  const GUTTER_MAP: Record<LayoutGutter, Gutter> = {
 46    all: Gutter.All,
 47    column: Gutter.Column,
 48    row: Gutter.Row,
 49  }
 50  
 51  // --
 52  // Yoga adapter
 53  
 54  export class YogaLayoutNode implements LayoutNode {
 55    readonly yoga: YogaNode
 56  
 57    constructor(yoga: YogaNode) {
 58      this.yoga = yoga
 59    }
 60  
 61    // Tree
 62  
 63    insertChild(child: LayoutNode, index: number): void {
 64      this.yoga.insertChild((child as YogaLayoutNode).yoga, index)
 65    }
 66  
 67    removeChild(child: LayoutNode): void {
 68      this.yoga.removeChild((child as YogaLayoutNode).yoga)
 69    }
 70  
 71    getChildCount(): number {
 72      return this.yoga.getChildCount()
 73    }
 74  
 75    getParent(): LayoutNode | null {
 76      const p = this.yoga.getParent()
 77      return p ? new YogaLayoutNode(p) : null
 78    }
 79  
 80    // Layout
 81  
 82    calculateLayout(width?: number, _height?: number): void {
 83      this.yoga.calculateLayout(width, undefined, Direction.LTR)
 84    }
 85  
 86    setMeasureFunc(fn: LayoutMeasureFunc): void {
 87      this.yoga.setMeasureFunc((w, wMode) => {
 88        const mode =
 89          wMode === MeasureMode.Exactly
 90            ? LayoutMeasureMode.Exactly
 91            : wMode === MeasureMode.AtMost
 92              ? LayoutMeasureMode.AtMost
 93              : LayoutMeasureMode.Undefined
 94        return fn(w, mode)
 95      })
 96    }
 97  
 98    unsetMeasureFunc(): void {
 99      this.yoga.unsetMeasureFunc()
100    }
101  
102    markDirty(): void {
103      this.yoga.markDirty()
104    }
105  
106    // Computed layout
107  
108    getComputedLeft(): number {
109      return this.yoga.getComputedLeft()
110    }
111  
112    getComputedTop(): number {
113      return this.yoga.getComputedTop()
114    }
115  
116    getComputedWidth(): number {
117      return this.yoga.getComputedWidth()
118    }
119  
120    getComputedHeight(): number {
121      return this.yoga.getComputedHeight()
122    }
123  
124    getComputedBorder(edge: LayoutEdge): number {
125      return this.yoga.getComputedBorder(EDGE_MAP[edge]!)
126    }
127  
128    getComputedPadding(edge: LayoutEdge): number {
129      return this.yoga.getComputedPadding(EDGE_MAP[edge]!)
130    }
131  
132    // Style setters
133  
134    setWidth(value: number): void {
135      this.yoga.setWidth(value)
136    }
137    setWidthPercent(value: number): void {
138      this.yoga.setWidthPercent(value)
139    }
140    setWidthAuto(): void {
141      this.yoga.setWidthAuto()
142    }
143    setHeight(value: number): void {
144      this.yoga.setHeight(value)
145    }
146    setHeightPercent(value: number): void {
147      this.yoga.setHeightPercent(value)
148    }
149    setHeightAuto(): void {
150      this.yoga.setHeightAuto()
151    }
152    setMinWidth(value: number): void {
153      this.yoga.setMinWidth(value)
154    }
155    setMinWidthPercent(value: number): void {
156      this.yoga.setMinWidthPercent(value)
157    }
158    setMinHeight(value: number): void {
159      this.yoga.setMinHeight(value)
160    }
161    setMinHeightPercent(value: number): void {
162      this.yoga.setMinHeightPercent(value)
163    }
164    setMaxWidth(value: number): void {
165      this.yoga.setMaxWidth(value)
166    }
167    setMaxWidthPercent(value: number): void {
168      this.yoga.setMaxWidthPercent(value)
169    }
170    setMaxHeight(value: number): void {
171      this.yoga.setMaxHeight(value)
172    }
173    setMaxHeightPercent(value: number): void {
174      this.yoga.setMaxHeightPercent(value)
175    }
176  
177    setFlexDirection(dir: LayoutFlexDirection): void {
178      const map: Record<LayoutFlexDirection, FlexDirection> = {
179        row: FlexDirection.Row,
180        'row-reverse': FlexDirection.RowReverse,
181        column: FlexDirection.Column,
182        'column-reverse': FlexDirection.ColumnReverse,
183      }
184      this.yoga.setFlexDirection(map[dir]!)
185    }
186  
187    setFlexGrow(value: number): void {
188      this.yoga.setFlexGrow(value)
189    }
190    setFlexShrink(value: number): void {
191      this.yoga.setFlexShrink(value)
192    }
193    setFlexBasis(value: number): void {
194      this.yoga.setFlexBasis(value)
195    }
196    setFlexBasisPercent(value: number): void {
197      this.yoga.setFlexBasisPercent(value)
198    }
199  
200    setFlexWrap(wrap: LayoutWrap): void {
201      const map: Record<LayoutWrap, Wrap> = {
202        nowrap: Wrap.NoWrap,
203        wrap: Wrap.Wrap,
204        'wrap-reverse': Wrap.WrapReverse,
205      }
206      this.yoga.setFlexWrap(map[wrap]!)
207    }
208  
209    setAlignItems(align: LayoutAlign): void {
210      const map: Record<LayoutAlign, Align> = {
211        auto: Align.Auto,
212        stretch: Align.Stretch,
213        'flex-start': Align.FlexStart,
214        center: Align.Center,
215        'flex-end': Align.FlexEnd,
216      }
217      this.yoga.setAlignItems(map[align]!)
218    }
219  
220    setAlignSelf(align: LayoutAlign): void {
221      const map: Record<LayoutAlign, Align> = {
222        auto: Align.Auto,
223        stretch: Align.Stretch,
224        'flex-start': Align.FlexStart,
225        center: Align.Center,
226        'flex-end': Align.FlexEnd,
227      }
228      this.yoga.setAlignSelf(map[align]!)
229    }
230  
231    setJustifyContent(justify: LayoutJustify): void {
232      const map: Record<LayoutJustify, Justify> = {
233        'flex-start': Justify.FlexStart,
234        center: Justify.Center,
235        'flex-end': Justify.FlexEnd,
236        'space-between': Justify.SpaceBetween,
237        'space-around': Justify.SpaceAround,
238        'space-evenly': Justify.SpaceEvenly,
239      }
240      this.yoga.setJustifyContent(map[justify]!)
241    }
242  
243    setDisplay(display: LayoutDisplay): void {
244      this.yoga.setDisplay(display === 'flex' ? Display.Flex : Display.None)
245    }
246  
247    getDisplay(): LayoutDisplay {
248      return this.yoga.getDisplay() === Display.None
249        ? LayoutDisplay.None
250        : LayoutDisplay.Flex
251    }
252  
253    setPositionType(type: LayoutPositionType): void {
254      this.yoga.setPositionType(
255        type === 'absolute' ? PositionType.Absolute : PositionType.Relative,
256      )
257    }
258  
259    setPosition(edge: LayoutEdge, value: number): void {
260      this.yoga.setPosition(EDGE_MAP[edge]!, value)
261    }
262  
263    setPositionPercent(edge: LayoutEdge, value: number): void {
264      this.yoga.setPositionPercent(EDGE_MAP[edge]!, value)
265    }
266  
267    setOverflow(overflow: LayoutOverflow): void {
268      const map: Record<LayoutOverflow, Overflow> = {
269        visible: Overflow.Visible,
270        hidden: Overflow.Hidden,
271        scroll: Overflow.Scroll,
272      }
273      this.yoga.setOverflow(map[overflow]!)
274    }
275  
276    setMargin(edge: LayoutEdge, value: number): void {
277      this.yoga.setMargin(EDGE_MAP[edge]!, value)
278    }
279    setPadding(edge: LayoutEdge, value: number): void {
280      this.yoga.setPadding(EDGE_MAP[edge]!, value)
281    }
282    setBorder(edge: LayoutEdge, value: number): void {
283      this.yoga.setBorder(EDGE_MAP[edge]!, value)
284    }
285    setGap(gutter: LayoutGutter, value: number): void {
286      this.yoga.setGap(GUTTER_MAP[gutter]!, value)
287    }
288  
289    // Lifecycle
290  
291    free(): void {
292      this.yoga.free()
293    }
294    freeRecursive(): void {
295      this.yoga.freeRecursive()
296    }
297  }
298  
299  // --
300  // Instance management
301  //
302  // The TS yoga-layout port is synchronous — no WASM loading, no linear memory
303  // growth, so no preload/swap/reset machinery is needed. The Yoga instance is
304  // just a plain JS object available at import time.
305  
306  export function createYogaLayoutNode(): LayoutNode {
307    return new YogaLayoutNode(Yoga.Node.create())
308  }