iced_core/layout/
flex.rs

1//! Distribute elements using a flex-based layout.
2// This code is heavily inspired by the [`druid`] codebase.
3//
4// [`druid`]: https://github.com/xi-editor/druid
5//
6// Copyright 2018 The xi-editor Authors, Héctor Ramón
7//
8// Licensed under the Apache License, Version 2.0 (the "License");
9// you may not use this file except in compliance with the License.
10// You may obtain a copy of the License at
11//
12//     http://www.apache.org/licenses/LICENSE-2.0
13//
14// Unless required by applicable law or agreed to in writing, software
15// distributed under the License is distributed on an "AS IS" BASIS,
16// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17// See the License for the specific language governing permissions and
18// limitations under the License.
19use crate::Element;
20
21use crate::layout::{Limits, Node};
22use crate::widget;
23use crate::{Alignment, Length, Padding, Point, Size};
24
25/// The main axis of a flex layout.
26#[derive(Debug)]
27pub enum Axis {
28    /// The horizontal axis
29    Horizontal,
30
31    /// The vertical axis
32    Vertical,
33}
34
35impl Axis {
36    fn main(&self, size: Size) -> f32 {
37        match self {
38            Axis::Horizontal => size.width,
39            Axis::Vertical => size.height,
40        }
41    }
42
43    fn cross(&self, size: Size) -> f32 {
44        match self {
45            Axis::Horizontal => size.height,
46            Axis::Vertical => size.width,
47        }
48    }
49
50    fn pack<T>(&self, main: T, cross: T) -> (T, T) {
51        match self {
52            Axis::Horizontal => (main, cross),
53            Axis::Vertical => (cross, main),
54        }
55    }
56}
57
58/// Computes the flex layout with the given axis and limits, applying spacing,
59/// padding and alignment to the items as needed.
60///
61/// It returns a new layout [`Node`].
62pub fn resolve<Message, Theme, Renderer>(
63    axis: Axis,
64    renderer: &Renderer,
65    limits: &Limits,
66    width: Length,
67    height: Length,
68    padding: Padding,
69    spacing: f32,
70    align_items: Alignment,
71    items: &[Element<'_, Message, Theme, Renderer>],
72    trees: &mut [widget::Tree],
73) -> Node
74where
75    Renderer: crate::Renderer,
76{
77    let limits = limits.width(width).height(height).shrink(padding);
78    let total_spacing = spacing * items.len().saturating_sub(1) as f32;
79    let max_cross = axis.cross(limits.max());
80
81    let mut fill_main_sum = 0;
82    let (mut cross, cross_compress) = match axis {
83        Axis::Vertical if width == Length::Shrink => (0.0, true),
84        Axis::Horizontal if height == Length::Shrink => (0.0, true),
85        _ => (max_cross, false),
86    };
87
88    let mut available = axis.main(limits.max()) - total_spacing;
89
90    let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
91    nodes.resize(items.len(), Node::default());
92
93    for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
94        let (fill_main_factor, fill_cross_factor) = {
95            let size = child.as_widget().size();
96
97            axis.pack(size.width.fill_factor(), size.height.fill_factor())
98        };
99
100        if fill_main_factor == 0 && (!cross_compress || fill_cross_factor == 0)
101        {
102            let (max_width, max_height) = axis.pack(
103                available,
104                if fill_cross_factor == 0 {
105                    max_cross
106                } else {
107                    cross
108                },
109            );
110
111            let child_limits =
112                Limits::new(Size::ZERO, Size::new(max_width, max_height));
113
114            let layout =
115                child.as_widget().layout(tree, renderer, &child_limits);
116            let size = layout.size();
117
118            available -= axis.main(size);
119            cross = cross.max(axis.cross(size));
120
121            nodes[i] = layout;
122        } else {
123            fill_main_sum += fill_main_factor;
124        }
125    }
126
127    let remaining = match axis {
128        Axis::Horizontal => match width {
129            Length::Shrink => 0.0,
130            _ => available.max(0.0),
131        },
132        Axis::Vertical => match height {
133            Length::Shrink => 0.0,
134            _ => available.max(0.0),
135        },
136    };
137
138    for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
139        let (fill_main_factor, fill_cross_factor) = {
140            let size = child.as_widget().size();
141
142            axis.pack(size.width.fill_factor(), size.height.fill_factor())
143        };
144
145        if fill_main_factor != 0 || (cross_compress && fill_cross_factor != 0) {
146            let max_main =
147                remaining * fill_main_factor as f32 / fill_main_sum as f32;
148
149            let min_main = if max_main.is_infinite() {
150                0.0
151            } else {
152                max_main
153            };
154
155            let (min_width, min_height) = axis.pack(min_main, 0.0);
156            let (max_width, max_height) = axis.pack(
157                max_main,
158                if fill_cross_factor == 0 {
159                    max_cross
160                } else {
161                    cross
162                },
163            );
164
165            let child_limits = Limits::new(
166                Size::new(min_width, min_height),
167                Size::new(max_width, max_height),
168            );
169
170            let layout =
171                child.as_widget().layout(tree, renderer, &child_limits);
172            cross = cross.max(axis.cross(layout.size()));
173
174            nodes[i] = layout;
175        }
176    }
177
178    let pad = axis.pack(padding.left, padding.top);
179    let mut main = pad.0;
180
181    for (i, node) in nodes.iter_mut().enumerate() {
182        if i > 0 {
183            main += spacing;
184        }
185
186        let (x, y) = axis.pack(main, pad.1);
187
188        node.move_to_mut(Point::new(x, y));
189
190        match axis {
191            Axis::Horizontal => {
192                node.align_mut(
193                    Alignment::Start,
194                    align_items,
195                    Size::new(0.0, cross),
196                );
197            }
198            Axis::Vertical => {
199                node.align_mut(
200                    align_items,
201                    Alignment::Start,
202                    Size::new(cross, 0.0),
203                );
204            }
205        }
206
207        let size = node.size();
208
209        main += axis.main(size);
210    }
211
212    let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross);
213    let size = limits.resolve(
214        width,
215        height,
216        Size::new(intrinsic_width, intrinsic_height),
217    );
218
219    Node::with_children(size.expand(padding), nodes)
220}