tessera_ui_basic_components/
spacer.rs

1use derive_builder::Builder;
2use tessera_ui::{ComputedData, Constraint, DimensionValue, Dp, Px};
3use tessera_ui_macros::tessera;
4
5/// Arguments for the Spacer component.
6#[derive(Default, Clone, Copy, Builder)]
7#[builder(pattern = "owned")]
8pub struct SpacerArgs {
9    /// The desired width behavior of the spacer.
10    /// Defaults to Fixed(Px(0)). Use Fill { min: None, max: None } for an expanding spacer.
11    #[builder(default = "DimensionValue::Fixed(Px(0))")]
12    pub width: DimensionValue,
13    /// The desired height behavior of the spacer.
14    /// Defaults to Fixed(Px(0)). Use Fill { min: None, max: None } for an expanding spacer.
15    #[builder(default = "DimensionValue::Fixed(Px(0))")]
16    pub height: DimensionValue,
17}
18
19impl SpacerArgs {
20    /// Creates a spacer that tries to fill available space in both dimensions.
21    pub fn fill_both() -> Self {
22        SpacerArgsBuilder::default()
23            .width(DimensionValue::Fill {
24                min: None,
25                max: None,
26            })
27            .height(DimensionValue::Fill {
28                min: None,
29                max: None,
30            })
31            .build()
32            .unwrap() // build() should not fail with these defaults
33    }
34
35    /// Creates a spacer that tries to fill available width.
36    pub fn fill_width() -> Self {
37        SpacerArgsBuilder::default()
38            .width(DimensionValue::Fill {
39                min: None,
40                max: None,
41            })
42            .height(DimensionValue::Fixed(Px(0))) // Default height if only filling width
43            .build()
44            .unwrap()
45    }
46
47    /// Creates a spacer that tries to fill available height.
48    pub fn fill_height() -> Self {
49        SpacerArgsBuilder::default()
50            .width(DimensionValue::Fixed(Px(0))) // Default width if only filling height
51            .height(DimensionValue::Fill {
52                min: None,
53                max: None,
54            })
55            .build()
56            .unwrap()
57    }
58}
59
60impl From<Dp> for SpacerArgs {
61    fn from(value: Dp) -> Self {
62        SpacerArgsBuilder::default()
63            .width(DimensionValue::Fixed(value.to_px()))
64            .height(DimensionValue::Fixed(value.to_px()))
65            .build()
66            .unwrap()
67    }
68}
69
70impl From<Px> for SpacerArgs {
71    fn from(value: Px) -> Self {
72        SpacerArgsBuilder::default()
73            .width(DimensionValue::Fixed(value))
74            .height(DimensionValue::Fixed(value))
75            .build()
76            .unwrap()
77    }
78}
79
80/// A component that creates an empty space in the layout.
81///
82/// `Spacer` can be used to add gaps between other components or to fill available space.
83/// Its behavior is defined by the `width` and `height` `DimensionValue` parameters.
84#[tessera]
85pub fn spacer(args: impl Into<SpacerArgs>) {
86    let args: SpacerArgs = args.into();
87
88    measure(Box::new(move |input| {
89        let spacer_intrinsic_constraint = Constraint::new(args.width, args.height);
90        let effective_spacer_constraint =
91            spacer_intrinsic_constraint.merge(input.parent_constraint);
92
93        let final_spacer_width = match effective_spacer_constraint.width {
94            DimensionValue::Fixed(w) => w,
95            DimensionValue::Wrap { min, .. } => min.unwrap_or(Px(0)), // Spacer has no content, so it's its min or 0.
96            DimensionValue::Fill { min, max: _ } => {
97                // If the effective constraint is Fill, it means the parent allows filling.
98                // However, a simple spacer has no content to expand beyond its minimum.
99                // The actual size it gets if parent is Fill and allocates space
100                // would be determined by the parent's layout logic (e.g. row/column giving it a Fixed size).
101                // Here, based purely on `effective_spacer_constraint` being Fill,
102                // it should take at least its `min` value.
103                // If parent constraint was Fixed(v), merge would result in Fixed(v.clamp(min, max)).
104                // If parent was Wrap, merge would result in Fill{min,max} (if spacer was Fill).
105                // If parent was Fill{p_min, p_max}, merge would result in Fill{combined_min, combined_max}.
106                // In all Fill cases, the spacer itself doesn't "push" for more than its min.
107                min.unwrap_or(Px(0))
108            }
109        };
110
111        let final_spacer_height = match effective_spacer_constraint.height {
112            DimensionValue::Fixed(h) => h,
113            DimensionValue::Wrap { min, .. } => min.unwrap_or(Px(0)),
114            DimensionValue::Fill { min, max: _ } => min.unwrap_or(Px(0)),
115        };
116
117        Ok(ComputedData {
118            width: final_spacer_width,
119            height: final_spacer_height,
120        })
121    }));
122}