waterui_layout/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/// ```ignore
23/// // Push button to trailing edge
24/// hstack((
25/// text("Title"),
26/// spacer(),
27/// button("Done", || {}),
28/// ))
29///
30/// // Center content with equal spacing
31/// hstack((spacer(), text("Centered"), spacer()))
32///
33/// // Spacer with minimum length (never shrinks below 20pt)
34/// spacer_min(20.0)
35/// ```
36//
37// ═══════════════════════════════════════════════════════════════════════════
38// INTERNAL: Layout Contract for Backend Implementers
39// ═══════════════════════════════════════════════════════════════════════════
40//
41
42// Measurement: Returns (minLength, minLength) as intrinsic size
43// Layout: Expands to fill remaining surplus space during place() phase
44// Overflow: Collapses to minLength when space is insufficient
45//
46// ═══════════════════════════════════════════════════════════════════════════
47//
48#[derive(Debug, Clone, PartialEq)]
49pub struct Spacer {
50 min_length: f32,
51}
52
53impl Spacer {
54 /// Creates a new spacer with the specified minimum length.
55 #[must_use]
56 pub const fn new(min_length: f32) -> Self {
57 Self { min_length }
58 }
59
60 /// Creates a spacer with zero minimum length.
61 #[must_use]
62 pub const fn flexible() -> Self {
63 Self { min_length: 0.0 }
64 }
65}
66
67/// Layout implementation for a single spacer.
68///
69/// Spacers are greedy and will expand to fill all available space
70/// in the direction they are placed, respecting their minimum length.
71#[derive(Debug, Clone)]
72pub struct SpacerLayout {
73 min_length: f32,
74}
75
76impl Layout for SpacerLayout {
77 fn size_that_fits(&self, _proposal: ProposalSize, _children: &[&dyn SubView]) -> Size {
78 // Spacer reports its minimum length as intrinsic size (like SwiftUI)
79 // The parent stack will expand it to fill remaining space during place()
80 Size::new(self.min_length, self.min_length)
81 }
82
83 fn place(&self, _bounds: Rect, _children: &[&dyn SubView]) -> Vec<Rect> {
84 // Spacer has no children to place
85 Vec::new()
86 }
87}
88
89impl From<Spacer> for SpacerLayout {
90 fn from(spacer: Spacer) -> Self {
91 Self {
92 min_length: spacer.min_length,
93 }
94 }
95}
96
97raw_view!(Spacer, StretchAxis::MainAxis);
98
99/// Creates a flexible spacer with zero minimum length.
100///
101/// This spacer will expand to fill all available space in layouts.
102#[must_use]
103pub const fn spacer() -> Spacer {
104 Spacer::flexible()
105}
106
107/// Creates a spacer with a specific minimum length.
108///
109/// This spacer will expand to fill available space but never shrink below the minimum.
110#[must_use]
111pub const fn spacer_min(min_length: f32) -> Spacer {
112 Spacer::new(min_length)
113}