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