winio_layout/
grid.rs

1use std::{fmt::Display, num::ParseFloatError, str::FromStr};
2
3use taffy::{
4    GridTemplateComponent, 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                        .map(GridTemplateComponent::Single)
205                        .collect(),
206                    grid_template_rows: self
207                        .rows
208                        .iter()
209                        .map(TrackSizingFunction::from)
210                        .map(GridTemplateComponent::Single)
211                        .collect(),
212                    ..Default::default()
213                },
214                &nodes,
215            )
216            .unwrap();
217        (tree, root, nodes)
218    }
219
220    fn render(&mut self) {
221        let (tree, root, nodes) = self.tree();
222        render(tree, root, nodes, self.loc, self.size, &mut self.children)
223    }
224}
225
226impl Layoutable for Grid<'_> {
227    fn loc(&self) -> Point {
228        self.loc
229    }
230
231    fn set_loc(&mut self, p: Point) {
232        self.loc = p;
233        self.render();
234    }
235
236    fn size(&self) -> Size {
237        self.size
238    }
239
240    fn set_size(&mut self, s: Size) {
241        self.size = s;
242        self.render();
243    }
244
245    fn set_rect(&mut self, r: Rect) {
246        self.loc = r.origin;
247        self.size = r.size;
248        self.render();
249    }
250
251    fn preferred_size(&self) -> Size {
252        let (mut tree, root, _) = self.tree();
253        tree.compute_layout(root, taffy::Size::max_content())
254            .unwrap();
255        rect_t2e(tree.layout(root).unwrap(), Margin::zero()).size
256    }
257}