cranpose_ui/widgets/flex_box.rs
1//! `FlexBox`: children along a main axis that grow, shrink and wrap, as in
2//! CSS flexbox.
3
4use cranpose_core::NodeId;
5use cranpose_ui_layout::Axis;
6
7use super::layout::{LayoutEnvironment, compose_layout};
8use crate::{composable, layout::flex_box::FlexBoxMeasurePolicy, modifier::Modifier};
9
10/// The main axis of a [`FlexBox`]. Compose's `FlexDirection`.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum FlexDirection {
13 /// Children from the start to the end of a row.
14 #[default]
15 Row,
16 /// Children from the end to the start of a row.
17 RowReverse,
18 /// Children from the top to the bottom of a column.
19 Column,
20 /// Children from the bottom to the top of a column.
21 ColumnReverse,
22}
23
24impl FlexDirection {
25 pub(crate) fn main_axis(self) -> Axis {
26 match self {
27 Self::Row | Self::RowReverse => Axis::Horizontal,
28 Self::Column | Self::ColumnReverse => Axis::Vertical,
29 }
30 }
31
32 pub(crate) fn is_reverse(self) -> bool {
33 matches!(self, Self::RowReverse | Self::ColumnReverse)
34 }
35}
36
37/// Whether a [`FlexBox`] starts new lines. Compose's `FlexWrap`.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub enum FlexWrap {
40 /// All children on one line.
41 #[default]
42 NoWrap,
43 /// New lines after the first, across the main axis.
44 Wrap,
45 /// New lines before the first: the last line is at the cross start.
46 WrapReverse,
47}
48
49impl FlexWrap {
50 pub(crate) fn wraps(self) -> bool {
51 self != Self::NoWrap
52 }
53}
54
55/// Where a line's children sit along the main axis when they leave room.
56/// Compose's `FlexJustifyContent`.
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
58pub enum FlexJustifyContent {
59 /// At the main start.
60 #[default]
61 Start,
62 /// At the main end.
63 End,
64 /// In the middle.
65 Center,
66 /// The first and last at the edges, the room between the others.
67 SpaceBetween,
68 /// Half as much room at the edges as between children.
69 SpaceAround,
70 /// As much room at the edges as between children.
71 SpaceEvenly,
72}
73
74/// Where children sit across their line. Compose's `FlexAlignItems`.
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
76pub enum FlexAlignItems {
77 /// At the cross start.
78 #[default]
79 Start,
80 /// At the cross end.
81 End,
82 /// In the middle.
83 Center,
84 /// As big as the line across it.
85 Stretch,
86 /// Their first baselines on one line; a child without one aligns its
87 /// bottom there.
88 Baseline,
89}
90
91/// Where one child sits across its line, over the box's
92/// [`FlexAlignItems`]. Compose's `FlexAlignSelf`.
93#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
94pub enum FlexAlignSelf {
95 /// As the box's [`FlexAlignItems`] says.
96 #[default]
97 Auto,
98 /// At the cross start.
99 Start,
100 /// At the cross end.
101 End,
102 /// In the middle.
103 Center,
104 /// As big as the line across it.
105 Stretch,
106 /// Its first baseline on the line's.
107 Baseline,
108}
109
110/// Where the lines of a wrapping [`FlexBox`] sit across it when they leave
111/// room. Compose's `FlexAlignContent`.
112#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
113pub enum FlexAlignContent {
114 /// At the cross start.
115 #[default]
116 Start,
117 /// At the cross end.
118 End,
119 /// In the middle.
120 Center,
121 /// Each line taller by an equal share of the room, up to the box's
122 /// smallest size.
123 Stretch,
124 /// The first and last at the edges, the room between the others.
125 SpaceBetween,
126 /// Half as much room at the edges as between lines.
127 SpaceAround,
128}
129
130/// A child's size along the main axis before it grows or shrinks. Compose's
131/// `FlexBasis`.
132#[derive(Clone, Copy, Debug, Default, PartialEq)]
133pub enum FlexBasis {
134 /// The size the child takes with no limit.
135 #[default]
136 Auto,
137 /// A size in dp.
138 Dp(f32),
139 /// A fraction, from 0 to 1, of the box's largest main size; the child's
140 /// own size when that is unbounded.
141 Percent(f32),
142}
143
144/// How a [`FlexBox`] lays its children out. Compose's `FlexBoxConfigScope`
145/// settings; the last call for a setting wins.
146#[derive(Clone, Copy, Debug, PartialEq)]
147pub struct FlexBoxConfig {
148 pub(crate) direction: FlexDirection,
149 pub(crate) wrap: FlexWrap,
150 pub(crate) justify_content: FlexJustifyContent,
151 pub(crate) align_items: FlexAlignItems,
152 pub(crate) align_content: FlexAlignContent,
153 pub(crate) max_items_in_each_line: usize,
154 pub(crate) row_gap: f32,
155 pub(crate) column_gap: f32,
156}
157
158impl Default for FlexBoxConfig {
159 fn default() -> Self {
160 Self {
161 direction: FlexDirection::Row,
162 wrap: FlexWrap::NoWrap,
163 justify_content: FlexJustifyContent::Start,
164 align_items: FlexAlignItems::Start,
165 align_content: FlexAlignContent::Start,
166 max_items_in_each_line: usize::MAX,
167 row_gap: 0.0,
168 column_gap: 0.0,
169 }
170 }
171}
172
173impl FlexBoxConfig {
174 /// A row that does not wrap, its children at the start and top.
175 pub fn new() -> Self {
176 Self::default()
177 }
178
179 /// Sets the main axis.
180 pub fn direction(mut self, direction: FlexDirection) -> Self {
181 self.direction = direction;
182 self
183 }
184
185 /// Sets whether children wrap onto new lines.
186 pub fn wrap(mut self, wrap: FlexWrap) -> Self {
187 self.wrap = wrap;
188 self
189 }
190
191 /// Sets where a line's children sit along the main axis.
192 pub fn justify_content(mut self, justify: FlexJustifyContent) -> Self {
193 self.justify_content = justify;
194 self
195 }
196
197 /// Sets where children sit across their line.
198 pub fn align_items(mut self, align: FlexAlignItems) -> Self {
199 self.align_items = align;
200 self
201 }
202
203 /// Sets where the lines sit across a wrapping box.
204 pub fn align_content(mut self, align: FlexAlignContent) -> Self {
205 self.align_content = align;
206 self
207 }
208
209 /// Sets how many children a line holds at most, at least 1; it applies
210 /// when the box wraps.
211 pub fn max_items_in_each_line(mut self, count: usize) -> Self {
212 self.max_items_in_each_line = count.max(1);
213 self
214 }
215
216 /// Sets the gap between rows, in dp: between the lines of a row box,
217 /// between the children of a column box.
218 pub fn row_gap(mut self, gap: f32) -> Self {
219 self.row_gap = gap.max(0.0);
220 self
221 }
222
223 /// Sets the gap between columns, in dp.
224 pub fn column_gap(mut self, gap: f32) -> Self {
225 self.column_gap = gap.max(0.0);
226 self
227 }
228
229 /// Sets both gaps, in dp.
230 pub fn gap(self, all: f32) -> Self {
231 self.row_gap(all).column_gap(all)
232 }
233}
234
235/// How one child of a [`FlexBox`] sizes and aligns. Compose's
236/// `FlexConfigScope` settings; give it with [`Modifier::flex`].
237#[derive(Clone, Copy, Debug, PartialEq)]
238pub struct FlexConfig {
239 pub(crate) align_self: FlexAlignSelf,
240 pub(crate) order: i32,
241 pub(crate) grow: Option<f32>,
242 pub(crate) shrink: f32,
243 pub(crate) basis: FlexBasis,
244}
245
246impl Default for FlexConfig {
247 fn default() -> Self {
248 Self {
249 align_self: FlexAlignSelf::Auto,
250 order: 0,
251 grow: None,
252 shrink: 1.0,
253 basis: FlexBasis::Auto,
254 }
255 }
256}
257
258impl FlexConfig {
259 /// A child that does not grow, shrinks with weight 1 and starts at the
260 /// size it takes with no limit.
261 pub fn new() -> Self {
262 Self::default()
263 }
264
265 /// Sets where this child sits across its line.
266 pub fn align_self(mut self, align: FlexAlignSelf) -> Self {
267 self.align_self = align;
268 self
269 }
270
271 /// Sets the child's place among its siblings: lower orders come first,
272 /// equal ones keep the composition order.
273 pub fn order(mut self, order: i32) -> Self {
274 self.order = order;
275 self
276 }
277
278 /// Sets the child's share of the room a line leaves; negative counts as
279 /// 0. Without one, a child grows by the fraction its fill modifier gives
280 /// along the main axis, and otherwise not.
281 pub fn grow(mut self, grow: f32) -> Self {
282 self.grow = Some(grow.max(0.0));
283 self
284 }
285
286 /// Sets how much the child gives up, in proportion to its basis, when a
287 /// line is too long; negative counts as 0.
288 pub fn shrink(mut self, shrink: f32) -> Self {
289 self.shrink = shrink.max(0.0);
290 self
291 }
292
293 /// Sets the child's size before it grows or shrinks.
294 pub fn basis(mut self, basis: FlexBasis) -> Self {
295 self.basis = basis;
296 self
297 }
298}
299
300impl Modifier {
301 /// Sets how this child of a [`FlexBox`] sizes and aligns. Compose's
302 /// `FlexBoxScope.flex`.
303 pub fn flex(self, config: FlexConfig) -> Self {
304 self.layout_data(config)
305 }
306}
307
308/// Lays children out along a main axis, as CSS flexbox does: they grow
309/// into the room a line leaves, shrink when it is too short, and wrap onto
310/// new lines when the config lets them. Compose's `FlexBox`.
311///
312/// # Example
313///
314/// ```rust
315/// use cranpose_ui::{
316/// widgets::{FlexBox, FlexBoxConfig, FlexConfig, FlexJustifyContent, FlexWrap},
317/// *,
318/// };
319///
320/// #[composable]
321/// fn Tags() {
322/// FlexBox(
323/// Modifier::empty().fill_max_width(),
324/// FlexBoxConfig::new()
325/// .wrap(FlexWrap::Wrap)
326/// .justify_content(FlexJustifyContent::SpaceBetween)
327/// .gap(8.0),
328/// || {
329/// Text("Rust", Modifier::empty(), TextStyle::default());
330/// Text(
331/// "Compose",
332/// Modifier::empty().flex(FlexConfig::new().grow(1.0)),
333/// TextStyle::default(),
334/// );
335/// },
336/// );
337/// }
338/// ```
339#[composable(no_skip)]
340pub fn FlexBox<F>(modifier: Modifier, config: FlexBoxConfig, content: F) -> NodeId
341where
342 F: FnMut() + 'static,
343{
344 let environment = LayoutEnvironment::current();
345 let policy = FlexBoxMeasurePolicy {
346 config,
347 layout_direction: environment.direction,
348 density: environment.density.density(),
349 };
350 compose_layout(modifier, &policy, environment, content)
351}