Skip to main content

frus_widgets/
spacer.rs

1//! [`Spacer`]: room between two children of a row or a column, and nothing else.
2//!
3//! It is a gap that **takes what is left** rather than a fixed one, which is the
4//! difference between a header whose two ends stay at the two ends and one whose right
5//! half drifts as its left half changes. A fixed `SizedBox` between them is a guess about
6//! the parent's width; a `Spacer` is not a guess.
7//!
8//! ```ignore
9//! Flex::row()
10//!     .child(avatar)
11//!     .child(Spacer::new())        // ← pushes the rest to the far end
12//!     .child(other_avatars)
13//! ```
14//!
15//! **Two of them centre a thing**, which is the second reason it exists: a `Spacer` either
16//! side of a child, at whatever ratio, places that child anywhere along the axis without
17//! anyone measuring anything.
18//!
19//! It is [`Expanded`](crate::Expanded) around nothing
20//! (`spacer.dart:58`) — the same three properties, without the child. Written out here
21//! rather than as that composition because a wrapper with no content is a wrapper whose
22//! whole implementation is the box, and one struct is less to read than two.
23
24use frus_core::{Rect, Scene};
25use frus_layout::{Dimension, Style};
26
27use crate::interaction::Status;
28use crate::theme::Theme;
29use crate::widget::Widget;
30
31/// Flexible empty space along a row's or a column's main axis.
32pub struct Spacer {
33    flex: f32,
34}
35
36impl Spacer {
37    /// A gap that takes the whole of what is left (a flex factor of 1).
38    pub fn new() -> Self {
39        Self { flex: 1.0 }
40    }
41
42    /// Its share of the spare room, when there is more than one flexible child: two
43    /// spacers at `2.0` and `1.0` split it two to one, and a child between them lands a
44    /// third of the way along (`spacer.dart:47`).
45    ///
46    /// The reference asserts the factor is at least one, its being an `int` there. This
47    /// takes an `f32` and does not: a spacer at `0.5` beside one at `1.0` is a two-thirds
48    /// mark, which is a thing to want and which whole numbers can only express by making
49    /// them larger.
50    pub fn flex(mut self, flex: f32) -> Self {
51        self.flex = flex;
52        self
53    }
54}
55
56impl Default for Spacer {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl<Msg> Widget<Msg> for Spacer {
63    /// The three properties [`Expanded`](crate::Expanded) sets, with no child to set them
64    /// around: a basis of nothing, the grow factor, and **no automatic minimum** — the one
65    /// that is always forgotten, and without which a flex item is floored at its own
66    /// content. A spacer has no content, so that floor is zero either way; it is written
67    /// out so that the box is the same box, and stays the same box if the two ever have to
68    /// be compared.
69    fn style(&self) -> Style {
70        Style {
71            flex_grow: self.flex,
72            flex_shrink: self.flex,
73            flex_basis: Dimension::Length(0.0),
74            min_width: Dimension::Length(0.0),
75            min_height: Dimension::Length(0.0),
76            ..Default::default()
77        }
78    }
79
80    fn children(&self) -> &[Box<dyn Widget<Msg>>] {
81        &[]
82    }
83
84    /// Nothing. A spacer that painted would not be a spacer.
85    fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
86
87    fn on_click(&self) -> Option<Msg> {
88        None
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    /// The box, and the part of it that is easy to leave out: `flex_grow` alone floors a
97    /// flex item at its content, so the three properties go together or none of them
98    /// works. See [`crate::Expanded`], where the same omission was a real bug.
99    #[test]
100    fn a_spacer_is_a_flexible_box_with_no_floor() {
101        let style = Widget::<()>::style(&Spacer::new());
102        assert_eq!(style.flex_grow, 1.0);
103        assert_eq!(style.flex_basis, Dimension::Length(0.0));
104        assert_eq!(style.min_width, Dimension::Length(0.0));
105        assert_eq!(style.min_height, Dimension::Length(0.0));
106    }
107
108    /// And the share is the caller's, in whatever fraction they want it.
109    #[test]
110    fn two_spacers_split_the_room_in_the_ratio_they_were_given() {
111        assert_eq!(Widget::<()>::style(&Spacer::new().flex(2.0)).flex_grow, 2.0);
112        assert_eq!(Widget::<()>::style(&Spacer::new().flex(0.5)).flex_grow, 0.5);
113    }
114
115    /// It draws nothing, which is the whole contract.
116    #[test]
117    fn a_spacer_paints_nothing() {
118        let mut scene = Scene::new();
119        Widget::<()>::paint(
120            &Spacer::new(),
121            Rect::new(0.0, 0.0, 100.0, 20.0),
122            Status::default(),
123            &Theme::default(),
124            &mut scene,
125        );
126        assert!(scene.primitives().is_empty());
127    }
128}