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}