Skip to content

feat: Complete C# port of Facebook Yoga flexbox layout engine#5

Merged
StevenTCramer merged 62 commits into
masterfrom
Cramer/2025-11-21/dev
Jan 22, 2026
Merged

feat: Complete C# port of Facebook Yoga flexbox layout engine#5
StevenTCramer merged 62 commits into
masterfrom
Cramer/2025-11-21/dev

Conversation

@StevenTCramer

Copy link
Copy Markdown
Contributor

Summary

  • Complete C# port of Facebook's Yoga flexbox layout engine (v3.x)
  • Implements all core types: enums, numeric types (FloatOptional), style system, node management, and layout algorithms
  • Comprehensive test suite with 700+ passing tests covering all components
  • Pure C# implementation with no native dependencies - enables cross-platform UI layout calculations

Key Components

Types & Enums: Align, BoxSizing, Dimension, Direction, Display, Edge, Errata, FlexDirection, Gutter, Justify, MeasureMode, NodeType, Overflow, PhysicalEdge, PositionType, SizingMode, Unit, Wrap

Style System: StyleLength, StyleSizeLength, StyleValueHandle, StyleValuePool, SmallValueBuffer, Style class with all CSS flexbox properties

Node System: Node, LayoutResults, LayoutableChildren (display:contents support), CachedMeasurement, Config

Algorithm: FlexBasis, FlexLine, FlexDistribution, JustifyContent, BoundAxis, Baseline, Cache, PixelGrid, AbsoluteLayout, MeasureNode, TrailingPosition, CalculateLayout (main entry point)

Testing: 700+ unit tests covering all components

Status

  • Core implementation complete
  • 20 integration tests failing (child layout computation under investigation)
  • Ready for review while integration issues are resolved

StevenTCramer and others added 30 commits December 15, 2025 09:38
Implement two Yoga features to align with the reference C++ implementation:

- AlignContent.SpaceEvenly: distributes flex lines with equal spacing
  before, between, and after all lines (gap = freeSpace / (lineCount + 1))

- Display.Contents: nodes don't generate a box but their children
  participate in layout as if they were direct children of the parent.
  Adds ContentsChildrenCount tracking and GetLayoutChildren() method
  for efficient tree flattening.

Also updates agent guidelines to emphasize the project objective of
porting Yoga C++ to C# with minimal deviation from the source.

Closes #051, #052

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
The incremental task-based approach resulted in disorganized,
non-dependency-ordered development. Starting over with a cleaner approach.
These tasks are deprecated as part of the fresh start. The previous
task breakdown was not in proper dependency order.
Port foundational types from Facebook Yoga C++ to C#:
- YogaEnums utilities: OrdinalCount, BitCount, ToUnderlying, Ordinals
- SmallValueBuffer: memory-efficient 32/64-bit value storage
- Comparison: float utilities (IsUndefined, IsDefined, InexactEquals)
- FloatOptional: optional float using NaN sentinel
- All 17 Yoga enums with ToCssString() extensions

180 tests passing. Move completed tasks 102-106 to done.
Provide core dimension value type needed for style properties that
require unit support (point, percent, auto, undefined, etc.). This
enables subsequent layout algorithm work that depends on typed values.

- Port YGValue readonly struct with C++ equality semantics
- Add YGValueUtilities with YGUndefined constant and helper
- Support all 7 unit types (Point, Percent, Auto, Undefined, FitContent, MaxContent, Stretch)
- 37 new tests (217 total), all passing

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
…ks 108, 109, 115)

- StyleLength: CSS length values with point/percent/auto/undefined units
- StyleSizeLength: extended length values with max-content/fit-content/stretch
- PhysicalEdge: enum for physical edges (left/top/right/bottom)
- SizingMode: CSS auto box sizes with MeasureMode conversions

259 tests passing
…sks 111, 117, 118)

Implement core infrastructure types for the Yoga layout algorithm port:

- FlexDirectionUtils: Extension methods for direction resolution, edge mapping,
  and dimension lookup based on flex direction
- StyleValueHandle: Compact 16-bit handle for style values supporting points,
  percentages, numbers, auto, and keywords with optional pool indexing
- CachedMeasurement: Measurement cache entry for avoiding redundant layout
  calculations with undefined-aware equality comparison

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Complete the following Yoga C# port tasks:
- Task 111: StyleValueHandle
- Task 117: FlexDirectionUtils
- Task 118: CachedMeasurement

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Enable nullable reference type analysis attributes project-wide
to support proper null-state static analysis in upcoming code.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
…116)

StyleValuePool provides memory-efficient storage for sparse collections of
style lengths and numbers using StyleValueHandle references. Small integer
values are packed inline while larger values use a buffer pool.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
The StyleValuePool implementation was completed in the previous commit.
This moves the corresponding kanban card to reflect the completed status.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Port yoga/event/event.h and event.cpp to C#:
- Add LayoutType, LayoutPassReason enums
- Add LayoutData class for layout pass statistics
- Add EventType enum and IEventData interface
- Add typed event data record structs
- Add YogaEvent static publisher class
- Add comprehensive tests for all event types
Port yoga/config/Config.h/.cpp to C#:
- Add Config class with errata, experimental features, point scale factor
- Add ExperimentalFeatureSet readonly struct
- Add clone node callback and logger support
- Add version tracking for config invalidation
- Add comprehensive tests
Port yoga/node/LayoutableChildren.h to C#:
- Add ILayoutableNode interface for node abstraction
- Add generic LayoutableChildren<T> struct with IEnumerable<T>
- Implement display:contents flattening with backtrack stack
- Add comprehensive tests for all iteration scenarios
… 119)

Port LayoutResults from C++ Yoga to store computed layout output for nodes
including position, dimensions, margins, borders, padding, and cached
measurements for layout optimization.

Key features:
- Position storage using PhysicalEdge (Left, Top, Right, Bottom)
- Three dimension types: computed, measured, and raw (pre-rounding)
- Cache fields for layout optimization (generation count, config version)
- 8 cached measurements per node (based on Yoga empirical data)
- Full equality comparison with inexact float matching

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Complete tasks 119 (LayoutResults), 120 (Cache utilities), and 121
(Style) by moving them to done. Begin work on task 122 (Node) by
moving it to in-progress.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Implement the Node class, the central component of the Flexbox layout system.
This is a direct port from Yoga's Node.h/Node.cpp (Level 7 complexity).

Node is the primary building block for layout trees, containing:
- Tree structure (Owner, Children list)
- Style and LayoutResults instances
- State flags (hasNewLayout, isDirty, isReferenceBaseline)
- Callbacks (MeasureFunc, BaselineFunc, DirtiedFunc)
- Child management (insert, remove, replace, clear)
- Dirty propagation (MarkDirtyAndPropagate)
- Flex resolution (ResolveFlexGrow, ResolveFlexShrink, ProcessFlexBasis)
- Position calculation (SetPosition, RelativePosition)
- Cloning support (Clone, CloneChildrenIfNeeded)

Test coverage: 501 tests pass covering default values, dirty tracking,
child management, cloning, flex resolution, and measure functions.

Task: 122

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Task 122 (Node class implementation) completed.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Change NodeTests namespace from 'NodeNs' suffix to 'Node_' suffix
to align with the project's test namespace convention.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
StevenTCramer and others added 29 commits December 16, 2025 10:29
- AlignUtilsTests.cs: Tests for ResolveChildAlignment and FallbackAlignment
- TrailingPositionTests.cs: Tests for GetPositionOfOppositeEdge, SetChildTrailingPosition, NeedsTrailingPosition
- PixelGridTests.cs: Tests for RoundValueToPixelGrid (scale factors, ceil/floor, negative, NaN)
- BaselineTests.cs: Tests for IsBaselineLayout and CalculateBaseline
- FlexLineTests.cs: Tests for FlexLine and FlexLineRunningLayout
- BoundAxisTests.cs: Tests for PaddingAndBorderForAxis, BoundAxisWithinMinAndMax, BoundAxisValue

108 new algorithm tests, all passing.
- Add PendingChild property to store child that triggered line break
- Add pendingChild parameter to CalculateFlexLine for processing
  children that didn't fit on the previous line
- Refactor ProcessChild as inner function for cleaner code
- Update task 128 with verification results
- LayoutAbsoluteChild: handles absolute child layout with insets
- LayoutAbsoluteDescendants: recursive layout of absolute children
- Supports errata: AbsolutePositionWithoutInsetsExcludesPadding,
  AbsolutePercentAgainstInnerSize
- Uses delegate pattern for CalculateLayoutInternal callback
The CalculateLayout task (~32,000+ lines to port) was too large for a
single work item. Breaking it into logical subtasks enables better
progress tracking and parallel work.

Subtasks: data structures, helper functions, measurement, flex basis,
flex lines, space distribution, justification, core algorithm, caching,
and integration testing.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
…ne files

Reorganize subtasks 125a-125j into independent task files (129-138)
to improve discoverability and align with the project's flat kanban
structure. Each task can now be tracked and managed independently.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
All data structures required by the CalculateLayout algorithm are verified:
- FlexLineRunningLayout struct (Algorithm/FlexLine.cs)
- FlexLine class with CalculateFlexLine method (Algorithm/FlexLine.cs)
- LayoutData class (Event/Event.cs)
- LayoutPassReason enum (Event/Event.cs)
- LayoutType enum (Event/Event.cs)
- Unit tests for all structures (FlexLineTests.cs, EventTests.cs)
Add LayoutHelpers.cs with constrainMaxSizeForMode, isFixedSize,
calculateAvailableInnerDimension, zeroOutLayoutRecursively, and
cleanupContentsNodesRecursively functions needed for the main
layout algorithm implementation.

Task: 130

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
All layout helper functions have been successfully ported from C++ Yoga:
- constrainMaxSizeForMode
- isFixedSize
- calculateAvailableInnerDimension
- zeroOutLayoutRecursively
- cleanupContentsNodesRecursively

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Enable measuring nodes during layout by implementing three functions:
- MeasureNodeWithMeasureFunc for nodes with custom callbacks (e.g., text)
- MeasureNodeWithoutChildren for leaf nodes using padding+border
- MeasureNodeWithFixedSize as fast path optimization for fixed dimensions

These functions support the layout algorithm's need to determine node
sizes when explicit dimensions aren't provided.

Refs: #131

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Task 131 (Measurement Functions) has been completed with all
measurement functions ported from C++ Yoga and tested.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Enable proper flex sizing by implementing computeFlexBasisForChild and
computeFlexBasisForChildren. Uses delegate pattern for layout integration
to avoid circular dependencies.

Closes #132

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Flex basis calculation implementation is complete with all tests passing.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Flex line calculation implementation is complete with unit tests for
FlexLine and FlexLineRunningLayout structs. Integration tests will be
ported in task 138 after the full layout algorithm is implemented.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
Implements the two-pass flex-grow/flex-shrink algorithm:
- DistributeFreeSpaceFirstPass: Detects items hitting min/max constraints
- DistributeFreeSpaceSecondPass: Distributes remaining space to flex items
- ResolveFlexibleLength: Orchestrates the two-pass algorithm

Ported from yoga/algorithm/CalculateLayout.cpp lines 622-980
Implements JustifyMainAxis function that:
- Positions flex items along main axis based on justify-content
- Handles all justify values: FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly
- Handles overflow fallback alignment
- Handles auto margins
- Calculates cross dimension for baseline-aligned containers
- Includes gap handling

Ported from yoga/algorithm/CalculateLayout.cpp lines 982-1156
Implements CalculateLayoutCore with all 11 steps of the flexbox algorithm:
- STEP 1: Axis resolution and padding/border/margin setup
- STEP 2: Available dimension calculation
- STEP 3: Flex basis computation for children
- STEP 4: Flex line collection
- STEP 5: Flexible length resolution
- STEP 6: Main-axis justification and cross-axis sizing
- STEP 7: Cross-axis alignment (including stretch re-layout)
- STEP 8: Multi-line content alignment
- STEP 9: Final dimension computation
- STEP 10: Trailing position setting
- STEP 11: Absolute child positioning

Ported from yoga/algorithm/CalculateLayout.cpp lines 1214-2139
Port calculateLayout and calculateLayoutInternal from C++ Yoga to provide
the public API for layout calculation with proper cache management.

- Add global generation counter using Interlocked.Increment for thread safety
- Implement Calculate() as main entry point with sizing mode resolution
- Implement CalculateLayoutInternal() cache wrapper with cache invalidation
- Initialize FlexBasis and AbsoluteLayout delegates for algorithm composition
- Add 28 integration tests covering basic layout, child positioning,
  idempotency, measure cache, RTL, flex grow/shrink, padding/margin,
  justify content, align items, absolute positioning, flex wrap, and
  pixel grid rounding

Test status: 736 tests pass, 20 fail (child layout issues to debug next)

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
…lization

- Add performLayout parameter to FlexBasis.ComputeFlexBasisForChildren
- Call child.SetPosition() during flex basis computation when performing layout
- Fix parameter ordering in ComputeFlexBasisForChild call to match C++ Yoga
- Add null validation in CalculateLayoutCore.CalculateLayoutInternal
- Update FlexBasisTests to include new performLayout parameter

This is partial progress on Task 138 (Integration Testing). 20 integration
tests still fail - children's dimensions remain NaN and positions are 0.
Further investigation needed in FlexDistribution and recursive layout calls.

🤖 Generated with [opencode](https://opencode.ai)

Co-Authored-By: opencode <[email protected]>
@StevenTCramer StevenTCramer merged commit 0669210 into master Jan 22, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant