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}