winio_layout/
stack_panel.rs

1use taffy::{
2    NodeId, Style, TaffyTree,
3    prelude::{auto, length, percent},
4};
5use winio_primitive::{HAlign, Margin, Orient, Point, Rect, Size, VAlign};
6
7use crate::{Layoutable, layout_child, rect_t2e, render};
8
9layout_child! {
10    /// Builder of a child for [`StackPanel`].
11    struct StackPanelChild {
12        /// Whether to fill the remain space of the container.
13        grow: bool = false,
14    }
15}
16
17/// A stacked layout container.
18pub struct StackPanel<'a> {
19    children: Vec<StackPanelChild<'a>>,
20    orient: Orient,
21    loc: Point,
22    size: Size,
23}
24
25impl<'a> StackPanel<'a> {
26    /// Create [`StackPanel`] with orientation.
27    pub fn new(orient: Orient) -> Self {
28        Self {
29            children: vec![],
30            orient,
31            loc: Point::zero(),
32            size: Size::zero(),
33        }
34    }
35
36    /// Push a child into the panel.
37    pub fn push<'b>(
38        &'b mut self,
39        widget: &'a mut dyn Layoutable,
40    ) -> StackPanelChildBuilder<'a, 'b> {
41        StackPanelChildBuilder {
42            child: StackPanelChild::new(widget),
43            children: &mut self.children,
44        }
45    }
46
47    fn tree(&self) -> (TaffyTree, NodeId, Vec<NodeId>) {
48        let mut tree: TaffyTree<()> = TaffyTree::new();
49        let mut nodes = vec![];
50        for child in &self.children {
51            let mut preferred_size = child.widget.preferred_size();
52            preferred_size.width += child.margin.horizontal();
53            preferred_size.height += child.margin.vertical();
54            let mut style = Style::default();
55            style.size.width = match child.width {
56                Some(w) => length(w as f32),
57                None => match (self.orient, child.halign, child.grow) {
58                    (Orient::Vertical, HAlign::Stretch, _) => percent(1.0),
59                    (Orient::Horizontal, _, true) => auto(),
60                    _ => length(preferred_size.width as f32),
61                },
62            };
63            style.size.height = match child.height {
64                Some(h) => length(h as f32),
65                None => match (self.orient, child.valign, child.grow) {
66                    (Orient::Horizontal, VAlign::Stretch, _) => percent(1.0),
67                    (Orient::Vertical, _, true) => auto(),
68                    _ => length(preferred_size.height as f32),
69                },
70            };
71            let mut min_size = child.widget.min_size();
72            min_size.width += child.margin.horizontal();
73            min_size.height += child.margin.vertical();
74            style.min_size = taffy::Size {
75                width: length(min_size.width as f32),
76                height: length(min_size.height as f32),
77            };
78            match self.orient {
79                Orient::Horizontal => {
80                    if matches!(child.valign, VAlign::Top | VAlign::Center) {
81                        style.margin.bottom = auto();
82                    }
83                    if matches!(child.valign, VAlign::Bottom | VAlign::Center) {
84                        style.margin.top = auto();
85                    }
86                }
87                Orient::Vertical => {
88                    if matches!(child.halign, HAlign::Left | HAlign::Center) {
89                        style.margin.right = auto();
90                    }
91                    if matches!(child.halign, HAlign::Right | HAlign::Center) {
92                        style.margin.left = auto();
93                    }
94                }
95            }
96            if child.grow {
97                style.flex_grow = 1.0
98            }
99            let node = tree.new_leaf(style).unwrap();
100            nodes.push(node);
101        }
102        let root = tree
103            .new_with_children(
104                Style {
105                    size: taffy::Size::from_percent(1.0, 1.0),
106                    flex_direction: match self.orient {
107                        Orient::Horizontal => taffy::FlexDirection::Row,
108                        Orient::Vertical => taffy::FlexDirection::Column,
109                    },
110                    ..Default::default()
111                },
112                &nodes,
113            )
114            .unwrap();
115        (tree, root, nodes)
116    }
117
118    fn render(&mut self) {
119        let (tree, root, nodes) = self.tree();
120        render(tree, root, nodes, self.loc, self.size, &mut self.children)
121    }
122}
123
124impl Layoutable for StackPanel<'_> {
125    fn loc(&self) -> Point {
126        self.loc
127    }
128
129    fn set_loc(&mut self, p: Point) {
130        self.loc = p;
131        self.render();
132    }
133
134    fn size(&self) -> Size {
135        self.size
136    }
137
138    fn set_size(&mut self, s: Size) {
139        self.size = s;
140        self.render();
141    }
142
143    fn set_rect(&mut self, r: Rect) {
144        self.loc = r.origin;
145        self.size = r.size;
146        self.render();
147    }
148
149    fn preferred_size(&self) -> Size {
150        let (mut tree, root, _) = self.tree();
151        tree.compute_layout(root, taffy::Size::max_content())
152            .unwrap();
153        rect_t2e(tree.layout(root).unwrap(), Margin::zero()).size
154    }
155
156    fn min_size(&self) -> Size {
157        let (mut tree, root, _) = self.tree();
158        tree.compute_layout(root, taffy::Size::min_content())
159            .unwrap();
160        rect_t2e(tree.layout(root).unwrap(), Margin::zero()).size
161    }
162}