Skip to content

Layout

The pure-Python flexbox engine that computes a frame (x, y, width, height) for every node in the rendered tree. The Reconciler runs calculate_layout after every commit and forwards the resulting frames to the platform handlers via set_frame.

For a conceptual overview, see Layout engine.

Pure-Python flexbox layout engine.

Computes absolute positions and sizes for every node in a layout tree based on CSS flexbox-inspired style properties. Inspired by Facebook's Yoga and React Native's layout system, but implemented entirely in Python so PythonNative does not depend on a native layout library.

The engine is invoked by the reconciler after each commit pass:

  1. The reconciler walks the committed VNode tree and builds a parallel LayoutNode tree, copying the layout-relevant style props onto each node and attaching a measure callback to leaves whose natural size depends on their content (text, images).
  2. calculate_layout is called with the viewport size; it recursively determines each node's (x, y, width, height) relative to its parent's coordinate space.
  3. The reconciler walks the tree again and applies each computed frame to the corresponding native view via the backend's set_frame method.

The algorithm supports:

  • Flex containers: flex_direction (row/column and their reverse variants), justify_content (flex_start / center / flex_end / space_between / space_around / space_evenly), align_items (stretch / flex_start / center / flex_end), and align_self overrides per child.
  • Sizing: explicit width / height (numbers or percentages), min_width / max_width / min_height / max_height constraints, aspect_ratio, and content-based sizing via the optional measure callback.
  • Flex distribution: flex (RN shorthand for grow factor with flex_basis: 0), flex_grow, flex_shrink, flex_basis.
  • Absolute positioning: position: "absolute" with top, right, bottom, left insets (numbers or percentages). Absolute children are positioned relative to the parent's padding box and do not participate in flex distribution.
  • Spacing: padding (scalar, dict with horizontal / vertical / all / per-edge keys, or per-edge keys directly), margin (same shape as padding), and inter-child spacing (alias: gap).
Example
from pythonnative.layout import LayoutNode, calculate_layout

root = LayoutNode(
    style={"flex_direction": "row", "padding": 8, "spacing": 4},
    children=[
        LayoutNode(style={"width": 80, "height": 40}),
        LayoutNode(style={"flex": 1, "height": 40}),
        LayoutNode(style={"width": 60, "height": 40}),
    ],
)
calculate_layout(root, 320, 200)
# root.children[0].x == 8, .width == 80
# root.children[1].x == 92, .width == 156  (filled by flex: 1)
# root.children[2].x == 252, .width == 60

Classes:

Name Description
LayoutNode

A node in the layout tree.

Functions:

Name Description
calculate_layout

Compute layout for node and all descendants, in place.

extract_layout_style

Return a dict of the layout-relevant entries in props.

Attributes:

Name Type Description
LAYOUT_STYLE_KEYS

Style keys that affect layout (and are consumed by the layout engine).

LAYOUT_STYLE_KEYS module-attribute

LAYOUT_STYLE_KEYS = frozenset({'width', 'height', 'min_width', 'max_width', 'min_height', 'max_height', 'flex', 'flex_grow', 'flex_shrink', 'flex_basis', 'align_self', 'position', 'top', 'right', 'bottom', 'left', 'margin', 'margin_top', 'margin_bottom', 'margin_left', 'margin_right', 'margin_horizontal', 'margin_vertical', 'padding', 'padding_top', 'padding_bottom', 'padding_left', 'padding_right', 'padding_horizontal', 'padding_vertical', 'flex_direction', 'justify_content', 'align_items', 'spacing', 'gap', 'aspect_ratio'})

Style keys that affect layout (and are consumed by the layout engine).

LayoutNode

LayoutNode(style: Optional[Dict[str, Any]] = None, children: Optional[List[LayoutNode]] = None, measure: Optional[MeasureFn] = None, user_data: Any = None)

A node in the layout tree.

Holds the layout-relevant style props, child layout nodes, and the computed output (x, y, width, height in points relative to the parent's coordinate space).

Attributes:

Name Type Description
style

Dict of layout-relevant style props (a subset of the element's full props; usually filtered through LAYOUT_STYLE_KEYS).

children

Ordered list of child LayoutNodes.

measure

Optional measure callback for leaf nodes whose natural size depends on their content (e.g., text). Receives (max_width, max_height) and returns (width, height). Either argument may be math.inf.

user_data

Free-form attribute the caller may use to associate each layout node with the corresponding native view; the engine itself does not inspect it.

x float

Computed x-coordinate relative to the parent's coordinate space.

y float

Computed y-coordinate relative to the parent's coordinate space.

width float

Computed width in points.

height float

Computed height in points.

calculate_layout

calculate_layout(node: LayoutNode, available_width: float, available_height: float) -> None

Compute layout for node and all descendants, in place.

Sizes the root from its own style and content. Callers that want the root to fill its viewport should wrap it in a synthetic outer node with explicit width / height (the Reconciler does this when running the layout pass after a commit).

Parameters:

Name Type Description Default
node LayoutNode

Root of the layout tree.

required
available_width float

Available width in points. Pass math.inf for unbounded (e.g., horizontal scroll).

required
available_height float

Available height in points. Pass math.inf for unbounded (e.g., vertical scroll).

required

extract_layout_style

extract_layout_style(props: Dict[str, Any]) -> Dict[str, Any]

Return a dict of the layout-relevant entries in props.

Used by the reconciler when building a LayoutNode from an element so the layout engine doesn't have to scan unrelated visual props.

Next steps