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:
- The reconciler walks the committed VNode tree and builds a parallel
LayoutNodetree, copying the layout-relevant style props onto each node and attaching ameasurecallback to leaves whose natural size depends on their content (text, images). calculate_layoutis called with the viewport size; it recursively determines each node's(x, y, width, height)relative to its parent's coordinate space.- The reconciler walks the tree again and applies each computed frame
to the corresponding native view via the backend's
set_framemethod.
The algorithm supports:
- Flex containers:
flex_direction(row/columnand their reverse variants),justify_content(flex_start/center/flex_end/space_between/space_around/space_evenly),align_items(stretch/flex_start/center/flex_end), andalign_selfoverrides per child. - Sizing: explicit
width/height(numbers or percentages),min_width/max_width/min_height/max_heightconstraints,aspect_ratio, and content-based sizing via the optionalmeasurecallback. - Flex distribution:
flex(RN shorthand for grow factor withflex_basis: 0),flex_grow,flex_shrink,flex_basis. - Absolute positioning:
position: "absolute"withtop,right,bottom,leftinsets (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 withhorizontal/vertical/all/ per-edge keys, or per-edge keys directly),margin(same shape aspadding), and inter-childspacing(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 |
extract_layout_style |
Return a dict of the layout-relevant entries in |
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
|
|
children |
Ordered list of child |
|
measure |
Optional measure callback for leaf nodes whose natural
size depends on their content (e.g., text). Receives
|
|
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
|
required |
available_height
|
float
|
Available height in points. Pass
|
required |
Next steps¶
- Browse the supported style keys: Component properties.
- See how leaf widgets contribute their intrinsic size: Native views.
- Read the conceptual walkthrough: Layout engine.