Layout System
Ribir's layout system uses a "Constraints Down, Size Up" single-pass model. This is very similar to Flutter's layout model and aims to achieve efficient and flexible UI layout.
Core Principles
- Constraints Down: Parent Widgets pass layout constraints (Constraints) down to child Widgets. These constraints define the minimum and maximum width and height that child Widgets can occupy.
- Size Up: Child Widgets calculate their own size based on the received constraints and return the final determined size (Size) to the parent Widget.
- Parent Sets Position: After receiving the child Widget's size, the parent Widget determines the child Widget's position in its own coordinate system.
BoxClamp
Layout constraints are represented by the BoxClamp struct. It contains four values:
min_width,max_widthmin_height,max_height
BoxClamp defines an allowed size range. The final size of a child Widget must be within this range.
- Loose Constraints:
minis 0,maxis some finite value. Child Widgets can be any size between 0 and max. - Tight Constraints:
minequalsmax. Child Widgets are forced to a specific size. - Unbounded Constraints:
maxis infinity. Child Widgets can extend infinitely (usually appears in scrolling containers).
Layout Process
Each Widget must implement the perform_layout method in the Render trait:
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size
In this method, the Widget needs to do three things:
- Layout children: Iterate through its child nodes, calculate a new
BoxClampfor each child (based on the incomingclampand its own layout logic), and callctx.perform_child_layout(child, child_clamp). - Determine position: Get the
Sizereturned by the child node, and set the child node's position according to the layout logicctx.update_position(child, position). - Return size: Calculate and return its own final
Size, and this size must satisfy the incomingclampconstraint.
Using the clamp Attribute to Intervene in Layout
Ribir provides a built-in clamp attribute that allows you to directly modify the parent constraints a Widget receives when declaring it. This is implemented behind the scenes by wrapping a ConstrainedBox.
use ribir::prelude::*;
fn example() -> Widget<'static> {
fn_widget! {
@Container {
size: Size::new(100., 100.),
background: Color::RED,
// Force constraint: no matter what constraint the parent gives, the Container's width must be between 50 and 200
clamp: BoxClamp {
min: Size::new(50., 0.),
max: Size::new(200., f32::INFINITY),
}
}
}.into_widget()
}
Note: The clamp attribute's role is to further restrict the constraints passed down from the parent, taking the intersection.
Common Layout Widgets
- Row / Column: Linear layout. Provides unbounded constraints in the main axis direction (if scrolling or adaptive is allowed), and passes loose or strict constraints in the cross axis.
- Stack: Stack layout. Passes the same constraints to all non-positioned child nodes.
- SizedBox: Forces child nodes to a specific size (by applying Tight Constraints).