4 releases
Uses new Rust 2024
| 0.2.2 | Dec 14, 2025 |
|---|---|
| 0.2.1 | Dec 14, 2025 |
| 0.2.0 | Dec 13, 2025 |
| 0.1.0 | Sep 7, 2025 |
#847 in GUI
Used in 6 crates
(4 directly)
310KB
5.5K
SLoC
waterui-layout
Layout building blocks for arranging views in WaterUI applications.
Overview
waterui-layout provides the fundamental layout primitives used to compose user interfaces in WaterUI. Unlike traditional UI frameworks that manually calculate positions, this crate implements a declarative, constraint-based layout system inspired by SwiftUI's layout protocol. All components render to native platform widgets (UIKit/AppKit on Apple, Android View on Android) rather than drawing custom pixels.
The crate bridges the declarative View trait with the imperative, backend-driven layout pass through the Layout trait, enabling flexible composition of stacks, spacers, frames, and scrollable containers. All layout values use logical pixels (points/dp) matching design tool specifications exactly, with native backends handling density-aware conversion to physical pixels.
Installation
Add this to your Cargo.toml:
[dependencies]
waterui-layout = "0.1.0"
Or use the main waterui crate which re-exports all layout components:
[dependencies]
waterui = "0.2"
Quick Start
Here's a simple toolbar layout demonstrating horizontal stacking and spacers:
use waterui_layout::{stack, spacer};
use waterui_text::text;
use waterui_core::View;
pub fn toolbar() -> impl View {
stack::hstack((
text("WaterUI"),
spacer(),
text("v0.1"),
))
.spacing(8.0)
}
This creates a horizontal layout with "WaterUI" on the left, "v0.1" on the right, and flexible space between them.
Core Concepts
Layout Trait
The Layout trait defines how containers arrange their children through a two-phase protocol:
- Sizing Phase:
size_that_fits(proposal, children)determines the container's size given a parent proposal - Placement Phase:
place(bounds, children)positions children within the final bounds
Layouts can query children multiple times with different proposals to negotiate optimal sizing.
Stretch Behavior
Views communicate their flexibility through StretchAxis:
None- Content-sized, uses intrinsic dimensionsHorizontal- Expands width only (e.g., TextField)Vertical- Expands height onlyBoth- Greedy, fills all space (e.g., Spacer, Color)MainAxis- Stretches along parent's main axis (used by Spacer)CrossAxis- Stretches along parent's cross axis (used by Divider)
Stack containers distribute remaining space among stretching children proportionally.
Logical Pixels
All layout values use logical pixels (points/dp) - the same unit as Figma, Sketch, and Adobe XD:
spacing(8.0)= 8pt in design toolswidth(100.0)= 100pt/dp, same physical size across all devices- iOS/macOS: Uses points natively
- Android: Converts dp → pixels via
displayMetrics.density
This ensures pixel-perfect design implementation across platforms.
Examples
Building a Form Layout
use waterui_layout::{stack, padding::EdgeInsets};
use waterui_text::text;
use waterui_controls::TextField;
use waterui_reactive::binding;
use waterui_core::View;
pub fn login_form() -> impl View {
let username = binding("");
let password = binding("");
stack::vstack((
text("Login").size(24.0).bold(),
TextField::new(&username)
.label(text("Username"))
.prompt("Enter username"),
TextField::new(&password)
.label(text("Password"))
.prompt("Enter password")
.secure(true),
))
.alignment(stack::HorizontalAlignment::Leading)
.spacing(16.0)
.padding_with(EdgeInsets::all(20.0))
}
Creating a Toolbar with Spacers
use waterui_layout::{stack, spacer};
use waterui_controls::button;
use waterui_text::text;
use waterui_core::View;
pub fn app_toolbar() -> impl View {
stack::hstack((
button("Menu", || { /* action */ }),
spacer(),
text("My App").bold(),
spacer(),
button("Settings", || { /* action */ }),
))
.spacing(12.0)
}
Overlaying Content
use waterui_layout::{stack, overlay, stack::Alignment};
use waterui_graphics::Color;
use waterui_text::text;
use waterui_core::View;
pub fn badge_overlay() -> impl View {
overlay(
Color::blue().frame(100.0, 100.0),
text("5").foreground(Color::white()),
)
.alignment(Alignment::TopTrailing)
}
Scrollable Content
use waterui_layout::{scroll, stack};
use waterui_text::text;
use waterui_core::View;
pub fn scrollable_list() -> impl View {
scroll(
stack::vstack((
text("Item 1"),
text("Item 2"),
text("Item 3"),
// ... many more items
))
.spacing(10.0)
)
}
Responsive Layout with Frames
use waterui_layout::frame::Frame;
use waterui_layout::stack::Alignment;
use waterui_text::text;
use waterui_core::View;
pub fn constrained_content() -> impl View {
Frame::new(text("Limited Width"))
.min_width(100.0)
.max_width(300.0)
.height(50.0)
.alignment(Alignment::Center)
}
API Overview
Stack Containers
-
stack::hstack(content)- Arranges children horizontally left-to-right.spacing(f32)- Sets spacing between children.alignment(VerticalAlignment)- Sets vertical alignment (Top, Center, Bottom)
-
stack::vstack(content)- Arranges children vertically top-to-bottom.spacing(f32)- Sets spacing between children.alignment(HorizontalAlignment)- Sets horizontal alignment (Leading, Center, Trailing)
-
stack::zstack(content)- Overlays children in the same space.alignment(Alignment)- Sets 2D alignment for overlaid content
Layout Primitives
-
spacer()- Flexible space that expands to push views apart -
spacer_min(f32)- Spacer with minimum length -
ScrollView- Scrollable container for overflow contentscroll(content)- Vertical scrollingscroll_horizontal(content)- Horizontal scrollingscroll_both(content)- Bidirectional scrolling
-
Frame- Constrains child size with min/max/ideal dimensions.width(f32),.height(f32)- Sets ideal dimensions.min_width(f32),.max_width(f32)- Sets size constraints.alignment(Alignment)- Aligns child within frame
-
Padding- Insets child with edge spacingEdgeInsets::all(f32)- Equal padding on all edgesEdgeInsets::symmetric(vertical, horizontal)- Symmetric paddingEdgeInsets::new(top, bottom, leading, trailing)- Custom edges
Advanced Layouts
overlay(base, layer)- Layers content on top of base without affecting layout sizeOverlayLayout- Layout engine where base child dictates container sizeLazyContainer- Efficient container for dynamic collections withForEachIgnoreSafeArea- Metadata to extend content into safe area regionsEdgeSet- Bitflags for specifying which edges ignore safe area
Alignment Types
Alignment- 2D alignment (TopLeading, Top, TopTrailing, Leading, Center, Trailing, BottomLeading, Bottom, BottomTrailing)HorizontalAlignment- Leading, Center, TrailingVerticalAlignment- Top, Center, BottomAxis- Horizontal, Vertical (for stack direction)
Features
This crate supports optional features:
serde- Enables serialization/deserialization of layout types via serde
Enable features in your Cargo.toml:
[dependencies]
waterui-layout = { version = "0.1.0", features = ["serde"] }
Architecture Notes
Backend Integration
Layouts communicate with native backends through the Layout trait's protocol:
- Backend calls
size_that_fits(proposal, children)to measure - Backend calls
place(bounds, children)to get child rectangles - Backend renders native widgets at the calculated positions
The FFI layer in waterui-ffi handles the Rust ↔ Native boundary.
Performance Characteristics
- All layout calculations happen in Rust, then native backends cache results
- The
SubViewtrait enables measurement caching at the platform level - Lazy containers (
LazyContainer) defer child instantiation for large collections - Layout is pure (no side effects), enabling aggressive optimization by backends
Layout Compression
When children exceed available space:
- HStack: Compresses largest non-stretching children first, preserving small labels
- VStack: Children maintain intrinsic heights, may overflow bounds (scrollable)
- Minimum size enforcement prevents unreadable content (20pt minimum for compressed children)
This behavior matches native platform conventions for graceful degradation.
Dependencies
~1MB
~19K SLoC