waterui_layout/containers/spacer.rs
1//! Flexible layout gaps used by stacks and other containers.
2
3use alloc::vec::Vec;
4use waterui_core::raw_view;
5
6use crate::{Layout, ProposalSize, Rect, Size, StretchAxis, SubView};
7
8/// A flexible space that expands to push views apart.
9///
10/// Spacer adapts to its parent container: in `HStack` it expands horizontally,
11/// in `VStack` it expands vertically. Use it to push views to opposite edges
12/// or distribute space evenly.
13///
14/// # Layout Behavior
15///
16/// - **In `HStack`:** Expands horizontally only
17/// - **In `VStack`:** Expands vertically only
18/// - **In `ZStack`:** No expansion (falls back to minimum length)
19///
20/// # Examples
21///
22/// ```rust
23/// use waterui::layout::spacer::spacer_min;
24/// use waterui::prelude::*;
25///
26/// // Push button to trailing edge
27/// hstack((
28/// text("Title"),
29/// spacer(),
30/// button("Done").action(|| {}),
31/// ));
32///
33/// // Center content with equal spacing
34/// hstack((spacer(), text("Centered"), spacer()));
35///
36/// // Spacer with minimum length (never shrinks below 20pt)
37/// spacer_min(20.0);
38/// ```
39//
40// ═══════════════════════════════════════════════════════════════════════════
41// INTERNAL: Layout Contract for Backend Implementers
42// ═══════════════════════════════════════════════════════════════════════════
43//
44
45// Measurement: Returns (minLength, minLength) as intrinsic size
46// Layout: Expands to fill remaining surplus space during place() phase
47// Overflow: Collapses to minLength when space is insufficient
48//
49// ═══════════════════════════════════════════════════════════════════════════
50//
51#[derive(Debug, Clone, PartialEq)]
52pub struct Spacer {
53 min_length: f32,
54}
55
56impl Spacer {
57 /// Creates a new spacer with the specified minimum length.
58 #[must_use]
59 pub const fn new(min_length: f32) -> Self {
60 Self { min_length }
61 }
62
63 /// Creates a spacer with zero minimum length.
64 #[must_use]
65 pub const fn flexible() -> Self {
66 Self { min_length: 0.0 }
67 }
68}
69
70/// Layout implementation for a single spacer.
71///
72/// Spacers are greedy and will expand to fill all available space
73/// in the direction they are placed, respecting their minimum length.
74#[derive(Debug, Clone)]
75pub struct SpacerLayout {
76 min_length: f32,
77}
78
79impl Layout for SpacerLayout {
80 fn size_that_fits(&self, _proposal: ProposalSize, _children: &[&dyn SubView]) -> Size {
81 // Spacer reports its minimum length as intrinsic size (like SwiftUI)
82 // The parent stack will expand it to fill remaining space during place()
83 Size::new(self.min_length, self.min_length)
84 }
85
86 fn place(&self, _bounds: Rect, _children: &[&dyn SubView]) -> Vec<Rect> {
87 // Spacer has no children to place
88 Vec::new()
89 }
90}
91
92impl From<Spacer> for SpacerLayout {
93 fn from(spacer: Spacer) -> Self {
94 Self {
95 min_length: spacer.min_length,
96 }
97 }
98}
99
100raw_view!(Spacer, StretchAxis::MainAxis);
101
102/// Creates a flexible spacer with zero minimum length.
103///
104/// This spacer will expand to fill all available space in layouts.
105#[must_use]
106pub const fn spacer() -> Spacer {
107 Spacer::flexible()
108}
109
110/// Creates a spacer with a specific minimum length.
111///
112/// This spacer will expand to fill available space but never shrink below the minimum.
113#[must_use]
114pub const fn spacer_min(min_length: f32) -> Spacer {
115 Spacer::new(min_length)
116}