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: &[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.iter().zip(assignments.iter()).zip(tree.iter_mut()) {
41        // Calculate the dimensions of the item.
42        let child_widget = child.as_widget();
43        let child_node = child_widget.layout(tree, renderer, limits);
44        let size = child_node.size();
45
46        nodes.push(child_node);
47
48        let c_size = child_widget.size();
49        let (width, flex_grow, justify_self) = match c_size.width {
50            Length::Fill | Length::FillPortion(_) => {
51                (Dimension::Auto, 1.0, Some(AlignItems::Stretch))
52            }
53            _ => (length(size.width), 0.0, None),
54        };
55
56        // Attach widget as leaf to be later assigned to grid.
57        let leaf = taffy.new_leaf(Style {
58            flex_grow,
59
60            grid_column: Line {
61                start: GridPlacement::Line((assignment.column as i16).into()),
62                end: GridPlacement::Line(
63                    (assignment.column as i16 + assignment.width as i16).into(),
64                ),
65            },
66
67            grid_row: Line {
68                start: GridPlacement::Line((assignment.row as i16).into()),
69                end: GridPlacement::Line((assignment.row as i16 + assignment.height as i16).into()),
70            },
71
72            size: taffy::geometry::Size {
73                width,
74                height: match c_size.height {
75                    Length::Fill | Length::FillPortion(_) => Dimension::Auto,
76                    _ => length(size.height),
77                },
78            },
79
80            justify_self,
81
82            ..Style::default()
83        });
84
85        match leaf {
86            Ok(leaf) => leafs.push(leaf),
87            Err(why) => {
88                tracing::error!(?why, "cannot add leaf node to grid");
89                continue;
90            }
91        }
92    }
93
94    let root = taffy.new_with_children(
95        Style {
96            align_items: Some(match width {
97                Length::Fill | Length::FillPortion(_) => AlignItems::Stretch,
98                _ => match row_alignment {
99                    Alignment::Start => AlignItems::Start,
100                    Alignment::Center => AlignItems::Center,
101                    Alignment::End => AlignItems::End,
102                },
103            }),
104
105            display: Display::Grid,
106
107            gap: taffy::geometry::Size {
108                width: length(column_spacing),
109                height: length(row_spacing),
110            },
111
112            justify_items: Some(match height {
113                Length::Fill | Length::FillPortion(_) => AlignItems::Stretch,
114                _ => match column_alignment {
115                    Alignment::Start => AlignItems::Start,
116                    Alignment::Center => AlignItems::Center,
117                    Alignment::End => AlignItems::End,
118                },
119            }),
120
121            justify_content,
122
123            padding: Rect {
124                left: length(padding.left),
125                right: length(padding.right),
126                top: length(padding.top),
127                bottom: length(padding.bottom),
128            },
129
130            size: taffy::geometry::Size {
131                width: match width {
132                    Length::Fixed(fixed) => length(fixed),
133                    _ => auto(),
134                },
135                height: match height {
136                    Length::Fixed(fixed) => length(fixed),
137                    _ => auto(),
138                },
139            },
140
141            ..Style::default()
142        },
143        &leafs,
144    );
145
146    let root = match root {
147        Ok(root) => root,
148        Err(why) => {
149            tracing::error!(?why, "grid root style invalid");
150            return Node::new(Size::ZERO);
151        }
152    };
153
154    if let Err(why) = taffy.compute_layout(
155        root,
156        taffy::geometry::Size {
157            width: length(max_size.width),
158            height: length(max_size.height),
159        },
160    ) {
161        tracing::error!(?why, "grid layout did not compute");
162        return Node::new(Size::ZERO);
163    }
164
165    let grid_layout = match taffy.layout(root) {
166        Ok(layout) => layout,
167        Err(why) => {
168            tracing::error!(?why, "cannot get layout of grid");
169            return Node::new(Size::ZERO);
170        }
171    };
172
173    for (((leaf, child), node), tree) in leafs
174        .into_iter()
175        .zip(items.iter())
176        .zip(nodes.iter_mut())
177        .zip(tree)
178    {
179        if let Ok(leaf_layout) = taffy.layout(leaf) {
180            let child_widget = child.as_widget();
181            let c_size = child_widget.size();
182            match c_size.width {
183                Length::Fill | Length::FillPortion(_) => {
184                    *node =
185                        child_widget.layout(tree, renderer, &limits.width(leaf_layout.size.width));
186                }
187                _ => (),
188            }
189
190            *node = node.clone().move_to(Point {
191                x: leaf_layout.location.x,
192                y: leaf_layout.location.y,
193            })
194        }
195    }
196
197    let grid_size = Size {
198        width: grid_layout.content_size.width,
199        height: grid_layout.content_size.height,
200    };
201
202    Node::with_children(grid_size, nodes)
203}