Skip to main content

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}