cosmic/widget/flex_row/
layout.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use crate::{Element, Renderer};
5use iced_core::layout::{Limits, Node};
6use iced_core::widget::Tree;
7use iced_core::{Length, Padding, Point, Size};
8use taffy::geometry::Rect;
9use taffy::style::{AlignItems, Dimension, Display, Style};
10use taffy::style_helpers::length;
11use taffy::{AlignContent, TaffyTree};
12
13#[allow(clippy::too_many_arguments)]
14#[allow(clippy::too_many_lines)]
15pub fn resolve<Message>(
16    renderer: &Renderer,
17    limits: &Limits,
18    items: &[Element<'_, Message>],
19    padding: Padding,
20    column_spacing: f32,
21    row_spacing: f32,
22    min_item_width: Option<f32>,
23    justify_items: Option<AlignItems>,
24    align_items: Option<AlignItems>,
25    justify_content: Option<AlignContent>,
26    tree: &mut [Tree],
27) -> Node {
28    let max_size = limits.max();
29
30    let mut leafs = Vec::with_capacity(items.len());
31    let mut nodes = Vec::with_capacity(items.len());
32
33    let mut taffy_tree = TaffyTree::<()>::with_capacity(items.len() + 1);
34
35    let style = taffy::Style {
36        display: Display::Flex,
37        flex_direction: taffy::FlexDirection::Row,
38        flex_wrap: taffy::FlexWrap::Wrap,
39
40        gap: taffy::geometry::Size {
41            width: length(column_spacing),
42            height: length(row_spacing),
43        },
44
45        min_size: taffy::geometry::Size {
46            width: length(max_size.width),
47            height: Dimension::Auto,
48        },
49
50        align_items,
51        justify_items,
52        justify_content,
53
54        padding: Rect {
55            left: length(padding.left),
56            right: length(padding.right),
57            top: length(padding.top),
58            bottom: length(padding.bottom),
59        },
60
61        ..taffy::Style::default()
62    };
63
64    for (child, tree) in items.iter().zip(tree.iter_mut()) {
65        let child_widget = child.as_widget();
66        let child_node = child_widget.layout(tree, renderer, limits);
67        let size = child_node.size();
68
69        nodes.push(child_node);
70
71        let c_size = child_widget.size();
72        let (width, flex_grow, justify_self) = match c_size.width {
73            Length::Fill | Length::FillPortion(_) => {
74                (Dimension::Auto, 1.0, Some(AlignItems::Stretch))
75            }
76            _ => (length(size.width), 0.0, None),
77        };
78
79        let child_style = Style {
80            flex_grow,
81
82            min_size: taffy::geometry::Size {
83                width: match min_item_width {
84                    Some(width) => length(size.width.min(width)),
85                    None => Dimension::Auto,
86                },
87                height: Dimension::Auto,
88            },
89
90            size: taffy::geometry::Size {
91                width,
92                height: match c_size.height {
93                    Length::Fill | Length::FillPortion(_) => Dimension::Auto,
94                    _ => length(size.height),
95                },
96            },
97
98            justify_self,
99
100            ..Style::default()
101        };
102
103        leafs.push(match taffy_tree.new_leaf(child_style) {
104            Ok(leaf) => leaf,
105            Err(why) => {
106                tracing::error!(?why, "failed to add child element to flex row");
107                continue;
108            }
109        });
110    }
111
112    let root = match taffy_tree.new_with_children(style, &leafs) {
113        Ok(root) => root,
114        Err(why) => {
115            tracing::error!(?why, "flex row style is invalid");
116            return Node::new(Size::ZERO);
117        }
118    };
119
120    if let Err(why) = taffy_tree.compute_layout(
121        root,
122        taffy::geometry::Size {
123            width: length(max_size.width),
124            height: length(max_size.height),
125        },
126    ) {
127        tracing::error!(?why, "flex row layout invalid");
128        return Node::new(Size::ZERO);
129    }
130
131    let flex_layout = match taffy_tree.layout(root) {
132        Ok(layout) => layout,
133        Err(why) => {
134            tracing::error!(?why, "cannot get flex row layout");
135            return Node::new(Size::ZERO);
136        }
137    };
138
139    leafs
140        .into_iter()
141        .zip(items.iter())
142        .zip(nodes.iter_mut())
143        .zip(tree)
144        .for_each(|(((leaf, child), node), tree)| {
145            let Ok(leaf_layout) = taffy_tree.layout(leaf) else {
146                return;
147            };
148
149            let child_widget = child.as_widget();
150            let c_size = child_widget.size();
151            match c_size.width {
152                Length::Fill | Length::FillPortion(_) => {
153                    *node =
154                        child_widget.layout(tree, renderer, &limits.width(leaf_layout.size.width));
155                }
156                _ => (),
157            }
158
159            *node = node.clone().move_to(Point {
160                x: leaf_layout.location.x,
161                y: leaf_layout.location.y,
162            });
163        });
164
165    let size = Size {
166        width: flex_layout.content_size.width,
167        height: flex_layout.content_size.height,
168    };
169
170    Node::with_children(size, nodes)
171}