Skip to main content

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::layout::{Limits, Vector};
20use crate::length;
21use crate::widget;
22use crate::{Alignment, Length, Padding, Size, Widget};
23
24/// The main axis of a flex layout.
25#[derive(Debug)]
26pub enum Axis {
27    /// The horizontal axis
28    Horizontal,
29
30    /// The vertical axis
31    Vertical,
32}
33
34impl Axis {
35    fn main<T>(&self, size: Size<T>) -> T {
36        match self {
37            Axis::Horizontal => size.width,
38            Axis::Vertical => size.height,
39        }
40    }
41
42    fn cross<T>(&self, size: Size<T>) -> T {
43        match self {
44            Axis::Horizontal => size.height,
45            Axis::Vertical => size.width,
46        }
47    }
48
49    fn pack<T>(&self, main: T, cross: T) -> (T, T) {
50        match self {
51            Axis::Horizontal => (main, cross),
52            Axis::Vertical => (cross, main),
53        }
54    }
55}
56
57/// Per-container retained state for [`resolve`].
58///
59/// [`resolve`] keeps per-child bookkeeping across its multiple passes. To
60/// avoid allocating that bookkeeping on every `layout` call, each flex
61/// container stores a [`Cache`] in its [`widget::Tree`] state, and
62/// [`resolve`] reuses the buffer stored there between layout calls.
63///
64/// The type is opaque: its contents are managed entirely by [`resolve`].
65#[derive(Debug, Default)]
66pub struct Cache {
67    metas: Vec<Meta>,
68}
69
70#[derive(Debug, Clone, Copy)]
71struct Meta {
72    main: Length,
73    cross: Length,
74    category: Category,
75    resolved: bool,
76}
77
78#[derive(Debug, Clone, Copy)]
79enum Category {
80    Static,
81    CrossFluid,
82    CrossFluidDeferred(f32),
83    MainFluid,
84}
85
86/// Computes the flex layout with the given axis and limits, applying spacing,
87/// padding and alignment to the items as needed.
88///
89/// The layout is stored in the provided children [`widget::Tree`]s, and the
90/// resolved size of the container is returned.
91///
92/// The provided [`Cache`] holds the per-child bookkeeping of the algorithm;
93/// it is expected to be reused between calls (see [`Cache`]).
94pub fn resolve<Message, Theme, Renderer>(
95    axis: Axis,
96    renderer: &Renderer,
97    limits: &Limits,
98    width: Length,
99    height: Length,
100    padding: Padding,
101    spacing: f32,
102    align_items: Alignment,
103    children: &mut [widget::Tree],
104    items: &mut [impl Widget<Message, Theme, Renderer>],
105    cache: &mut Cache,
106) -> Size
107where
108    Renderer: crate::Renderer,
109{
110    let limits = limits.width(width).height(height).shrink(padding);
111    let total_spacing = spacing * items.len().saturating_sub(1) as f32;
112    let max_cross = axis.cross(limits.max);
113
114    let compression = limits.compression;
115    let infinite = limits.infinite;
116
117    let (main_compress, cross_compress) = axis.pack(compression.width, compression.height);
118
119    let compression = {
120        let (compress_x, compress_y) = axis.pack(main_compress, false);
121        Size::new(compress_x, compress_y)
122    };
123
124    let cross_infinite = {
125        let (infinite_x, infinite_y) = axis.pack(axis.main(infinite), false);
126        Size::new(infinite_x, infinite_y)
127    };
128
129    let cross_dynamic = cross_compress || axis.cross(infinite);
130
131    let mut fill_main_sum = 0;
132    let mut some_fill_cross = false;
133    let mut some_fill_max = false;
134    let mut some_fill_min = false;
135    let mut min_total = 0.0;
136    let mut min_factors = 0;
137    let mut cross = 0.0f32;
138    let mut available = axis.main(limits.max) - total_spacing;
139
140    cache.metas.clear();
141    cache.metas.reserve(items.len());
142
143    // STATIC PASS
144    // We lay out non-fluid elements in the main axis.
145    // If we need to compress the cross axis, then we skip any of these elements
146    // that are also fluid in the cross axis.
147    for (i, child) in items.iter_mut().enumerate() {
148        let size = child.size();
149        let (size_main, size_cross) = axis.pack(size.width, size.height);
150
151        let fill_main_factor = size_main.fill_factor();
152        let fill_cross_factor = size_cross.fill_factor();
153        let main_is_static = main_compress || fill_main_factor == 0;
154
155        let category = match (main_is_static, cross_dynamic, fill_cross_factor == 0) {
156            (true, false, _) | (true, _, true) => Category::Static,
157            (true, true, false) => {
158                if let Length::Fixed(main) = size_main {
159                    available -= main;
160                    Category::CrossFluidDeferred(main)
161                } else {
162                    Category::CrossFluid
163                }
164            }
165            (false, _, _) => Category::MainFluid,
166        };
167
168        let meta = Meta {
169            main: size_main,
170            cross: size_cross,
171            resolved: false,
172            category,
173        };
174
175        cache.metas.push(meta);
176
177        match meta.main {
178            Length::Bounded {
179                sizing: length::Sizing::Fill(_),
180                bounds: length::Bounds::Min(min),
181            }
182            | Length::Fluid(length::Constraint::Min(min)) => {
183                min_total += min;
184                min_factors += fill_main_factor;
185                some_fill_min = true;
186            }
187            Length::Bounded {
188                sizing: length::Sizing::Fill(_),
189                bounds: length::Bounds::Max(_),
190            }
191            | Length::Fluid(length::Constraint::Max) => {
192                some_fill_max = true;
193            }
194            Length::Bounded {
195                sizing: length::Sizing::Fill(_),
196                bounds: length::Bounds::Both { .. },
197            } => {
198                some_fill_max = true;
199                some_fill_min = true;
200            }
201            _ => {}
202        }
203
204        let Category::Static = meta.category else {
205            fill_main_sum += fill_main_factor;
206            some_fill_cross = some_fill_cross || fill_cross_factor != 0;
207            continue;
208        };
209
210        let (max_width, max_height) = axis.pack(available, max_cross);
211
212        let child_limits = Limits::with_flags(
213            Size::ZERO,
214            Size::new(max_width, max_height),
215            compression,
216            infinite,
217        );
218
219        child.layout(&mut children[i], renderer, &child_limits);
220
221        let size = children[i].size;
222
223        available -= axis.main(size);
224        cross = cross.max(axis.cross(size));
225    }
226
227    // CROSS FLUID PASS
228    // If we must compress the cross axis and there are fluid elements in the
229    // cross axis, we lay out any of these elements that are also non-fluid in
230    // the main axis (i.e. the ones we deliberately skipped in the first pass).
231    //
232    // We use the maximum cross length obtained in the first pass as the maximum
233    // cross limit.
234    //
235    // We can defer the layout of any elements that have a fixed size in the main axis,
236    // allowing them to use the cross calculations of the next pass.
237    if cross_dynamic && some_fill_cross {
238        for (i, child) in items.iter_mut().enumerate() {
239            let meta = cache.metas[i];
240
241            let Category::CrossFluid = meta.category else {
242                continue;
243            };
244
245            let (max_width, max_height) =
246                axis.pack(available, if cross_dynamic { cross } else { max_cross });
247
248            let child_limits = Limits::with_flags(
249                Size::ZERO,
250                Size::new(max_width, max_height),
251                compression,
252                cross_infinite,
253            );
254
255            child.layout(&mut children[i], renderer, &child_limits);
256
257            let size = children[i].size;
258
259            available -= axis.main(size);
260            cross = cross.max(axis.cross(size));
261        }
262    }
263
264    let mut remaining = available.max(0.0);
265
266    // MIN / MAX PASSES
267    // We lay out any elements that are either bounded or fluid with some min / max constraints.
268    //
269    // First, we lay out any elements with `Max` bounds to potentially free up space; then
270    // another pass lays out elements with `Min` bounds.
271    //
272    // Since the remaining space may change mid-iteration, we need to revisit previous elements
273    // until they stabilize. Still, `layout` is only called once per element.
274    #[derive(Debug, Clone, Copy)]
275    enum Stage {
276        Max,
277        Min,
278    }
279
280    let mut step = if main_compress {
281        None
282    } else if some_fill_max {
283        Some(Stage::Max)
284    } else if some_fill_min {
285        Some(Stage::Min)
286    } else {
287        None
288    };
289
290    while let Some(stage) = step {
291        let current = remaining;
292        let (reserved_space, reserved_factors) = match stage {
293            Stage::Max => (min_total, min_factors),
294            Stage::Min => (0.0, 0),
295        };
296
297        for (i, child) in items.iter_mut().enumerate() {
298            let meta = &mut cache.metas[i];
299
300            if meta.resolved {
301                continue;
302            }
303
304            let fill_main_factor = meta.main.fill_factor();
305
306            if fill_main_factor == 0 {
307                continue;
308            }
309
310            let bounds = match stage {
311                Stage::Max => match meta.main {
312                    Length::Bounded {
313                        bounds: bounds @ (length::Bounds::Max(_) | length::Bounds::Both { .. }),
314                        ..
315                    } => bounds,
316                    Length::Fluid(length::Constraint::Max) => length::Bounds::Min(0.0),
317                    _ => continue,
318                },
319                Stage::Min => match meta.main {
320                    Length::Bounded {
321                        bounds: bounds @ (length::Bounds::Min(_) | length::Bounds::Both { .. }),
322                        ..
323                    } => bounds,
324                    Length::Fluid(length::Constraint::Min(min)) => length::Bounds::Min(min),
325                    _ => continue,
326                },
327            };
328
329            let max_available = (remaining - reserved_space) * fill_main_factor as f32
330                / (fill_main_sum - reserved_factors) as f32;
331
332            let max_available = if max_available.is_nan() {
333                f32::INFINITY
334            } else {
335                max_available
336            };
337
338            let (min, max) = match bounds {
339                length::Bounds::Max(max) => (0.0, max),
340                length::Bounds::Both { min, max } => (min, max),
341                length::Bounds::Min(min) => (min, max_available),
342            };
343
344            match stage {
345                Stage::Max if max > max_available => continue,
346                Stage::Min if min < max_available => continue,
347                _ => {}
348            }
349
350            let min = min.min(remaining);
351            let max = max.min(max_available).max(min);
352
353            let (max_cross, infinite) = if !cross_dynamic || meta.cross.fill_factor() == 0 {
354                (max_cross, infinite)
355            } else {
356                (cross, cross_infinite)
357            };
358
359            let (min_width, min_height) = axis.pack(min, 0.0);
360            let (max_width, max_height) = axis.pack(max, max_cross);
361
362            let child_limits = Limits::with_flags(
363                Size::new(min_width, min_height),
364                Size::new(max_width, max_height),
365                compression,
366                infinite,
367            );
368
369            child.layout(&mut children[i], renderer, &child_limits);
370
371            let size = children[i].size;
372
373            cross = cross.max(axis.cross(size));
374            remaining -= axis.main(size);
375            fill_main_sum -= fill_main_factor;
376            meta.resolved = true;
377        }
378
379        if remaining == current {
380            step = match stage {
381                Stage::Max if some_fill_min => Some(Stage::Min),
382                _ => None,
383            };
384        }
385    }
386
387    // MAIN FLUID PASS (conditional)
388    // We lay out the elements that are fluid in the main axis.
389    // We use the remaining space to evenly allocate space based on fill factors.
390    if !main_compress {
391        for (i, child) in items.iter_mut().enumerate() {
392            let meta = cache.metas[i];
393
394            if meta.resolved {
395                continue;
396            }
397
398            let Category::MainFluid = meta.category else {
399                continue;
400            };
401
402            let max_main = remaining * meta.main.fill_factor() as f32 / fill_main_sum as f32;
403
404            let max_main = if max_main.is_nan() {
405                f32::INFINITY
406            } else {
407                max_main
408            };
409
410            let min_main = if max_main.is_infinite() {
411                0.0
412            } else {
413                max_main
414            };
415
416            let (max_cross, infinite) = if !cross_dynamic || meta.cross.fill_factor() == 0 {
417                (max_cross, infinite)
418            } else {
419                (cross, cross_infinite)
420            };
421
422            let (min_width, min_height) = axis.pack(min_main, 0.0);
423            let (max_width, max_height) = axis.pack(max_main, max_cross);
424
425            let child_limits = Limits::with_flags(
426                Size::new(min_width, min_height),
427                Size::new(max_width, max_height),
428                compression,
429                infinite,
430            );
431
432            child.layout(&mut children[i], renderer, &child_limits);
433
434            let size = children[i].size;
435            cross = cross.max(axis.cross(size));
436        }
437    }
438
439    // CROSS FLUID DEFERRED PASS (conditional)
440    // We lay out any elements that were deferred in the second pass.
441    // These are elements that must be compressed in their cross axis and have
442    // a fixed length in the main axis.
443    if cross_dynamic && some_fill_cross {
444        for (i, child) in items.iter_mut().enumerate() {
445            let meta = cache.metas[i];
446
447            let Category::CrossFluidDeferred(main) = meta.category else {
448                continue;
449            };
450
451            let (max_width, max_height) = axis.pack(main, cross);
452
453            let child_limits = Limits::with_flags(
454                Size::ZERO,
455                Size::new(max_width, max_height),
456                Size::new(false, false),
457                cross_infinite,
458            );
459
460            child.layout(&mut children[i], renderer, &child_limits);
461
462            let size = children[i].size;
463            cross = cross.max(axis.cross(size));
464        }
465    }
466
467    let pad = axis.pack(padding.left, padding.top);
468    let mut main = pad.0;
469
470    let cross = match axis {
471        Axis::Horizontal => limits.resolve_height(height, cross),
472        Axis::Vertical => limits.resolve_width(width, cross),
473    };
474
475    // ALIGNMENT PASS
476    // We align all the laid out nodes in the cross axis, if needed.
477
478    for (i, tree) in children.iter_mut().enumerate() {
479        if i > 0 {
480            main += spacing;
481        }
482
483        let position = {
484            let (x, y) = axis.pack(main, pad.1);
485
486            Vector::new(x, y)
487        };
488
489        let alignment = match axis {
490            Axis::Horizontal => {
491                tree.size
492                    .align(Size::new(0.0, cross), Alignment::Start, align_items)
493            }
494            Axis::Vertical => tree
495                .size
496                .align(Size::new(cross, 0.0), align_items, Alignment::Start),
497        };
498
499        tree.translation = position + alignment;
500        main += axis.main(tree.size);
501    }
502
503    let main = match axis {
504        Axis::Horizontal => limits.resolve_width(width, main - pad.0),
505        Axis::Vertical => limits.resolve_height(height, main - pad.0),
506    };
507
508    Size::from(axis.pack(main, cross)).expand(padding)
509}