cosmic/widget/grid/
layout.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use super::widget::Assignment;
5use crate::{Element, Renderer};
6use iced_core::layout::{Limits, Node};
7use iced_core::widget::Tree;
8use iced_core::{Alignment, Length, Padding, Point, Size};
9
10use taffy::geometry::{Line, Rect};
11use taffy::style::{AlignItems, Dimension, Display, GridPlacement, Style};
12use taffy::style_helpers::{auto, length};
13use taffy::{AlignContent, TaffyTree};
14
15#[allow(clippy::too_many_arguments)]
16#[allow(clippy::too_many_lines)]
17pub fn resolve<Message>(
18    renderer: &Renderer,
19    limits: &Limits,
20    items: &mut [Element<'_, Message>],
21    assignments: &[Assignment],
22    width: Length,
23    height: Length,
24    padding: Padding,
25    column_alignment: Alignment,
26    row_alignment: Alignment,
27    justify_content: Option<AlignContent>,
28    column_spacing: f32,
29    row_spacing: f32,
30    tree: &mut [Tree],
31) -> Node {
32    let max_size = limits.max();
33
34    let mut leafs = Vec::with_capacity(items.len());
35    let mut nodes = Vec::with_capacity(items.len());
36
37    let mut taffy = TaffyTree::<()>::with_capacity(items.len() + 1);
38
39    // Attach widgets as child nodes.
40    for ((child, assignment), tree) in items
41        .iter_mut()
42        .zip(assignments.iter())
43        .zip(tree.iter_mut())
44    {
45        // Calculate the dimensions of the item.
46        let child_widget = child.as_widget_mut();
47        let child_node = child_widget.layout(tree, renderer, limits);
48        let size = child_node.size();
49
50        nodes.push(child_node);
51
52        let c_size = child_widget.size();
53        let (width, flex_grow, justify_self) = match c_size.width {
54            Length::Fill | Length::FillPortion(_) => {
55                (Dimension::auto(), 1.0, Some(AlignItems::Stretch))
56            }
57            _ => (length(size.width), 0.0, None),
58        };
59
60        // Attach widget as leaf to be later assigned to grid.
61        let leaf = taffy.new_leaf(Style {
62            flex_grow,
63
64            grid_column: Line {
65                start: GridPlacement::Line((assignment.column as i16).into()),
66                end: GridPlacement::Line(
67                    (assignment.column as i16 + assignment.width as i16).into(),
68                ),
69            },
70
71            grid_row: Line {
72                start: GridPlacement::Line((assignment.row as i16).into()),
73                end: GridPlacement::Line((assignment.row as i16 + assignment.height as i16).into()),
74            },
75
76            size: taffy::geometry::Size {
77                width,
78                height: match c_size.height {
79                    Length::Fill | Length::FillPortion(_) => Dimension::auto(),
80                    _ => length(size.height),
81                },
82            },
83
84            justify_self,
85
86            ..Style::default()
87        });
88
89        match leaf {
90            Ok(leaf) => leafs.push(leaf),
91            Err(why) => {
92                tracing::error!(?why, "cannot add leaf node to grid");
93                continue;
94            }
95        }
96    }
97
98    let root = taffy.new_with_children(
99        Style {
100            align_items: Some(match width {
101                Length::Fill | Length::FillPortion(_) => AlignItems::Stretch,
102                _ => match row_alignment {
103                    Alignment::Start => AlignItems::Start,
104                    Alignment::Center => AlignItems::Center,
105                    Alignment::End => AlignItems::End,
106                },
107            }),
108
109            display: Display::Grid,
110
111            gap: taffy::geometry::Size {
112                width: length(column_spacing),
113                height: length(row_spacing),
114            },
115
116            justify_items: Some(match height {
117                Length::Fill | Length::FillPortion(_) => AlignItems::Stretch,
118                _ => match column_alignment {
119                    Alignment::Start => AlignItems::Start,
120                    Alignment::Center => AlignItems::Center,
121                    Alignment::End => AlignItems::End,
122                },
123            }),
124
125            justify_content,
126
127            padding: Rect {
128                left: length(padding.left),
129                right: length(padding.right),
130                top: length(padding.top),
131                bottom: length(padding.bottom),
132            },
133
134            size: taffy::geometry::Size {
135                width: match width {
136                    Length::Fixed(fixed) => length(fixed),
137                    _ => auto(),
138                },
139                height: match height {
140                    Length::Fixed(fixed) => length(fixed),
141                    _ => auto(),
142                },
143            },
144
145            ..Style::default()
146        },
147        &leafs,
148    );
149
150    let root = match root {
151        Ok(root) => root,
152        Err(why) => {
153            tracing::error!(?why, "grid root style invalid");
154            return Node::new(Size::ZERO);
155        }
156    };
157
158    if let Err(why) = taffy.compute_layout(
159        root,
160        taffy::geometry::Size {
161            width: length(max_size.width),
162            height: length(max_size.height),
163        },
164    ) {
165        tracing::error!(?why, "grid layout did not compute");
166        return Node::new(Size::ZERO);
167    }
168
169    let grid_layout = match taffy.layout(root) {
170        Ok(layout) => layout,
171        Err(why) => {
172            tracing::error!(?why, "cannot get layout of grid");
173            return Node::new(Size::ZERO);
174        }
175    };
176
177    for (((leaf, child), node), tree) in leafs
178        .into_iter()
179        .zip(items.iter_mut())
180        .zip(nodes.iter_mut())
181        .zip(tree)
182    {
183        if let Ok(leaf_layout) = taffy.layout(leaf) {
184            let child_widget = child.as_widget_mut();
185            let c_size = child_widget.size();
186            match c_size.width {
187                Length::Fill | Length::FillPortion(_) => {
188                    *node =
189                        child_widget.layout(tree, renderer, &limits.width(leaf_layout.size.width));
190                }
191                _ => (),
192            }
193
194            node.move_to_mut(Point {
195                x: leaf_layout.location.x,
196                y: leaf_layout.location.y,
197            })
198        }
199    }
200
201    let grid_size = Size {
202        width: grid_layout.content_size.width,
203        height: grid_layout.content_size.height,
204    };
205
206    Node::with_children(grid_size, nodes)
207}