@simonklee/yoga 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1324 @@
1
+ const std = @import("std");
2
+
3
+ // Import C headers from Yoga
4
+ const c = @cImport({
5
+ @cInclude("yoga/Yoga.h");
6
+ });
7
+
8
+ // Re-export all Yoga types and constants for easier access
9
+ pub const YGNodeRef = c.YGNodeRef;
10
+ pub const YGNodeConstRef = c.YGNodeConstRef;
11
+ pub const YGConfigRef = c.YGConfigRef;
12
+ pub const YGConfigConstRef = c.YGConfigConstRef;
13
+
14
+ // Yoga enums
15
+ pub const YGAlign = c.YGAlign;
16
+ pub const YGBoxSizing = c.YGBoxSizing;
17
+ pub const YGDimension = c.YGDimension;
18
+ pub const YGDirection = c.YGDirection;
19
+ pub const YGDisplay = c.YGDisplay;
20
+ pub const YGEdge = c.YGEdge;
21
+ pub const YGErrata = c.YGErrata;
22
+ pub const YGExperimentalFeature = c.YGExperimentalFeature;
23
+ pub const YGFlexDirection = c.YGFlexDirection;
24
+ pub const YGGutter = c.YGGutter;
25
+ pub const YGJustify = c.YGJustify;
26
+ pub const YGLogLevel = c.YGLogLevel;
27
+ pub const YGMeasureMode = c.YGMeasureMode;
28
+ pub const YGNodeType = c.YGNodeType;
29
+ pub const YGOverflow = c.YGOverflow;
30
+ pub const YGPositionType = c.YGPositionType;
31
+ pub const YGUnit = c.YGUnit;
32
+ pub const YGWrap = c.YGWrap;
33
+
34
+ // Yoga structures
35
+ pub const YGSize = c.YGSize;
36
+ pub const YGValue = c.YGValue;
37
+
38
+ // Yoga function pointer types
39
+ pub const YGMeasureFunc = c.YGMeasureFunc;
40
+ pub const YGBaselineFunc = c.YGBaselineFunc;
41
+ pub const YGDirtiedFunc = c.YGDirtiedFunc;
42
+
43
+ // Constants
44
+ pub const YGValueAuto = c.YGValueAuto;
45
+ pub const YGValueUndefined = c.YGValueUndefined;
46
+ pub const YGValueZero = c.YGValueZero;
47
+
48
+ //=============================================================================
49
+ // CONFIG FUNCTIONS
50
+ //=============================================================================
51
+
52
+ /// Creates a new Yoga configuration
53
+ pub export fn ygConfigNew() YGConfigRef {
54
+ return c.YGConfigNew();
55
+ }
56
+
57
+ /// Frees a Yoga configuration
58
+ pub export fn ygConfigFree(config: YGConfigRef) void {
59
+ c.YGConfigFree(config);
60
+ }
61
+
62
+ /// Gets the default Yoga configuration
63
+ pub export fn ygConfigGetDefault() YGConfigConstRef {
64
+ return c.YGConfigGetDefault();
65
+ }
66
+
67
+ /// Sets whether to use web defaults
68
+ pub export fn ygConfigSetUseWebDefaults(config: YGConfigRef, enabled: bool) void {
69
+ c.YGConfigSetUseWebDefaults(config, enabled);
70
+ }
71
+
72
+ /// Gets whether web defaults are enabled
73
+ pub export fn ygConfigGetUseWebDefaults(config: YGConfigConstRef) bool {
74
+ return c.YGConfigGetUseWebDefaults(config);
75
+ }
76
+
77
+ /// Sets point scale factor for layout rounding
78
+ pub export fn ygConfigSetPointScaleFactor(config: YGConfigRef, pixelsInPoint: f32) void {
79
+ c.YGConfigSetPointScaleFactor(config, pixelsInPoint);
80
+ }
81
+
82
+ /// Gets the current point scale factor
83
+ pub export fn ygConfigGetPointScaleFactor(config: YGConfigConstRef) f32 {
84
+ return c.YGConfigGetPointScaleFactor(config);
85
+ }
86
+
87
+ /// Sets errata for balancing W3C conformance vs compatibility
88
+ pub export fn ygConfigSetErrata(config: YGConfigRef, errata: YGErrata) void {
89
+ c.YGConfigSetErrata(config, errata);
90
+ }
91
+
92
+ /// Gets current errata settings
93
+ pub export fn ygConfigGetErrata(config: YGConfigConstRef) YGErrata {
94
+ return c.YGConfigGetErrata(config);
95
+ }
96
+
97
+ /// Enables or disables an experimental feature
98
+ pub export fn ygConfigSetExperimentalFeatureEnabled(config: YGConfigRef, feature: YGExperimentalFeature, enabled: bool) void {
99
+ c.YGConfigSetExperimentalFeatureEnabled(config, feature, enabled);
100
+ }
101
+
102
+ /// Checks if an experimental feature is enabled
103
+ pub export fn ygConfigIsExperimentalFeatureEnabled(config: YGConfigConstRef, feature: YGExperimentalFeature) bool {
104
+ return c.YGConfigIsExperimentalFeatureEnabled(config, feature);
105
+ }
106
+
107
+ /// Sets a custom logger
108
+ pub export fn ygConfigSetLogger(config: YGConfigRef, logger: ?*const anyopaque) void {
109
+ c.YGConfigSetLogger(config, @ptrCast(@alignCast(logger)));
110
+ }
111
+
112
+ /// Sets config context
113
+ pub export fn ygConfigSetContext(config: YGConfigRef, context: ?*anyopaque) void {
114
+ c.YGConfigSetContext(config, context);
115
+ }
116
+
117
+ /// Gets config context
118
+ pub export fn ygConfigGetContext(config: YGConfigConstRef) ?*anyopaque {
119
+ return c.YGConfigGetContext(config);
120
+ }
121
+
122
+ /// Sets clone node function
123
+ pub export fn ygConfigSetCloneNodeFunc(config: YGConfigRef, cloneNodeFunc: ?*const anyopaque) void {
124
+ c.YGConfigSetCloneNodeFunc(config, @ptrCast(@alignCast(cloneNodeFunc)));
125
+ }
126
+
127
+ //=============================================================================
128
+ // NODE CREATION AND MANAGEMENT
129
+ //=============================================================================
130
+
131
+ /// Creates a new Yoga node with default configuration
132
+ pub export fn ygNodeNew() YGNodeRef {
133
+ return c.YGNodeNew();
134
+ }
135
+
136
+ /// Creates a new Yoga node with custom configuration
137
+ pub export fn ygNodeNewWithConfig(config: YGConfigConstRef) YGNodeRef {
138
+ return c.YGNodeNewWithConfig(config);
139
+ }
140
+
141
+ /// Clones an existing Yoga node (cloned node has no callbacks)
142
+ pub export fn ygNodeClone(node: YGNodeConstRef) YGNodeRef {
143
+ const cloned = c.YGNodeClone(node);
144
+ // Clear callbacks/context on cloned node to prevent shared CallbackContext
145
+ // and dangling callback pointers.
146
+ if (cloned) |cl| {
147
+ c.YGNodeSetMeasureFunc(cl, null);
148
+ c.YGNodeSetBaselineFunc(cl, null);
149
+ c.YGNodeSetDirtiedFunc(cl, null);
150
+ c.YGNodeSetContext(cl, null);
151
+ }
152
+ return cloned;
153
+ }
154
+
155
+ /// Frees a Yoga node (also cleans up any callback context)
156
+ pub export fn ygNodeFree(node: YGNodeRef) void {
157
+ // Free callback context BEFORE freeing the node to prevent use-after-free
158
+ // when Yoga internally accesses the context during cleanup
159
+ freeContext(node);
160
+ c.YGNodeFree(node);
161
+ }
162
+
163
+ /// Frees a Yoga node and all its children recursively
164
+ pub export fn ygNodeFreeRecursive(node: YGNodeRef) void {
165
+ // Free callback contexts for all children first (recursively)
166
+ freeContextRecursive(node);
167
+ c.YGNodeFreeRecursive(node);
168
+ }
169
+
170
+ /// Helper to recursively free callback contexts for a node and its children
171
+ fn freeContextRecursive(node: YGNodeRef) void {
172
+ const childCount = c.YGNodeGetChildCount(node);
173
+ for (0..childCount) |i| {
174
+ const child = c.YGNodeGetChild(node, i);
175
+ if (child) |ch| {
176
+ freeContextRecursive(ch);
177
+ }
178
+ }
179
+ freeContext(node);
180
+ }
181
+
182
+ /// Finalizes a node without disconnecting from owner/children
183
+ pub export fn ygNodeFinalize(node: YGNodeRef) void {
184
+ c.YGNodeFinalize(node);
185
+ }
186
+
187
+ /// Resets a node to its default state
188
+ pub export fn ygNodeReset(node: YGNodeRef) void {
189
+ // Free callback context before reset since reset clears all state
190
+ freeContext(node);
191
+ c.YGNodeReset(node);
192
+ }
193
+
194
+ //=============================================================================
195
+ // NODE HIERARCHY MANAGEMENT
196
+ //=============================================================================
197
+
198
+ /// Inserts a child node at the given index
199
+ pub export fn ygNodeInsertChild(node: YGNodeRef, child: YGNodeRef, index: usize) void {
200
+ c.YGNodeInsertChild(node, child, index);
201
+ }
202
+
203
+ /// Swaps a child node at the given index
204
+ pub export fn ygNodeSwapChild(node: YGNodeRef, child: YGNodeRef, index: usize) void {
205
+ c.YGNodeSwapChild(node, child, index);
206
+ }
207
+
208
+ /// Removes a child node
209
+ pub export fn ygNodeRemoveChild(node: YGNodeRef, child: YGNodeRef) void {
210
+ c.YGNodeRemoveChild(node, child);
211
+ }
212
+
213
+ /// Removes all child nodes
214
+ pub export fn ygNodeRemoveAllChildren(node: YGNodeRef) void {
215
+ c.YGNodeRemoveAllChildren(node);
216
+ }
217
+
218
+ /// Sets children according to the given array
219
+ pub export fn ygNodeSetChildren(owner: YGNodeRef, children: [*]const YGNodeRef, count: usize) void {
220
+ c.YGNodeSetChildren(owner, children, count);
221
+ }
222
+
223
+ /// Gets a child node at the given index
224
+ pub export fn ygNodeGetChild(node: YGNodeRef, index: usize) YGNodeRef {
225
+ return c.YGNodeGetChild(node, index);
226
+ }
227
+
228
+ /// Gets the number of child nodes
229
+ pub export fn ygNodeGetChildCount(node: YGNodeConstRef) usize {
230
+ return c.YGNodeGetChildCount(node);
231
+ }
232
+
233
+ /// Gets the owner/parent of a node
234
+ pub export fn ygNodeGetOwner(node: YGNodeRef) YGNodeRef {
235
+ return c.YGNodeGetOwner(node);
236
+ }
237
+
238
+ /// Gets the parent of a node
239
+ pub export fn ygNodeGetParent(node: YGNodeRef) YGNodeRef {
240
+ return c.YGNodeGetParent(node);
241
+ }
242
+
243
+ //=============================================================================
244
+ // LAYOUT CALCULATION
245
+ //=============================================================================
246
+
247
+ /// Calculates the layout of the node tree
248
+ pub export fn ygNodeCalculateLayout(node: YGNodeRef, availableWidth: f32, availableHeight: f32, ownerDirection: YGDirection) void {
249
+ c.YGNodeCalculateLayout(node, availableWidth, availableHeight, ownerDirection);
250
+ }
251
+
252
+ /// Checks if the node has new layout
253
+ pub export fn ygNodeGetHasNewLayout(node: YGNodeConstRef) bool {
254
+ return c.YGNodeGetHasNewLayout(node);
255
+ }
256
+
257
+ /// Sets whether a node has new layout
258
+ pub export fn ygNodeSetHasNewLayout(node: YGNodeRef, hasNewLayout: bool) void {
259
+ c.YGNodeSetHasNewLayout(node, hasNewLayout);
260
+ }
261
+
262
+ /// Checks if the node is dirty
263
+ pub export fn ygNodeIsDirty(node: YGNodeConstRef) bool {
264
+ return c.YGNodeIsDirty(node);
265
+ }
266
+
267
+ /// Marks a node as dirty
268
+ pub export fn ygNodeMarkDirty(node: YGNodeRef) void {
269
+ c.YGNodeMarkDirty(node);
270
+ }
271
+
272
+ //=============================================================================
273
+ // LAYOUT RESULTS ACCESS
274
+ //=============================================================================
275
+
276
+ /// Gets the computed left position
277
+ pub export fn ygNodeLayoutGetLeft(node: YGNodeConstRef) f32 {
278
+ return c.YGNodeLayoutGetLeft(node);
279
+ }
280
+
281
+ /// Gets the computed top position
282
+ pub export fn ygNodeLayoutGetTop(node: YGNodeConstRef) f32 {
283
+ return c.YGNodeLayoutGetTop(node);
284
+ }
285
+
286
+ /// Gets the computed right position
287
+ pub export fn ygNodeLayoutGetRight(node: YGNodeConstRef) f32 {
288
+ return c.YGNodeLayoutGetRight(node);
289
+ }
290
+
291
+ /// Gets the computed bottom position
292
+ pub export fn ygNodeLayoutGetBottom(node: YGNodeConstRef) f32 {
293
+ return c.YGNodeLayoutGetBottom(node);
294
+ }
295
+
296
+ /// Gets the computed width
297
+ pub export fn ygNodeLayoutGetWidth(node: YGNodeConstRef) f32 {
298
+ return c.YGNodeLayoutGetWidth(node);
299
+ }
300
+
301
+ /// Gets the computed height
302
+ pub export fn ygNodeLayoutGetHeight(node: YGNodeConstRef) f32 {
303
+ return c.YGNodeLayoutGetHeight(node);
304
+ }
305
+
306
+ /// Gets the computed direction
307
+ pub export fn ygNodeLayoutGetDirection(node: YGNodeConstRef) YGDirection {
308
+ return c.YGNodeLayoutGetDirection(node);
309
+ }
310
+
311
+ /// Checks if layout had overflow
312
+ pub export fn ygNodeLayoutGetHadOverflow(node: YGNodeConstRef) bool {
313
+ return c.YGNodeLayoutGetHadOverflow(node);
314
+ }
315
+
316
+ /// Gets computed margin for an edge
317
+ pub export fn ygNodeLayoutGetMargin(node: YGNodeConstRef, edge: YGEdge) f32 {
318
+ return c.YGNodeLayoutGetMargin(node, edge);
319
+ }
320
+
321
+ /// Gets computed border for an edge
322
+ pub export fn ygNodeLayoutGetBorder(node: YGNodeConstRef, edge: YGEdge) f32 {
323
+ return c.YGNodeLayoutGetBorder(node, edge);
324
+ }
325
+
326
+ /// Gets computed padding for an edge
327
+ pub export fn ygNodeLayoutGetPadding(node: YGNodeConstRef, edge: YGEdge) f32 {
328
+ return c.YGNodeLayoutGetPadding(node, edge);
329
+ }
330
+
331
+ /// Gets the measured width of the node, before layout rounding
332
+ pub export fn ygNodeLayoutGetRawWidth(node: YGNodeConstRef) f32 {
333
+ return c.YGNodeLayoutGetRawWidth(node);
334
+ }
335
+
336
+ /// Gets the measured height of the node, before layout rounding
337
+ pub export fn ygNodeLayoutGetRawHeight(node: YGNodeConstRef) f32 {
338
+ return c.YGNodeLayoutGetRawHeight(node);
339
+ }
340
+
341
+ //=============================================================================
342
+ // STYLE - LAYOUT PROPERTIES
343
+ //=============================================================================
344
+
345
+ /// Copies style from one node to another
346
+ pub export fn ygNodeCopyStyle(dstNode: YGNodeRef, srcNode: YGNodeConstRef) void {
347
+ c.YGNodeCopyStyle(dstNode, srcNode);
348
+ }
349
+
350
+ /// Sets direction
351
+ pub export fn ygNodeStyleSetDirection(node: YGNodeRef, direction: YGDirection) void {
352
+ c.YGNodeStyleSetDirection(node, direction);
353
+ }
354
+
355
+ /// Gets direction
356
+ pub export fn ygNodeStyleGetDirection(node: YGNodeConstRef) YGDirection {
357
+ return c.YGNodeStyleGetDirection(node);
358
+ }
359
+
360
+ /// Sets flex direction
361
+ pub export fn ygNodeStyleSetFlexDirection(node: YGNodeRef, flexDirection: YGFlexDirection) void {
362
+ c.YGNodeStyleSetFlexDirection(node, flexDirection);
363
+ }
364
+
365
+ /// Gets flex direction
366
+ pub export fn ygNodeStyleGetFlexDirection(node: YGNodeConstRef) YGFlexDirection {
367
+ return c.YGNodeStyleGetFlexDirection(node);
368
+ }
369
+
370
+ /// Sets justify content
371
+ pub export fn ygNodeStyleSetJustifyContent(node: YGNodeRef, justifyContent: YGJustify) void {
372
+ c.YGNodeStyleSetJustifyContent(node, justifyContent);
373
+ }
374
+
375
+ /// Gets justify content
376
+ pub export fn ygNodeStyleGetJustifyContent(node: YGNodeConstRef) YGJustify {
377
+ return c.YGNodeStyleGetJustifyContent(node);
378
+ }
379
+
380
+ /// Sets align content
381
+ pub export fn ygNodeStyleSetAlignContent(node: YGNodeRef, alignContent: YGAlign) void {
382
+ c.YGNodeStyleSetAlignContent(node, alignContent);
383
+ }
384
+
385
+ /// Gets align content
386
+ pub export fn ygNodeStyleGetAlignContent(node: YGNodeConstRef) YGAlign {
387
+ return c.YGNodeStyleGetAlignContent(node);
388
+ }
389
+
390
+ /// Sets align items
391
+ pub export fn ygNodeStyleSetAlignItems(node: YGNodeRef, alignItems: YGAlign) void {
392
+ c.YGNodeStyleSetAlignItems(node, alignItems);
393
+ }
394
+
395
+ /// Gets align items
396
+ pub export fn ygNodeStyleGetAlignItems(node: YGNodeConstRef) YGAlign {
397
+ return c.YGNodeStyleGetAlignItems(node);
398
+ }
399
+
400
+ /// Sets align self
401
+ pub export fn ygNodeStyleSetAlignSelf(node: YGNodeRef, alignSelf: YGAlign) void {
402
+ c.YGNodeStyleSetAlignSelf(node, alignSelf);
403
+ }
404
+
405
+ /// Gets align self
406
+ pub export fn ygNodeStyleGetAlignSelf(node: YGNodeConstRef) YGAlign {
407
+ return c.YGNodeStyleGetAlignSelf(node);
408
+ }
409
+
410
+ /// Sets position type
411
+ pub export fn ygNodeStyleSetPositionType(node: YGNodeRef, positionType: YGPositionType) void {
412
+ c.YGNodeStyleSetPositionType(node, positionType);
413
+ }
414
+
415
+ /// Gets position type
416
+ pub export fn ygNodeStyleGetPositionType(node: YGNodeConstRef) YGPositionType {
417
+ return c.YGNodeStyleGetPositionType(node);
418
+ }
419
+
420
+ /// Sets flex wrap
421
+ pub export fn ygNodeStyleSetFlexWrap(node: YGNodeRef, flexWrap: YGWrap) void {
422
+ c.YGNodeStyleSetFlexWrap(node, flexWrap);
423
+ }
424
+
425
+ /// Gets flex wrap
426
+ pub export fn ygNodeStyleGetFlexWrap(node: YGNodeConstRef) YGWrap {
427
+ return c.YGNodeStyleGetFlexWrap(node);
428
+ }
429
+
430
+ /// Sets overflow
431
+ pub export fn ygNodeStyleSetOverflow(node: YGNodeRef, overflow: YGOverflow) void {
432
+ c.YGNodeStyleSetOverflow(node, overflow);
433
+ }
434
+
435
+ /// Gets overflow
436
+ pub export fn ygNodeStyleGetOverflow(node: YGNodeConstRef) YGOverflow {
437
+ return c.YGNodeStyleGetOverflow(node);
438
+ }
439
+
440
+ /// Sets display
441
+ pub export fn ygNodeStyleSetDisplay(node: YGNodeRef, display: YGDisplay) void {
442
+ c.YGNodeStyleSetDisplay(node, display);
443
+ }
444
+
445
+ /// Gets display
446
+ pub export fn ygNodeStyleGetDisplay(node: YGNodeConstRef) YGDisplay {
447
+ return c.YGNodeStyleGetDisplay(node);
448
+ }
449
+
450
+ /// Sets box sizing
451
+ pub export fn ygNodeStyleSetBoxSizing(node: YGNodeRef, boxSizing: YGBoxSizing) void {
452
+ c.YGNodeStyleSetBoxSizing(node, boxSizing);
453
+ }
454
+
455
+ /// Gets box sizing
456
+ pub export fn ygNodeStyleGetBoxSizing(node: YGNodeConstRef) YGBoxSizing {
457
+ return c.YGNodeStyleGetBoxSizing(node);
458
+ }
459
+
460
+ //=============================================================================
461
+ // STYLE - FLEX PROPERTIES
462
+ //=============================================================================
463
+
464
+ /// Sets flex shorthand
465
+ pub export fn ygNodeStyleSetFlex(node: YGNodeRef, flex: f32) void {
466
+ c.YGNodeStyleSetFlex(node, flex);
467
+ }
468
+
469
+ /// Gets flex shorthand
470
+ pub export fn ygNodeStyleGetFlex(node: YGNodeConstRef) f32 {
471
+ return c.YGNodeStyleGetFlex(node);
472
+ }
473
+
474
+ /// Sets flex grow
475
+ pub export fn ygNodeStyleSetFlexGrow(node: YGNodeRef, flexGrow: f32) void {
476
+ c.YGNodeStyleSetFlexGrow(node, flexGrow);
477
+ }
478
+
479
+ /// Gets flex grow
480
+ pub export fn ygNodeStyleGetFlexGrow(node: YGNodeConstRef) f32 {
481
+ return c.YGNodeStyleGetFlexGrow(node);
482
+ }
483
+
484
+ /// Sets flex shrink
485
+ pub export fn ygNodeStyleSetFlexShrink(node: YGNodeRef, flexShrink: f32) void {
486
+ c.YGNodeStyleSetFlexShrink(node, flexShrink);
487
+ }
488
+
489
+ /// Gets flex shrink
490
+ pub export fn ygNodeStyleGetFlexShrink(node: YGNodeConstRef) f32 {
491
+ return c.YGNodeStyleGetFlexShrink(node);
492
+ }
493
+
494
+ /// Sets flex basis (points)
495
+ pub export fn ygNodeStyleSetFlexBasis(node: YGNodeRef, flexBasis: f32) void {
496
+ c.YGNodeStyleSetFlexBasis(node, flexBasis);
497
+ }
498
+
499
+ /// Sets flex basis (percent)
500
+ pub export fn ygNodeStyleSetFlexBasisPercent(node: YGNodeRef, flexBasis: f32) void {
501
+ c.YGNodeStyleSetFlexBasisPercent(node, flexBasis);
502
+ }
503
+
504
+ /// Sets flex basis to auto
505
+ pub export fn ygNodeStyleSetFlexBasisAuto(node: YGNodeRef) void {
506
+ c.YGNodeStyleSetFlexBasisAuto(node);
507
+ }
508
+
509
+ /// Sets flex basis to max content
510
+ pub export fn ygNodeStyleSetFlexBasisMaxContent(node: YGNodeRef) void {
511
+ c.YGNodeStyleSetFlexBasisMaxContent(node);
512
+ }
513
+
514
+ /// Sets flex basis to fit content
515
+ pub export fn ygNodeStyleSetFlexBasisFitContent(node: YGNodeRef) void {
516
+ c.YGNodeStyleSetFlexBasisFitContent(node);
517
+ }
518
+
519
+ /// Sets flex basis to stretch
520
+ pub export fn ygNodeStyleSetFlexBasisStretch(node: YGNodeRef) void {
521
+ c.YGNodeStyleSetFlexBasisStretch(node);
522
+ }
523
+
524
+ /// Gets flex basis
525
+ pub export fn ygNodeStyleGetFlexBasis(node: YGNodeConstRef) YGValue {
526
+ return c.YGNodeStyleGetFlexBasis(node);
527
+ }
528
+
529
+ //=============================================================================
530
+ // STYLE - POSITION PROPERTIES
531
+ //=============================================================================
532
+
533
+ /// Sets position (points)
534
+ pub export fn ygNodeStyleSetPosition(node: YGNodeRef, edge: YGEdge, position: f32) void {
535
+ c.YGNodeStyleSetPosition(node, edge, position);
536
+ }
537
+
538
+ /// Sets position (percent)
539
+ pub export fn ygNodeStyleSetPositionPercent(node: YGNodeRef, edge: YGEdge, position: f32) void {
540
+ c.YGNodeStyleSetPositionPercent(node, edge, position);
541
+ }
542
+
543
+ /// Sets position to auto
544
+ pub export fn ygNodeStyleSetPositionAuto(node: YGNodeRef, edge: YGEdge) void {
545
+ c.YGNodeStyleSetPositionAuto(node, edge);
546
+ }
547
+
548
+ /// Gets position
549
+ pub export fn ygNodeStyleGetPosition(node: YGNodeConstRef, edge: YGEdge) YGValue {
550
+ return c.YGNodeStyleGetPosition(node, edge);
551
+ }
552
+
553
+ //=============================================================================
554
+ // STYLE - MARGIN PROPERTIES
555
+ //=============================================================================
556
+
557
+ /// Sets margin (points)
558
+ pub export fn ygNodeStyleSetMargin(node: YGNodeRef, edge: YGEdge, margin: f32) void {
559
+ c.YGNodeStyleSetMargin(node, edge, margin);
560
+ }
561
+
562
+ /// Sets margin (percent)
563
+ pub export fn ygNodeStyleSetMarginPercent(node: YGNodeRef, edge: YGEdge, margin: f32) void {
564
+ c.YGNodeStyleSetMarginPercent(node, edge, margin);
565
+ }
566
+
567
+ /// Sets margin to auto
568
+ pub export fn ygNodeStyleSetMarginAuto(node: YGNodeRef, edge: YGEdge) void {
569
+ c.YGNodeStyleSetMarginAuto(node, edge);
570
+ }
571
+
572
+ /// Gets margin
573
+ pub export fn ygNodeStyleGetMargin(node: YGNodeConstRef, edge: YGEdge) YGValue {
574
+ return c.YGNodeStyleGetMargin(node, edge);
575
+ }
576
+
577
+ //=============================================================================
578
+ // STYLE - PADDING PROPERTIES
579
+ //=============================================================================
580
+
581
+ /// Sets padding (points)
582
+ pub export fn ygNodeStyleSetPadding(node: YGNodeRef, edge: YGEdge, padding: f32) void {
583
+ c.YGNodeStyleSetPadding(node, edge, padding);
584
+ }
585
+
586
+ /// Sets padding (percent)
587
+ pub export fn ygNodeStyleSetPaddingPercent(node: YGNodeRef, edge: YGEdge, padding: f32) void {
588
+ c.YGNodeStyleSetPaddingPercent(node, edge, padding);
589
+ }
590
+
591
+ /// Gets padding
592
+ pub export fn ygNodeStyleGetPadding(node: YGNodeConstRef, edge: YGEdge) YGValue {
593
+ return c.YGNodeStyleGetPadding(node, edge);
594
+ }
595
+
596
+ //=============================================================================
597
+ // STYLE - BORDER PROPERTIES
598
+ //=============================================================================
599
+
600
+ /// Sets border width
601
+ pub export fn ygNodeStyleSetBorder(node: YGNodeRef, edge: YGEdge, border: f32) void {
602
+ c.YGNodeStyleSetBorder(node, edge, border);
603
+ }
604
+
605
+ /// Gets border width
606
+ pub export fn ygNodeStyleGetBorder(node: YGNodeConstRef, edge: YGEdge) f32 {
607
+ return c.YGNodeStyleGetBorder(node, edge);
608
+ }
609
+
610
+ //=============================================================================
611
+ // STYLE - GAP PROPERTIES
612
+ //=============================================================================
613
+
614
+ /// Sets gap (points)
615
+ pub export fn ygNodeStyleSetGap(node: YGNodeRef, gutter: YGGutter, gapLength: f32) void {
616
+ c.YGNodeStyleSetGap(node, gutter, gapLength);
617
+ }
618
+
619
+ /// Sets gap (percent)
620
+ pub export fn ygNodeStyleSetGapPercent(node: YGNodeRef, gutter: YGGutter, gapLength: f32) void {
621
+ c.YGNodeStyleSetGapPercent(node, gutter, gapLength);
622
+ }
623
+
624
+ /// Gets gap
625
+ pub export fn ygNodeStyleGetGap(node: YGNodeConstRef, gutter: YGGutter) YGValue {
626
+ return c.YGNodeStyleGetGap(node, gutter);
627
+ }
628
+
629
+ //=============================================================================
630
+ // STYLE - SIZE PROPERTIES
631
+ //=============================================================================
632
+
633
+ /// Sets width (points)
634
+ pub export fn ygNodeStyleSetWidth(node: YGNodeRef, width: f32) void {
635
+ c.YGNodeStyleSetWidth(node, width);
636
+ }
637
+
638
+ /// Sets width (percent)
639
+ pub export fn ygNodeStyleSetWidthPercent(node: YGNodeRef, width: f32) void {
640
+ c.YGNodeStyleSetWidthPercent(node, width);
641
+ }
642
+
643
+ /// Sets width to auto
644
+ pub export fn ygNodeStyleSetWidthAuto(node: YGNodeRef) void {
645
+ c.YGNodeStyleSetWidthAuto(node);
646
+ }
647
+
648
+ /// Sets width to max content
649
+ pub export fn ygNodeStyleSetWidthMaxContent(node: YGNodeRef) void {
650
+ c.YGNodeStyleSetWidthMaxContent(node);
651
+ }
652
+
653
+ /// Sets width to fit content
654
+ pub export fn ygNodeStyleSetWidthFitContent(node: YGNodeRef) void {
655
+ c.YGNodeStyleSetWidthFitContent(node);
656
+ }
657
+
658
+ /// Sets width to stretch
659
+ pub export fn ygNodeStyleSetWidthStretch(node: YGNodeRef) void {
660
+ c.YGNodeStyleSetWidthStretch(node);
661
+ }
662
+
663
+ /// Gets width
664
+ pub export fn ygNodeStyleGetWidth(node: YGNodeConstRef) YGValue {
665
+ return c.YGNodeStyleGetWidth(node);
666
+ }
667
+
668
+ /// Sets height (points)
669
+ pub export fn ygNodeStyleSetHeight(node: YGNodeRef, height: f32) void {
670
+ c.YGNodeStyleSetHeight(node, height);
671
+ }
672
+
673
+ /// Sets height (percent)
674
+ pub export fn ygNodeStyleSetHeightPercent(node: YGNodeRef, height: f32) void {
675
+ c.YGNodeStyleSetHeightPercent(node, height);
676
+ }
677
+
678
+ /// Sets height to auto
679
+ pub export fn ygNodeStyleSetHeightAuto(node: YGNodeRef) void {
680
+ c.YGNodeStyleSetHeightAuto(node);
681
+ }
682
+
683
+ /// Sets height to max content
684
+ pub export fn ygNodeStyleSetHeightMaxContent(node: YGNodeRef) void {
685
+ c.YGNodeStyleSetHeightMaxContent(node);
686
+ }
687
+
688
+ /// Sets height to fit content
689
+ pub export fn ygNodeStyleSetHeightFitContent(node: YGNodeRef) void {
690
+ c.YGNodeStyleSetHeightFitContent(node);
691
+ }
692
+
693
+ /// Sets height to stretch
694
+ pub export fn ygNodeStyleSetHeightStretch(node: YGNodeRef) void {
695
+ c.YGNodeStyleSetHeightStretch(node);
696
+ }
697
+
698
+ /// Gets height
699
+ pub export fn ygNodeStyleGetHeight(node: YGNodeConstRef) YGValue {
700
+ return c.YGNodeStyleGetHeight(node);
701
+ }
702
+
703
+ /// Sets min width (points)
704
+ pub export fn ygNodeStyleSetMinWidth(node: YGNodeRef, minWidth: f32) void {
705
+ c.YGNodeStyleSetMinWidth(node, minWidth);
706
+ }
707
+
708
+ /// Sets min width (percent)
709
+ pub export fn ygNodeStyleSetMinWidthPercent(node: YGNodeRef, minWidth: f32) void {
710
+ c.YGNodeStyleSetMinWidthPercent(node, minWidth);
711
+ }
712
+
713
+ /// Sets min width to max content
714
+ pub export fn ygNodeStyleSetMinWidthMaxContent(node: YGNodeRef) void {
715
+ c.YGNodeStyleSetMinWidthMaxContent(node);
716
+ }
717
+
718
+ /// Sets min width to fit content
719
+ pub export fn ygNodeStyleSetMinWidthFitContent(node: YGNodeRef) void {
720
+ c.YGNodeStyleSetMinWidthFitContent(node);
721
+ }
722
+
723
+ /// Sets min width to stretch
724
+ pub export fn ygNodeStyleSetMinWidthStretch(node: YGNodeRef) void {
725
+ c.YGNodeStyleSetMinWidthStretch(node);
726
+ }
727
+
728
+ /// Gets min width
729
+ pub export fn ygNodeStyleGetMinWidth(node: YGNodeConstRef) YGValue {
730
+ return c.YGNodeStyleGetMinWidth(node);
731
+ }
732
+
733
+ /// Sets min height (points)
734
+ pub export fn ygNodeStyleSetMinHeight(node: YGNodeRef, minHeight: f32) void {
735
+ c.YGNodeStyleSetMinHeight(node, minHeight);
736
+ }
737
+
738
+ /// Sets min height (percent)
739
+ pub export fn ygNodeStyleSetMinHeightPercent(node: YGNodeRef, minHeight: f32) void {
740
+ c.YGNodeStyleSetMinHeightPercent(node, minHeight);
741
+ }
742
+
743
+ /// Sets min height to max content
744
+ pub export fn ygNodeStyleSetMinHeightMaxContent(node: YGNodeRef) void {
745
+ c.YGNodeStyleSetMinHeightMaxContent(node);
746
+ }
747
+
748
+ /// Sets min height to fit content
749
+ pub export fn ygNodeStyleSetMinHeightFitContent(node: YGNodeRef) void {
750
+ c.YGNodeStyleSetMinHeightFitContent(node);
751
+ }
752
+
753
+ /// Sets min height to stretch
754
+ pub export fn ygNodeStyleSetMinHeightStretch(node: YGNodeRef) void {
755
+ c.YGNodeStyleSetMinHeightStretch(node);
756
+ }
757
+
758
+ /// Gets min height
759
+ pub export fn ygNodeStyleGetMinHeight(node: YGNodeConstRef) YGValue {
760
+ return c.YGNodeStyleGetMinHeight(node);
761
+ }
762
+
763
+ /// Sets max width (points)
764
+ pub export fn ygNodeStyleSetMaxWidth(node: YGNodeRef, maxWidth: f32) void {
765
+ c.YGNodeStyleSetMaxWidth(node, maxWidth);
766
+ }
767
+
768
+ /// Sets max width (percent)
769
+ pub export fn ygNodeStyleSetMaxWidthPercent(node: YGNodeRef, maxWidth: f32) void {
770
+ c.YGNodeStyleSetMaxWidthPercent(node, maxWidth);
771
+ }
772
+
773
+ /// Sets max width to max content
774
+ pub export fn ygNodeStyleSetMaxWidthMaxContent(node: YGNodeRef) void {
775
+ c.YGNodeStyleSetMaxWidthMaxContent(node);
776
+ }
777
+
778
+ /// Sets max width to fit content
779
+ pub export fn ygNodeStyleSetMaxWidthFitContent(node: YGNodeRef) void {
780
+ c.YGNodeStyleSetMaxWidthFitContent(node);
781
+ }
782
+
783
+ /// Sets max width to stretch
784
+ pub export fn ygNodeStyleSetMaxWidthStretch(node: YGNodeRef) void {
785
+ c.YGNodeStyleSetMaxWidthStretch(node);
786
+ }
787
+
788
+ /// Gets max width
789
+ pub export fn ygNodeStyleGetMaxWidth(node: YGNodeConstRef) YGValue {
790
+ return c.YGNodeStyleGetMaxWidth(node);
791
+ }
792
+
793
+ /// Sets max height (points)
794
+ pub export fn ygNodeStyleSetMaxHeight(node: YGNodeRef, maxHeight: f32) void {
795
+ c.YGNodeStyleSetMaxHeight(node, maxHeight);
796
+ }
797
+
798
+ /// Sets max height (percent)
799
+ pub export fn ygNodeStyleSetMaxHeightPercent(node: YGNodeRef, maxHeight: f32) void {
800
+ c.YGNodeStyleSetMaxHeightPercent(node, maxHeight);
801
+ }
802
+
803
+ /// Sets max height to max content
804
+ pub export fn ygNodeStyleSetMaxHeightMaxContent(node: YGNodeRef) void {
805
+ c.YGNodeStyleSetMaxHeightMaxContent(node);
806
+ }
807
+
808
+ /// Sets max height to fit content
809
+ pub export fn ygNodeStyleSetMaxHeightFitContent(node: YGNodeRef) void {
810
+ c.YGNodeStyleSetMaxHeightFitContent(node);
811
+ }
812
+
813
+ /// Sets max height to stretch
814
+ pub export fn ygNodeStyleSetMaxHeightStretch(node: YGNodeRef) void {
815
+ c.YGNodeStyleSetMaxHeightStretch(node);
816
+ }
817
+
818
+ /// Gets max height
819
+ pub export fn ygNodeStyleGetMaxHeight(node: YGNodeConstRef) YGValue {
820
+ return c.YGNodeStyleGetMaxHeight(node);
821
+ }
822
+
823
+ //=============================================================================
824
+ // STYLE - ASPECT RATIO
825
+ //=============================================================================
826
+
827
+ /// Sets aspect ratio
828
+ pub export fn ygNodeStyleSetAspectRatio(node: YGNodeRef, aspectRatio: f32) void {
829
+ c.YGNodeStyleSetAspectRatio(node, aspectRatio);
830
+ }
831
+
832
+ /// Gets aspect ratio
833
+ pub export fn ygNodeStyleGetAspectRatio(node: YGNodeConstRef) f32 {
834
+ return c.YGNodeStyleGetAspectRatio(node);
835
+ }
836
+
837
+ //=============================================================================
838
+ // NODE CONFIGURATION AND CONTEXT
839
+ //=============================================================================
840
+
841
+ /// Sets node configuration
842
+ pub export fn ygNodeSetConfig(node: YGNodeRef, config: YGConfigRef) void {
843
+ c.YGNodeSetConfig(node, config);
844
+ }
845
+
846
+ /// Gets node configuration
847
+ pub export fn ygNodeGetConfig(node: YGNodeRef) YGConfigConstRef {
848
+ return c.YGNodeGetConfig(node);
849
+ }
850
+
851
+ /// Sets node context (user data)
852
+ pub export fn ygNodeSetContext(node: YGNodeRef, context: ?*anyopaque) void {
853
+ c.YGNodeSetContext(node, context);
854
+ }
855
+
856
+ /// Gets node context (user data)
857
+ pub export fn ygNodeGetContext(node: YGNodeConstRef) ?*anyopaque {
858
+ return c.YGNodeGetContext(node);
859
+ }
860
+
861
+ /// Sets measure function
862
+ pub export fn ygNodeSetMeasureFunc(node: YGNodeRef, measureFunc: ?*const anyopaque) void {
863
+ c.YGNodeSetMeasureFunc(node, @ptrCast(@alignCast(measureFunc)));
864
+ }
865
+
866
+ /// Unsets measure function
867
+ pub export fn ygNodeUnsetMeasureFunc(node: YGNodeRef) void {
868
+ c.YGNodeSetMeasureFunc(node, null);
869
+ }
870
+
871
+ /// Checks if node has measure function
872
+ pub export fn ygNodeHasMeasureFunc(node: YGNodeConstRef) bool {
873
+ return c.YGNodeHasMeasureFunc(node);
874
+ }
875
+
876
+ /// Sets baseline function
877
+ pub export fn ygNodeSetBaselineFunc(node: YGNodeRef, baselineFunc: ?*const anyopaque) void {
878
+ c.YGNodeSetBaselineFunc(node, @ptrCast(@alignCast(baselineFunc)));
879
+ }
880
+
881
+ /// Unsets baseline function
882
+ pub export fn ygNodeUnsetBaselineFunc(node: YGNodeRef) void {
883
+ c.YGNodeSetBaselineFunc(node, null);
884
+ }
885
+
886
+ /// Checks if node has baseline function
887
+ pub export fn ygNodeHasBaselineFunc(node: YGNodeConstRef) bool {
888
+ return c.YGNodeHasBaselineFunc(node);
889
+ }
890
+
891
+ /// Sets dirtied callback function
892
+ pub export fn ygNodeSetDirtiedFunc(node: YGNodeRef, dirtiedFunc: ?*const anyopaque) void {
893
+ c.YGNodeSetDirtiedFunc(node, @ptrCast(@alignCast(dirtiedFunc)));
894
+ }
895
+
896
+ /// Unsets dirtied callback function
897
+ pub export fn ygNodeUnsetDirtiedFunc(node: YGNodeRef) void {
898
+ c.YGNodeSetDirtiedFunc(node, null);
899
+ }
900
+
901
+ /// Gets dirtied callback function
902
+ pub export fn ygNodeGetDirtiedFunc(node: YGNodeConstRef) ?*const anyopaque {
903
+ return @ptrCast(c.YGNodeGetDirtiedFunc(node));
904
+ }
905
+
906
+ /// Sets node type
907
+ pub export fn ygNodeSetNodeType(node: YGNodeRef, nodeType: YGNodeType) void {
908
+ c.YGNodeSetNodeType(node, nodeType);
909
+ }
910
+
911
+ /// Gets node type
912
+ pub export fn ygNodeGetNodeType(node: YGNodeConstRef) YGNodeType {
913
+ return c.YGNodeGetNodeType(node);
914
+ }
915
+
916
+ /// Sets whether node is reference baseline
917
+ pub export fn ygNodeSetIsReferenceBaseline(node: YGNodeRef, isReferenceBaseline: bool) void {
918
+ c.YGNodeSetIsReferenceBaseline(node, isReferenceBaseline);
919
+ }
920
+
921
+ /// Checks if node is reference baseline
922
+ pub export fn ygNodeIsReferenceBaseline(node: YGNodeConstRef) bool {
923
+ return c.YGNodeIsReferenceBaseline(node);
924
+ }
925
+
926
+ /// Sets whether node always forms containing block
927
+ pub export fn ygNodeSetAlwaysFormsContainingBlock(node: YGNodeRef, alwaysFormsContainingBlock: bool) void {
928
+ c.YGNodeSetAlwaysFormsContainingBlock(node, alwaysFormsContainingBlock);
929
+ }
930
+
931
+ /// Checks if node always forms containing block
932
+ pub export fn ygNodeGetAlwaysFormsContainingBlock(node: YGNodeConstRef) bool {
933
+ return c.YGNodeGetAlwaysFormsContainingBlock(node);
934
+ }
935
+
936
+ //=============================================================================
937
+ // CALLBACK HELPER FUNCTIONS - MEASURE/BASELINE FUNCTION WORKAROUND
938
+ //=============================================================================
939
+ //
940
+ // The ARM64 calling convention returns struct { float, float } in s0 and s1 registers.
941
+ // However, Bun FFI callbacks can only return via x0 (integer) or d0 (single float register).
942
+ // Since d0 != s0+s1 on ARM64, we can't properly return YGSize from a JSCallback.
943
+ // Single f32 returns also have issues on ARM64 (returned in s0, not readable by Bun).
944
+ //
945
+ // Workaround: Use trampolines that store results via thread-local storage.
946
+ //=============================================================================
947
+
948
+ /// Thread-local storage for callback results (must be threadlocal for safety)
949
+ threadlocal var tls_measure_width: f32 = 0;
950
+ threadlocal var tls_measure_height: f32 = 0;
951
+ threadlocal var tls_baseline_result: f32 = 0;
952
+
953
+ /// Callback context - stores both measure and baseline callbacks for a node
954
+ const CallbackContext = struct {
955
+ measure_callback: ?*const anyopaque = null,
956
+ baseline_callback: ?*const anyopaque = null,
957
+ };
958
+
959
+ /// Use C allocator for FFI compatibility with glibc malloc/free on Linux.
960
+ /// Zig's GeneralPurposeAllocator can cause heap corruption ("malloc(): unaligned
961
+ /// tcache chunk detected") when mixed with glibc's thread-cached malloc.
962
+ const callback_allocator = std.heap.c_allocator;
963
+
964
+ /// Get or create callback context for a node
965
+ fn getOrCreateContext(node: YGNodeRef) *CallbackContext {
966
+ const existing = c.YGNodeGetContext(node);
967
+ if (existing) |ptr| {
968
+ return @ptrCast(@alignCast(ptr));
969
+ }
970
+ // Create new context using C allocator for malloc/free compatibility
971
+ const ctx = callback_allocator.create(CallbackContext) catch @panic("Failed to allocate callback context");
972
+ ctx.* = CallbackContext{};
973
+ c.YGNodeSetContext(node, ctx);
974
+ return ctx;
975
+ }
976
+
977
+ /// Get callback context for a node (const version)
978
+ fn getContext(node: YGNodeConstRef) ?*CallbackContext {
979
+ const existing = c.YGNodeGetContext(node);
980
+ if (existing) |ptr| {
981
+ return @ptrCast(@alignCast(ptr));
982
+ }
983
+ return null;
984
+ }
985
+
986
+ /// Free callback context for a node
987
+ fn freeContext(node: YGNodeRef) void {
988
+ const existing = c.YGNodeGetContext(node);
989
+ if (existing) |ptr| {
990
+ const ctx: *CallbackContext = @ptrCast(@alignCast(ptr));
991
+ callback_allocator.destroy(ctx);
992
+ c.YGNodeSetContext(node, null);
993
+ }
994
+ }
995
+
996
+ /// Store measure result - call this from JS callback before returning
997
+ pub export fn ygStoreMeasureResult(width: f32, height: f32) void {
998
+ tls_measure_width = width;
999
+ tls_measure_height = height;
1000
+ }
1001
+
1002
+ /// Store baseline result - call this from JS callback before returning
1003
+ pub export fn ygStoreBaselineResult(baseline: f32) void {
1004
+ tls_baseline_result = baseline;
1005
+ }
1006
+
1007
+ /// JS callback type for measure function trampoline
1008
+ const JsMeasureTrampoline = *const fn (?*anyopaque, f32, c.YGMeasureMode, f32, c.YGMeasureMode) callconv(.c) void;
1009
+
1010
+ /// JS callback type for baseline function trampoline
1011
+ const JsBaselineTrampoline = *const fn (?*anyopaque, f32, f32) callconv(.c) void;
1012
+
1013
+ /// Internal measure function that reads from TLS
1014
+ fn internalMeasureFunc(
1015
+ node: YGNodeConstRef,
1016
+ width: f32,
1017
+ widthMode: YGMeasureMode,
1018
+ height: f32,
1019
+ heightMode: YGMeasureMode,
1020
+ ) callconv(.c) YGSize {
1021
+ if (getContext(node)) |ctx| {
1022
+ if (ctx.measure_callback) |cb| {
1023
+ const trampoline: JsMeasureTrampoline = @ptrCast(@alignCast(cb));
1024
+ trampoline(@ptrCast(@constCast(node)), width, widthMode, height, heightMode);
1025
+ }
1026
+ }
1027
+ return YGSize{ .width = tls_measure_width, .height = tls_measure_height };
1028
+ }
1029
+
1030
+ /// Internal baseline function that reads from TLS
1031
+ fn internalBaselineFunc(
1032
+ node: YGNodeConstRef,
1033
+ width: f32,
1034
+ height: f32,
1035
+ ) callconv(.c) f32 {
1036
+ if (getContext(node)) |ctx| {
1037
+ if (ctx.baseline_callback) |cb| {
1038
+ const trampoline: JsBaselineTrampoline = @ptrCast(@alignCast(cb));
1039
+ trampoline(@ptrCast(@constCast(node)), width, height);
1040
+ }
1041
+ }
1042
+ return tls_baseline_result;
1043
+ }
1044
+
1045
+ /// Set measure function using the trampoline approach
1046
+ pub export fn ygNodeSetMeasureFuncTrampoline(node: YGNodeRef, trampoline_ptr: ?*const anyopaque) void {
1047
+ if (trampoline_ptr) |ptr| {
1048
+ const ctx = getOrCreateContext(node);
1049
+ ctx.measure_callback = ptr;
1050
+ c.YGNodeSetMeasureFunc(node, &internalMeasureFunc);
1051
+ } else {
1052
+ if (getContext(node)) |ctx| {
1053
+ ctx.measure_callback = null;
1054
+ // Only clear measure func, keep baseline if set
1055
+ c.YGNodeSetMeasureFunc(node, null);
1056
+ }
1057
+ }
1058
+ }
1059
+
1060
+ /// Unset the trampoline-based measure function
1061
+ pub export fn ygNodeUnsetMeasureFuncTrampoline(node: YGNodeRef) void {
1062
+ if (getContext(node)) |ctx| {
1063
+ ctx.measure_callback = null;
1064
+ c.YGNodeSetMeasureFunc(node, null);
1065
+ // Free context if both callbacks are null
1066
+ if (ctx.baseline_callback == null) {
1067
+ freeContext(node);
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ /// Set baseline function using the trampoline approach
1073
+ pub export fn ygNodeSetBaselineFuncTrampoline(node: YGNodeRef, trampoline_ptr: ?*const anyopaque) void {
1074
+ if (trampoline_ptr) |ptr| {
1075
+ const ctx = getOrCreateContext(node);
1076
+ ctx.baseline_callback = ptr;
1077
+ c.YGNodeSetBaselineFunc(node, &internalBaselineFunc);
1078
+ } else {
1079
+ if (getContext(node)) |ctx| {
1080
+ ctx.baseline_callback = null;
1081
+ c.YGNodeSetBaselineFunc(node, null);
1082
+ }
1083
+ }
1084
+ }
1085
+
1086
+ /// Unset the trampoline-based baseline function
1087
+ pub export fn ygNodeUnsetBaselineFuncTrampoline(node: YGNodeRef) void {
1088
+ if (getContext(node)) |ctx| {
1089
+ ctx.baseline_callback = null;
1090
+ c.YGNodeSetBaselineFunc(node, null);
1091
+ // Free context if both callbacks are null
1092
+ if (ctx.measure_callback == null) {
1093
+ freeContext(node);
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ /// Free all callbacks for a node (call before freeing the node)
1099
+ pub export fn ygNodeFreeCallbacks(node: YGNodeRef) void {
1100
+ freeContext(node);
1101
+ }
1102
+
1103
+ /// Legacy helper - kept for compatibility but prefer trampoline approach
1104
+ pub export fn ygCreateSize(width: f32, height: f32) u64 {
1105
+ const w_bits: u32 = @bitCast(width);
1106
+ const h_bits: u32 = @bitCast(height);
1107
+ return @as(u64, h_bits) << 32 | @as(u64, w_bits);
1108
+ }
1109
+
1110
+ //=============================================================================
1111
+ // VALUE GETTERS (packed for FFI - returns unit in lower 32 bits, value in upper 32)
1112
+ //=============================================================================
1113
+
1114
+ /// Helper to pack YGValue into u64 for FFI (unit in lower 32, value bits in upper 32)
1115
+ fn packValue(val: YGValue) u64 {
1116
+ const unit_bits: u32 = @intCast(val.unit);
1117
+ const value_bits: u32 = @bitCast(val.value);
1118
+ return @as(u64, value_bits) << 32 | @as(u64, unit_bits);
1119
+ }
1120
+
1121
+ /// Gets width as packed value (unit | value)
1122
+ pub export fn ygNodeStyleGetWidthPacked(node: YGNodeConstRef) u64 {
1123
+ return packValue(c.YGNodeStyleGetWidth(node));
1124
+ }
1125
+
1126
+ /// Gets height as packed value
1127
+ pub export fn ygNodeStyleGetHeightPacked(node: YGNodeConstRef) u64 {
1128
+ return packValue(c.YGNodeStyleGetHeight(node));
1129
+ }
1130
+
1131
+ /// Gets min width as packed value
1132
+ pub export fn ygNodeStyleGetMinWidthPacked(node: YGNodeConstRef) u64 {
1133
+ return packValue(c.YGNodeStyleGetMinWidth(node));
1134
+ }
1135
+
1136
+ /// Gets min height as packed value
1137
+ pub export fn ygNodeStyleGetMinHeightPacked(node: YGNodeConstRef) u64 {
1138
+ return packValue(c.YGNodeStyleGetMinHeight(node));
1139
+ }
1140
+
1141
+ /// Gets max width as packed value
1142
+ pub export fn ygNodeStyleGetMaxWidthPacked(node: YGNodeConstRef) u64 {
1143
+ return packValue(c.YGNodeStyleGetMaxWidth(node));
1144
+ }
1145
+
1146
+ /// Gets max height as packed value
1147
+ pub export fn ygNodeStyleGetMaxHeightPacked(node: YGNodeConstRef) u64 {
1148
+ return packValue(c.YGNodeStyleGetMaxHeight(node));
1149
+ }
1150
+
1151
+ /// Gets margin as packed value
1152
+ pub export fn ygNodeStyleGetMarginPacked(node: YGNodeConstRef, edge: YGEdge) u64 {
1153
+ return packValue(c.YGNodeStyleGetMargin(node, edge));
1154
+ }
1155
+
1156
+ /// Gets padding as packed value
1157
+ pub export fn ygNodeStyleGetPaddingPacked(node: YGNodeConstRef, edge: YGEdge) u64 {
1158
+ return packValue(c.YGNodeStyleGetPadding(node, edge));
1159
+ }
1160
+
1161
+ /// Gets position as packed value
1162
+ pub export fn ygNodeStyleGetPositionPacked(node: YGNodeConstRef, edge: YGEdge) u64 {
1163
+ return packValue(c.YGNodeStyleGetPosition(node, edge));
1164
+ }
1165
+
1166
+ /// Gets gap as packed value
1167
+ pub export fn ygNodeStyleGetGapPacked(node: YGNodeConstRef, gutter: YGGutter) u64 {
1168
+ return packValue(c.YGNodeStyleGetGap(node, gutter));
1169
+ }
1170
+
1171
+ /// Gets flex basis as packed value
1172
+ pub export fn ygNodeStyleGetFlexBasisPacked(node: YGNodeConstRef) u64 {
1173
+ return packValue(c.YGNodeStyleGetFlexBasis(node));
1174
+ }
1175
+
1176
+ //=============================================================================
1177
+ // UTILITY FUNCTIONS
1178
+ //=============================================================================
1179
+
1180
+ /// Checks if a float value is undefined
1181
+ pub export fn ygFloatIsUndefined(value: f32) bool {
1182
+ return c.YGFloatIsUndefined(value);
1183
+ }
1184
+
1185
+ /// Returns the Undefined value
1186
+ pub export fn ygUndefined() f32 {
1187
+ return c.YGUndefined;
1188
+ }
1189
+
1190
+ /// Rounds value to pixel grid
1191
+ pub export fn ygRoundValueToPixelGrid(value: f64, pointScaleFactor: f64, forceCeil: bool, forceFloor: bool) f32 {
1192
+ return c.YGRoundValueToPixelGrid(value, pointScaleFactor, forceCeil, forceFloor);
1193
+ }
1194
+
1195
+ /// Checks if node can use cached measurement
1196
+ pub export fn ygNodeCanUseCachedMeasurement(
1197
+ widthMode: YGMeasureMode,
1198
+ width: f32,
1199
+ heightMode: YGMeasureMode,
1200
+ height: f32,
1201
+ lastWidthMode: YGMeasureMode,
1202
+ lastWidth: f32,
1203
+ lastHeightMode: YGMeasureMode,
1204
+ lastHeight: f32,
1205
+ lastComputedWidth: f32,
1206
+ lastComputedHeight: f32,
1207
+ marginRow: f32,
1208
+ marginColumn: f32,
1209
+ config: YGConfigRef,
1210
+ ) bool {
1211
+ return c.YGNodeCanUseCachedMeasurement(
1212
+ widthMode,
1213
+ width,
1214
+ heightMode,
1215
+ height,
1216
+ lastWidthMode,
1217
+ lastWidth,
1218
+ lastHeightMode,
1219
+ lastHeight,
1220
+ lastComputedWidth,
1221
+ lastComputedHeight,
1222
+ marginRow,
1223
+ marginColumn,
1224
+ config,
1225
+ );
1226
+ }
1227
+
1228
+ //=============================================================================
1229
+ // TESTS
1230
+ //=============================================================================
1231
+
1232
+ test "basic yoga test" {
1233
+ const root = c.YGNodeNew();
1234
+ defer c.YGNodeFree(root);
1235
+
1236
+ c.YGNodeStyleSetFlexDirection(root, c.YGFlexDirectionRow);
1237
+ c.YGNodeStyleSetWidth(root, 100);
1238
+ c.YGNodeStyleSetHeight(root, 100);
1239
+
1240
+ const child0 = c.YGNodeNew();
1241
+ defer c.YGNodeFree(child0);
1242
+ c.YGNodeStyleSetFlexGrow(child0, 1);
1243
+ c.YGNodeStyleSetMargin(child0, c.YGEdgeRight, 10);
1244
+ c.YGNodeInsertChild(root, child0, 0);
1245
+
1246
+ const child1 = c.YGNodeNew();
1247
+ defer c.YGNodeFree(child1);
1248
+ c.YGNodeStyleSetFlexGrow(child1, 1);
1249
+ c.YGNodeInsertChild(root, child1, 1);
1250
+
1251
+ c.YGNodeCalculateLayout(root, c.YGUndefined, c.YGUndefined, c.YGDirectionLTR);
1252
+
1253
+ const child0Width = c.YGNodeLayoutGetWidth(child0);
1254
+ const child1Width = c.YGNodeLayoutGetWidth(child1);
1255
+
1256
+ // With flex grow 1 each and 10px margin, children should split 90px (100-10)
1257
+ try std.testing.expectApproxEqAbs(@as(f32, 45), child0Width, 0.1);
1258
+ try std.testing.expectApproxEqAbs(@as(f32, 45), child1Width, 0.1);
1259
+ }
1260
+
1261
+ fn dummyDirtiedFunc(node: YGNodeConstRef) callconv(.c) void {
1262
+ _ = node;
1263
+ }
1264
+
1265
+ test "cloned node does not share callback context" {
1266
+ // Setup: create node with callback context
1267
+ const original = c.YGNodeNew();
1268
+ defer c.YGNodeFree(original);
1269
+
1270
+ // Set up a trampoline callback (creates CallbackContext)
1271
+ ygNodeSetMeasureFuncTrampoline(original, null);
1272
+ const ctx = getOrCreateContext(original);
1273
+ ctx.measure_callback = @ptrFromInt(0xDEADBEEF); // Fake callback pointer
1274
+
1275
+ // Clone the node
1276
+ const cloned = ygNodeClone(original);
1277
+ defer ygNodeFree(cloned); // Should not crash
1278
+
1279
+ // Verify: cloned node has no context (null)
1280
+ const cloned_ctx = c.YGNodeGetContext(cloned);
1281
+ try std.testing.expectEqual(@as(?*anyopaque, null), cloned_ctx);
1282
+
1283
+ // Verify: original still has its context
1284
+ const original_ctx = c.YGNodeGetContext(original);
1285
+ try std.testing.expect(original_ctx != null);
1286
+ }
1287
+
1288
+ test "cloned node clears callbacks" {
1289
+ const original = c.YGNodeNew();
1290
+ defer ygNodeFree(original);
1291
+
1292
+ c.YGNodeSetMeasureFunc(original, &internalMeasureFunc);
1293
+ c.YGNodeSetBaselineFunc(original, &internalBaselineFunc);
1294
+ c.YGNodeSetDirtiedFunc(original, &dummyDirtiedFunc);
1295
+
1296
+ try std.testing.expect(c.YGNodeHasMeasureFunc(original));
1297
+ try std.testing.expect(c.YGNodeHasBaselineFunc(original));
1298
+ try std.testing.expect(c.YGNodeGetDirtiedFunc(original) != null);
1299
+
1300
+ const cloned = ygNodeClone(original);
1301
+ defer ygNodeFree(cloned);
1302
+
1303
+ try std.testing.expect(!c.YGNodeHasMeasureFunc(cloned));
1304
+ try std.testing.expect(!c.YGNodeHasBaselineFunc(cloned));
1305
+ try std.testing.expect(c.YGNodeGetDirtiedFunc(cloned) == null);
1306
+ }
1307
+
1308
+ test "freeing cloned node does not affect original callbacks" {
1309
+ const original = c.YGNodeNew();
1310
+ defer ygNodeFree(original);
1311
+
1312
+ // Set up callback on original
1313
+ const ctx = getOrCreateContext(original);
1314
+ ctx.measure_callback = @ptrFromInt(0xCAFEBABE);
1315
+
1316
+ // Clone and immediately free
1317
+ const cloned = ygNodeClone(original);
1318
+ ygNodeFree(cloned);
1319
+
1320
+ // Original's context should still be valid and unchanged
1321
+ const original_ctx = getContext(original);
1322
+ try std.testing.expect(original_ctx != null);
1323
+ try std.testing.expectEqual(@as(usize, 0xCAFEBABE), @intFromPtr(original_ctx.?.measure_callback));
1324
+ }