winio_layout/
grid.rs

1use std::{fmt::Display, num::ParseFloatError, str::FromStr};
2
3use taffy::{
4    NodeId, Style, TaffyTree, TrackSizingFunction,
5    prelude::{auto, fr, length, line, span},
6};
7
8use super::{layout_child, rect_t2e, render};
9use crate::{HAlign, Layoutable, Margin, Point, Rect, Size, VAlign};
10
11/// Error can be returned when parsing [`GridLength`].
12#[derive(Debug)]
13#[non_exhaustive]
14pub enum ParseGridLengthError {
15    /// Invalid length value.
16    InvalidLength(ParseFloatError),
17}
18
19impl Display for ParseGridLengthError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            Self::InvalidLength(e) => write!(f, "invalid length value: {e}"),
23        }
24    }
25}
26
27impl std::error::Error for ParseGridLengthError {}
28
29/// The width or height of a grid cell.
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum GridLength {
32    /// The length is determined automatically.
33    Auto,
34    /// Represents a relative ratio.
35    Stretch(f64),
36    /// Fixed length.
37    Length(f64),
38}
39
40impl FromStr for GridLength {
41    type Err = ParseGridLengthError;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        if s.eq_ignore_ascii_case("auto") {
45            Ok(Self::Auto)
46        } else if let Some(s) = s.strip_suffix('*') {
47            s.parse::<f64>()
48                .map(Self::Stretch)
49                .map_err(ParseGridLengthError::InvalidLength)
50        } else {
51            s.parse::<f64>()
52                .map(Self::Length)
53                .map_err(ParseGridLengthError::InvalidLength)
54        }
55    }
56}
57
58impl From<GridLength> for TrackSizingFunction {
59    fn from(value: GridLength) -> Self {
60        match value {
61            GridLength::Auto => auto(),
62            GridLength::Length(v) => length(v as f32),
63            GridLength::Stretch(v) => fr(v as f32),
64        }
65    }
66}
67
68impl From<&GridLength> for TrackSizingFunction {
69    fn from(value: &GridLength) -> Self {
70        TrackSizingFunction::from(*value)
71    }
72}
73
74layout_child! {
75    /// Builder of a child for [`Grid`].
76    struct GridChild {
77        /// The column index in the grid.
78        column: usize = 0,
79        /// The row index in the grid.
80        row: usize = 0,
81        /// The column span in the grid.
82        column_span: usize = 1,
83        /// The row span in the grid.
84        row_span: usize = 1,
85    }
86}
87
88/// A grid layout container.
89pub struct Grid<'a> {
90    children: Vec<GridChild<'a>>,
91    columns: Vec<GridLength>,
92    rows: Vec<GridLength>,
93    loc: Point,
94    size: Size,
95}
96
97impl<'a> Grid<'a> {
98    /// Create [`Grid`].
99    pub fn new(columns: Vec<GridLength>, rows: Vec<GridLength>) -> Self {
100        Self {
101            children: vec![],
102            columns,
103            rows,
104            loc: Point::zero(),
105            size: Size::zero(),
106        }
107    }
108
109    /// Create [`Grid`] with string-representative of grid lengths.
110    pub fn from_str(
111        columns: impl AsRef<str>,
112        rows: impl AsRef<str>,
113    ) -> Result<Self, ParseGridLengthError> {
114        Ok(Self::new(
115            Self::parse_grid_lengths(columns.as_ref())?,
116            Self::parse_grid_lengths(rows.as_ref())?,
117        ))
118    }
119
120    fn parse_grid_lengths(s: &str) -> Result<Vec<GridLength>, ParseGridLengthError> {
121        let mut lengths = vec![];
122        for s in s.split(',') {
123            let s = s.trim();
124            lengths.push(s.parse()?);
125        }
126        Ok(lengths)
127    }
128
129    /// Push a child into the panel.
130    pub fn push<'b>(&'b mut self, widget: &'a mut dyn Layoutable) -> GridChildBuilder<'a, 'b> {
131        GridChildBuilder {
132            child: GridChild::new(widget),
133            children: &mut self.children,
134        }
135    }
136
137    fn tree(&self) -> (TaffyTree, NodeId, Vec<NodeId>) {
138        let mut tree: TaffyTree<()> = TaffyTree::new();
139        let mut nodes = vec![];
140        for child in &self.children {
141            let mut preferred_size = child.widget.preferred_size();
142            preferred_size.width += child.margin.horizontal();
143            preferred_size.height += child.margin.vertical();
144            let mut style = Style::default();
145            style.size.width = match child.width {
146                Some(w) => length(w as f32),
147                None => match child.halign {
148                    HAlign::Stretch => auto(),
149                    _ => length(preferred_size.width as f32),
150                },
151            };
152            style.size.height = match child.height {
153                Some(h) => length(h as f32),
154                None => match child.valign {
155                    VAlign::Stretch => auto(),
156                    _ => length(preferred_size.height as f32),
157                },
158            };
159            let mut min_size = child.widget.min_size();
160            min_size.width += child.margin.horizontal();
161            min_size.height += child.margin.vertical();
162            style.min_size = taffy::Size {
163                width: length(min_size.width as f32),
164                height: length(min_size.height as f32),
165            };
166            if matches!(child.valign, VAlign::Top | VAlign::Center) {
167                style.margin.bottom = auto();
168            }
169            if matches!(child.valign, VAlign::Bottom | VAlign::Center) {
170                style.margin.top = auto();
171            }
172            if matches!(child.halign, HAlign::Left | HAlign::Center) {
173                style.margin.right = auto();
174            }
175            if matches!(child.halign, HAlign::Right | HAlign::Center) {
176                style.margin.left = auto();
177            }
178
179            style.grid_column.start = line(child.column as i16 + 1);
180            style.grid_row.start = line(child.row as i16 + 1);
181
182            let cspan = child.column_span;
183            if cspan > 1 {
184                style.grid_column.end = span(cspan as u16);
185            }
186
187            let rspan = child.row_span;
188            if rspan > 1 {
189                style.grid_row.end = span(rspan as u16);
190            }
191
192            let node = tree.new_leaf(style).unwrap();
193            nodes.push(node);
194        }
195        let root = tree
196            .new_with_children(
197                Style {
198                    display: taffy::Display::Grid,
199                    size: taffy::Size::from_percent(1.0, 1.0),
200                    grid_template_columns: self
201                        .columns
202                        .iter()
203                        .map(TrackSizingFunction::from)
204                        .collect(),
205                    grid_template_rows: self.rows.iter().map(TrackSizingFunction::from).collect(),
206                    ..Default::default()
207                },
208                &nodes,
209            )
210            .unwrap();
211        (tree, root, nodes)
212    }
213
214    fn render(&mut self) {
215        let (tree, root, nodes) = self.tree();
216        render(tree, root, nodes, self.loc, self.size, &mut self.children)
217    }
218}
219
220impl Layoutable for Grid<'_> {
221    fn loc(&self) -> Point {
222        self.loc
223    }
224
225    fn set_loc(&mut self, p: Point) {
226        self.loc = p;
227        self.render();
228    }
229
230    fn size(&self) -> Size {
231        self.size
232    }
233
234    fn set_size(&mut self, s: Size) {
235        self.size = s;
236        self.render();
237    }
238
239    fn set_rect(&mut self, r: Rect) {
240        self.loc = r.origin;
241        self.size = r.size;
242        self.render();
243    }
244
245    fn preferred_size(&self) -> Size {
246        let (mut tree, root, _) = self.tree();
247        tree.compute_layout(root, taffy::Size::max_content())
248            .unwrap();
249        rect_t2e(tree.layout(root).unwrap(), Margin::zero()).size
250    }
251}