embedded_ui/
layout.rs

1use alloc::vec::Vec;
2use embedded_graphics::geometry::Point;
3
4use crate::{
5    align::{Alignment, Axis, HorizontalAlign, VerticalAlign},
6    el::El,
7    event::Event,
8    padding::Padding,
9    render::Renderer,
10    size::{Bounds, Length, Size},
11    state::StateNode,
12    ui::UiCtx,
13    widget::Widget,
14};
15
16/// Positioning strategy, don't confuse with logic of CSS position.
17/// For now, [`Position::Relative`] means "relative to the parent".
18/// [`Position::Absolute`] is relative to viewport.
19#[derive(Clone, Copy)]
20pub enum Position {
21    Relative,
22    Absolute,
23}
24
25#[derive(Clone, Copy)]
26pub struct Viewport {
27    pub size: Size,
28}
29
30#[derive(Clone)]
31pub struct LayoutNode {
32    position: Position,
33    bounds: Bounds,
34    children: Vec<LayoutNode>,
35}
36
37impl LayoutNode {
38    pub fn new(size: Size) -> Self {
39        Self {
40            position: Position::Relative,
41            bounds: Bounds { position: Point::zero(), size },
42            children: vec![],
43        }
44    }
45
46    pub fn with_children(size: Size, children: impl IntoIterator<Item = LayoutNode>) -> Self {
47        Self {
48            position: Position::Relative,
49            bounds: Bounds { position: Point::zero(), size },
50            children: children.into_iter().collect(),
51        }
52    }
53
54    pub fn absolute(size: Size) -> Self {
55        Self {
56            position: Position::Absolute,
57            bounds: Bounds { position: Point::zero(), size },
58            children: vec![],
59        }
60    }
61
62    pub fn position(&self) -> Position {
63        self.position
64    }
65
66    pub fn moved(mut self, to: impl Into<Point>) -> Self {
67        self.move_mut(to);
68        self
69    }
70
71    pub fn move_mut(&mut self, to: impl Into<Point>) -> &mut Self {
72        self.bounds.position = to.into();
73        self
74    }
75
76    pub fn align_mut(
77        &mut self,
78        horizontal: Alignment,
79        vertical: Alignment,
80        parent_size: Size,
81    ) -> &mut Self {
82        match horizontal {
83            Alignment::Start => {},
84            Alignment::Center => {
85                self.bounds.position.x +=
86                    (parent_size.width as i32 - self.bounds.size.width as i32) / 2;
87            },
88            Alignment::End => {
89                self.bounds.position.x += parent_size.width as i32 - self.bounds.size.width as i32;
90            },
91        }
92
93        match vertical {
94            Alignment::Start => {},
95            Alignment::Center => {
96                self.bounds.position.y +=
97                    (parent_size.height as i32 - self.bounds.size.height as i32) / 2;
98            },
99            Alignment::End => {
100                self.bounds.position.y += parent_size.width as i32 - self.bounds.size.width as i32;
101            },
102        }
103
104        self
105    }
106
107    pub fn aligned(
108        mut self,
109        horizontal: Alignment,
110        vertical: Alignment,
111        parent_size: Size,
112    ) -> Self {
113        self.align_mut(horizontal, vertical, parent_size);
114        self
115    }
116
117    pub fn size(&self) -> Size {
118        self.bounds.size
119    }
120}
121
122impl Default for LayoutNode {
123    fn default() -> Self {
124        Self::new(Size::zero())
125    }
126}
127
128#[derive(Clone)]
129pub struct Layout<'a> {
130    /// Position in viewport (display)
131    viewport_position: Point,
132    node: &'a LayoutNode,
133}
134
135impl<'a> Layout<'a> {
136    pub fn new(node: &'a LayoutNode) -> Self {
137        Self { viewport_position: node.bounds.position.into(), node }
138    }
139
140    pub fn with_offset(offset: Point, node: &'a LayoutNode) -> Self {
141        let bounds = node.bounds;
142
143        let offset = match node.position {
144            Position::Relative => offset,
145            Position::Absolute => Point::zero(),
146        };
147
148        Self { viewport_position: bounds.position + offset, node }
149    }
150
151    /// Get iterator of children with offset relative to parent
152    pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
153        self.node
154            .children
155            .iter()
156            .map(move |child| Layout::with_offset(self.viewport_position, child))
157    }
158
159    /// Bounds in viewport
160    pub fn bounds(&self) -> Bounds {
161        Bounds { position: self.viewport_position, size: self.node.bounds.size }
162    }
163
164    pub fn sized(
165        limits: &Limits,
166        size: impl Into<Size<Length>>,
167        position: Position,
168        viewport: &Viewport,
169        content_limits: impl FnOnce(&Limits) -> Size,
170    ) -> LayoutNode {
171        let size = size.into();
172
173        let limits = limits
174            .for_position(position, viewport)
175            .limit_width(size.width)
176            .limit_height(size.height);
177        let content_size = content_limits(&limits);
178
179        LayoutNode::new(limits.resolve_size(size.width, size.height, content_size))
180    }
181
182    pub fn container(
183        limits: &Limits,
184        size: impl Into<Size<Length>>,
185        position: Position,
186        viewport: &Viewport,
187        padding: impl Into<Padding>,
188        border: impl Into<Padding>,
189        content_align_h: Alignment,
190        content_align_v: Alignment,
191        content_layout: impl FnOnce(&Limits) -> LayoutNode,
192        // place_content: impl FnOnce(LayoutNode, Size) -> LayoutNode,
193    ) -> LayoutNode {
194        let size = size.into();
195        let padding = padding.into();
196        let border = border.into();
197
198        let full_padding = padding + border;
199
200        let limits = limits
201            .for_position(position, viewport)
202            .limit_width(size.width)
203            .limit_height(size.height);
204        let content = content_layout(&limits.shrink(full_padding));
205        let fit_padding = full_padding.fit(content.size(), limits.max());
206
207        let size = limits.shrink(fit_padding).resolve_size(size.width, size.height, content.size());
208        let content_offset = full_padding.top_left();
209
210        let content = content.moved(content_offset).aligned(content_align_h, content_align_v, size);
211
212        LayoutNode::with_children(size.expand(fit_padding), vec![content])
213    }
214
215    pub fn flex<Message, R: Renderer, E: Event, S>(
216        ctx: &mut UiCtx<Message>,
217        state_tree: &mut StateNode,
218        styler: &S,
219        axis: Axis,
220        limits: &Limits,
221        size: impl Into<Size<Length>>,
222        position: Position,
223        viewport: &Viewport,
224        padding: impl Into<Padding>,
225        gap: u32,
226        align: Alignment,
227        children: &[El<'_, Message, R, E, S>],
228    ) -> LayoutNode {
229        let size = size.into();
230        let padding = padding.into();
231
232        let limits = limits
233            .for_position(position, viewport)
234            .limit_width(size.width)
235            .limit_height(size.height)
236            .shrink(padding);
237        let total_gap = gap * children.len().saturating_sub(1) as u32;
238        let max_anti = axis.size_anti(limits.max());
239
240        let mut layout_children = Vec::with_capacity(children.len());
241        layout_children.resize(children.len(), LayoutNode::default());
242
243        let mut total_main_divs = 0;
244
245        let mut free_main = axis.size_main(limits.max()).saturating_sub(total_gap);
246        let mut free_anti = match axis {
247            Axis::X if size.width == Length::Shrink => 0,
248            Axis::Y if size.height == Length::Shrink => 0,
249            _ => max_anti,
250        };
251
252        // Calculate non-auto-sized children (main axis length is not Length::Fill or Length::Div)
253        for ((i, child), child_state) in
254            children.iter().enumerate().zip(state_tree.children.iter_mut())
255        {
256            match child.position() {
257                Position::Absolute => {
258                    layout_children[i] = child.layout(ctx, child_state, styler, &limits, viewport);
259                },
260                Position::Relative => {
261                    let (fill_main_div, fill_anti_div) = {
262                        let size = child.size();
263                        axis.canon(size.width.div_factor(), size.height.div_factor())
264                    };
265
266                    if fill_main_div == 0 {
267                        let (max_width, max_height) = axis.canon(
268                            free_main,
269                            if fill_anti_div == 0 { max_anti } else { free_anti },
270                        );
271
272                        let child_limits =
273                            Limits::new(Size::zero(), Size::new(max_width, max_height));
274
275                        let layout =
276                            child.layout(ctx, child_state, styler, &child_limits, viewport);
277                        let size = layout.size();
278
279                        free_main -= axis.size_main(size);
280                        free_anti = free_anti.max(axis.size_anti(size));
281
282                        layout_children[i] = layout;
283                    } else {
284                        total_main_divs += fill_main_div as u32;
285                    }
286                },
287            }
288        }
289
290        // Remaining main axis length after calculating sizes of non-auto-sized children
291        let remaining = match axis {
292            Axis::X => match size.width {
293                Length::Shrink => 0,
294                _ => free_main.max(0),
295            },
296            Axis::Y => match size.height {
297                Length::Shrink => 0,
298                _ => free_main.max(0),
299            },
300        };
301        let remaining_div = remaining.checked_div(total_main_divs).unwrap_or(0);
302        let mut remaining_mod = remaining.checked_rem(total_main_divs).unwrap_or(0);
303
304        // Calculate auto-sized children (Length::Fill, Length::Div(N))
305        for ((i, child), child_state) in
306            children.iter().enumerate().zip(state_tree.children.iter_mut())
307        {
308            if let Position::Relative = child.position() {
309                let (fill_main_div, fill_anti_div) = {
310                    let size = child.size();
311
312                    axis.canon(size.width.div_factor(), size.height.div_factor())
313                };
314
315                if fill_main_div != 0 {
316                    let max_main = if total_main_divs == 0 {
317                        remaining
318                    } else {
319                        remaining_div * fill_main_div as u32
320                            + if remaining_mod > 0 {
321                                remaining_mod -= 1;
322                                1
323                            } else {
324                                0
325                            }
326                    };
327                    let min_main = 0;
328
329                    let (min_width, min_height) = axis.canon(min_main, 0);
330                    let (max_width, max_height) =
331                        axis.canon(max_main, if fill_anti_div == 0 { max_anti } else { free_anti });
332
333                    let child_limits = Limits::new(
334                        Size::new(min_width, min_height),
335                        Size::new(max_width, max_height),
336                    );
337
338                    let layout = child.layout(ctx, child_state, styler, &child_limits, viewport);
339                    free_anti = free_anti.max(axis.size_anti(layout.size()));
340                    layout_children[i] = layout;
341                }
342            }
343        }
344
345        let (main_padding, anti_padding) = axis.canon(padding.left, padding.right);
346        let mut main_offset = main_padding;
347
348        for (i, node) in layout_children.iter_mut().enumerate() {
349            if let Position::Relative = node.position() {
350                if i > 0 {
351                    main_offset += gap;
352                }
353
354                let (x, y) = axis.canon(main_offset as i32, anti_padding as i32);
355                node.move_mut(Point::new(x, y));
356
357                match axis {
358                    Axis::X => node.align_mut(align, Alignment::Start, Size::new(0, free_anti)),
359                    Axis::Y => node.align_mut(Alignment::Start, align, Size::new(free_anti, 0)),
360                };
361
362                let size = node.size();
363
364                main_offset += axis.size_main(size);
365            }
366        }
367
368        let (content_width, content_height) = axis.canon(main_offset - main_padding, free_anti);
369        let size =
370            limits.resolve_size(size.width, size.height, Size::new(content_width, content_height));
371
372        LayoutNode::with_children(size.expand(padding), layout_children)
373    }
374}
375
376#[derive(Clone, Copy)]
377pub struct Limits {
378    min: Size<u32>,
379    max: Size<u32>,
380}
381
382impl Limits {
383    pub fn new(min: Size<u32>, max: Size<u32>) -> Self {
384        Self { min, max }
385    }
386
387    pub fn only_max(max: Size<u32>) -> Self {
388        Self { min: Size::zero(), max }
389    }
390
391    pub fn min(&self) -> Size<u32> {
392        self.min
393    }
394
395    pub fn max(&self) -> Size<u32> {
396        self.max
397    }
398
399    pub fn min_square(&self) -> u32 {
400        self.min().width.min(self.min().height)
401    }
402
403    pub fn max_square(&self) -> u32 {
404        self.max().width.min(self.max().height)
405    }
406
407    pub fn for_position(&self, position: Position, viewport: &Viewport) -> Self {
408        match position {
409            Position::Relative => *self,
410            // TODO: Review this, may be only_max(viewport.size)
411            Position::Absolute => Limits::new(self.min, viewport.size),
412        }
413    }
414
415    pub fn limit_width(self, width: impl Into<Length>) -> Self {
416        match width.into() {
417            Length::Shrink | Length::Div(_) | Length::Fill => self,
418            Length::Fixed(fixed) => {
419                let new_width = fixed.min(self.max.width).max(self.min.width);
420
421                Self::new(self.min.new_width(new_width), self.max.new_width(new_width))
422            },
423        }
424    }
425
426    pub fn limit_height(self, height: impl Into<Length>) -> Self {
427        match height.into() {
428            Length::Shrink | Length::Div(_) | Length::Fill => self,
429            Length::Fixed(fixed) => {
430                let new_height = fixed.min(self.max.height).max(self.min.height);
431
432                Self::new(self.min.new_height(new_height), self.max.new_height(new_height))
433            },
434        }
435    }
436
437    pub fn shrink(self, by: impl Into<Size>) -> Self {
438        let by = by.into();
439
440        Limits::new(self.min() - by, self.max() - by)
441    }
442
443    pub fn resolve_size(
444        &self,
445        width: impl Into<Length>,
446        height: impl Into<Length>,
447        content_size: Size<u32>,
448    ) -> Size<u32> {
449        let width = match width.into() {
450            Length::Fill | Length::Div(_) => self.max.width,
451            Length::Fixed(fixed) => fixed.min(self.max.width).max(self.min.width),
452            Length::Shrink => content_size.width.min(self.max.width).max(self.min.width),
453        };
454
455        let height = match height.into() {
456            Length::Fill | Length::Div(_) => self.max.height,
457            Length::Fixed(fixed) => fixed.min(self.max.height).max(self.min.height),
458            Length::Shrink => content_size.height.min(self.max.height).max(self.min.height),
459        };
460
461        Size::new(width, height)
462    }
463
464    pub fn resolve_square(&self, size: impl Into<Length>) -> u32 {
465        let min_square = self.min_square();
466        let max_square = self.max_square();
467
468        match size.into() {
469            Length::Fill | Length::Div(_) => max_square,
470            Length::Fixed(fixed) => fixed.min(max_square).max(min_square),
471            Length::Shrink => min_square,
472        }
473    }
474}
475
476impl From<Bounds> for Limits {
477    fn from(value: Bounds) -> Self {
478        Self::new(Size::zero(), value.size.into())
479    }
480}