Skip to main content

cranpose_ui/layout/policies/
flex_measure_policy.rs

1use std::cell::RefCell;
2
3use cranpose_ui_graphics::whole;
4use cranpose_ui_layout::{
5    Axis, Constraints, MeasurePolicy, MeasureResult, MeasureScope, ParentData, Placement,
6    bias_offset, round_to_px,
7};
8use smallvec::SmallVec;
9
10use super::empty_measure_policy::empty_layout_hold;
11use crate::{
12    layout::core::{
13        Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
14    },
15    thread_state,
16};
17
18/// Unified Flex layout policy that powers both Row and Column.
19///
20/// This policy implements Jetpack Compose's flex layout semantics:
21/// - Measures children with proper loose constraints (min = 0 on both axes)
22/// - Supports weighted distribution of remaining space
23/// - Handles bounded/unbounded main axis correctly
24/// - Implements correct intrinsics for both axes
25///
26/// ## Overflow Behavior
27///
28/// Like Jetpack Compose, this policy **allows children to overflow** their container bounds:
29/// - Children can be positioned outside the parent's measured size
30/// - Overflowing content is rendered (unless clipped by a modifier)
31/// - When content overflows, distribution arrangements switch to `Start` to avoid negative spacing
32/// - `SpacedBy` keeps its fixed inter-child spacing even when content overflows
33///
34/// Example: A Row with 300px of content in a 200px container will:
35/// 1. Measure children at their natural sizes
36/// 2. Detect overflow (300px > 200px)
37/// 3. Switch to Start arrangement (pack children at the start)
38/// 4. Position last children beyond the 200px boundary
39///
40/// To prevent overflow:
41/// - Use weights for flexible sizing: `.weight(1.0, true)`
42/// - Use `fillMaxWidth()`/`fillMaxHeight()` modifiers
43/// - Design UI to fit within available space
44/// - Add a clip modifier to hide overflowing content
45///
46/// ## Weighted Children
47///
48/// When the main axis is bounded and children have weights:
49/// 1. Fixed children (no weight) are measured first
50/// 2. Remaining space is distributed proportionally to weights
51/// 3. Each weighted child gets: `remaining * (weight / total_weight)`
52/// 4. If `fill=true`, child gets tight constraints; if `fill=false`, loose constraints
53///
54/// When the main axis is unbounded, weights are ignored (all children wrap content).
55#[derive(Clone, Debug, PartialEq)]
56pub struct FlexMeasurePolicy {
57    /// Main axis direction (Horizontal for Row, Vertical for Column)
58    pub axis: Axis,
59    /// Arrangement along the main axis
60    pub main_axis_arrangement: LinearArrangement,
61    /// Alignment along the cross axis (used as default for children without explicit alignment)
62    pub cross_axis_alignment: CrossAxisAlignment,
63    /// The device pixel grid children are spaced and placed on, the
64    /// composition's density.
65    pub density: f32,
66    /// Direction used to resolve horizontal order and start/end alignment.
67    pub layout_direction: crate::LayoutDirection,
68}
69
70/// Compose's weight distribution in whole device pixels: each weighted
71/// child's share of the remaining space rounded half up, and the pixels
72/// that rounding gained or lost handed back one per child from the first.
73pub(crate) struct WeightShares {
74    density: f32,
75    unit_px: f32,
76    remainder_px: f32,
77}
78
79impl WeightShares {
80    pub(crate) fn new(
81        remaining: f32,
82        weights: impl Iterator<Item = f32> + Clone,
83        density: f32,
84    ) -> Self {
85        let density = if density > 0.0 && density.is_finite() {
86            density
87        } else {
88            1.0
89        };
90        let total_weight: f32 = weights.clone().sum();
91        let remaining_px = whole::round(remaining * density);
92        let unit_px = if total_weight > 0.0 {
93            remaining_px / total_weight
94        } else {
95            0.0
96        };
97        let rounded_px: f32 = weights
98            .map(|weight| whole::floor(unit_px * weight + 0.5))
99            .sum();
100        Self {
101            density,
102            unit_px,
103            remainder_px: remaining_px - rounded_px,
104        }
105    }
106
107    /// The main-axis size of the next weighted child, which weighs `weight`.
108    pub(crate) fn next_share(&mut self, weight: f32) -> f32 {
109        let step = if self.remainder_px > 0.0 {
110            1.0
111        } else if self.remainder_px < 0.0 {
112            -1.0
113        } else {
114            0.0
115        };
116        self.remainder_px -= step;
117        (whole::floor(self.unit_px * weight + 0.5) + step).max(0.0) / self.density
118    }
119}
120
121/// Cross-axis alignment for flex layouts.
122/// This is axis-agnostic and gets interpreted based on the flex axis.
123#[derive(Clone, Copy, Debug, PartialEq)]
124pub enum CrossAxisAlignment {
125    /// Align to the start of the cross axis (Top for Row, Start for Column)
126    Start,
127    /// Align to the center of the cross axis
128    Center,
129    /// Align to the end of the cross axis (Bottom for Row, End for Column)
130    End,
131}
132
133impl CrossAxisAlignment {
134    /// Where a child sits in `available` space: see [`bias_offset`].
135    fn align(&self, available: f32, child: f32, density: f32) -> f32 {
136        let bias = match self {
137            Self::Start => -1.0,
138            Self::Center => 0.0,
139            Self::End => 1.0,
140        };
141        bias_offset(bias, available, child, density)
142    }
143}
144
145impl From<HorizontalAlignment> for CrossAxisAlignment {
146    fn from(alignment: HorizontalAlignment) -> Self {
147        match alignment {
148            HorizontalAlignment::Start => Self::Start,
149            HorizontalAlignment::CenterHorizontally => Self::Center,
150            HorizontalAlignment::End => Self::End,
151        }
152    }
153}
154
155impl From<VerticalAlignment> for CrossAxisAlignment {
156    fn from(alignment: VerticalAlignment) -> Self {
157        match alignment {
158            VerticalAlignment::Top => Self::Start,
159            VerticalAlignment::CenterVertically => Self::Center,
160            VerticalAlignment::Bottom => Self::End,
161        }
162    }
163}
164
165impl FlexMeasurePolicy {
166    /// A flex layout along `axis` on the device pixel grid of `density`.
167    pub fn new(
168        axis: Axis,
169        main_axis_arrangement: LinearArrangement,
170        cross_axis_alignment: CrossAxisAlignment,
171        density: f32,
172    ) -> Self {
173        Self {
174            axis,
175            main_axis_arrangement,
176            cross_axis_alignment,
177            density,
178            layout_direction: crate::LayoutDirection::Ltr,
179        }
180    }
181
182    /// Creates a FlexMeasurePolicy for Row (horizontal main axis).
183    pub fn row(
184        horizontal_arrangement: LinearArrangement,
185        vertical_alignment: VerticalAlignment,
186        density: f32,
187    ) -> Self {
188        Self::new(
189            Axis::Horizontal,
190            horizontal_arrangement,
191            vertical_alignment.into(),
192            density,
193        )
194    }
195
196    /// Creates a FlexMeasurePolicy for Column (vertical main axis).
197    pub fn column(
198        vertical_arrangement: LinearArrangement,
199        horizontal_alignment: HorizontalAlignment,
200        density: f32,
201    ) -> Self {
202        Self::new(
203            Axis::Vertical,
204            vertical_arrangement,
205            horizontal_alignment.into(),
206            density,
207        )
208    }
209
210    fn get_axis_constraints(&self, constraints: Constraints) -> (f32, f32, f32, f32) {
211        match self.axis {
212            Axis::Horizontal => (
213                constraints.min_width,
214                constraints.max_width,
215                constraints.min_height,
216                constraints.max_height,
217            ),
218            Axis::Vertical => (
219                constraints.min_height,
220                constraints.max_height,
221                constraints.min_width,
222                constraints.max_width,
223            ),
224        }
225    }
226
227    fn make_constraints(
228        &self,
229        min_main: f32,
230        max_main: f32,
231        min_cross: f32,
232        max_cross: f32,
233    ) -> Constraints {
234        match self.axis {
235            Axis::Horizontal => Constraints {
236                min_width: min_main,
237                max_width: max_main,
238                min_height: min_cross,
239                max_height: max_cross,
240            },
241            Axis::Vertical => Constraints {
242                min_width: min_cross,
243                max_width: max_cross,
244                min_height: min_main,
245                max_height: max_main,
246            },
247        }
248    }
249
250    fn get_main_axis_size(&self, width: f32, height: f32) -> f32 {
251        match self.axis {
252            Axis::Horizontal => width,
253            Axis::Vertical => height,
254        }
255    }
256
257    fn get_cross_axis_size(&self, width: f32, height: f32) -> f32 {
258        match self.axis {
259            Axis::Horizontal => height,
260            Axis::Vertical => width,
261        }
262    }
263
264    /// Measures the unweighted children in order and returns Compose's
265    /// `fixedSpace` with the largest cross size. Each child is offered only
266    /// the main-axis space the ones before it leave, as
267    /// `RowColumnMeasurementHelper` offers it, and the spacing after a child
268    /// never runs past the main axis.
269    fn measure_fixed_children(
270        &self,
271        measurables: &[Box<dyn Measurable>],
272        children: &mut [FlexChild],
273        (max_main, max_cross): (f32, f32),
274        spacing: f32,
275    ) -> (f32, f32) {
276        let main_axis_bounded = max_main.is_finite();
277        let mut fixed_space = 0.0_f32;
278        let mut max_cross_size = 0.0_f32;
279        for (child, measurable) in children.iter_mut().zip(measurables) {
280            if child.data.has_weight() {
281                continue;
282            }
283            let available_main = if main_axis_bounded {
284                (max_main - fixed_space).max(0.0)
285            } else {
286                max_main
287            };
288            let placeable =
289                measurable.measure(self.make_constraints(0.0, available_main, 0.0, max_cross));
290            let main_size = self.get_main_axis_size(placeable.width(), placeable.height());
291            let spacing_after = if main_axis_bounded {
292                spacing.min(available_main - main_size).max(0.0)
293            } else {
294                spacing
295            };
296            fixed_space += main_size + spacing_after;
297            max_cross_size =
298                max_cross_size.max(self.get_cross_axis_size(placeable.width(), placeable.height()));
299            child.placeable = Some(placeable);
300        }
301        (fixed_space, max_cross_size)
302    }
303
304    /// Compose's intrinsic size along the main axis: the sizes `main_size`
305    /// gives of the unweighted children, the space in which each weighted
306    /// child gets its own size, and the spacing.
307    fn intrinsic_main_size(
308        &self,
309        measurables: &[Box<dyn Measurable>],
310        main_size: impl Fn(&dyn Measurable) -> f32,
311    ) -> f32 {
312        if measurables.is_empty() {
313            return 0.0;
314        }
315        let mut fixed_space = 0.0_f32;
316        let mut weight_unit = 0.0_f32;
317        let mut total_weight = 0.0_f32;
318        for measurable in measurables {
319            let size = main_size(measurable.as_ref());
320            match child_weight(measurable.as_ref()) {
321                Some(weight) => {
322                    total_weight += weight;
323                    weight_unit = weight_unit.max(round_to_px(size / weight, self.density));
324                }
325                None => fixed_space += size,
326            }
327        }
328        round_to_px(weight_unit * total_weight, self.density)
329            + fixed_space
330            + self.get_spacing() * (measurables.len() - 1) as f32
331    }
332
333    /// Compose's intrinsic size across the main axis, in `main_available`
334    /// main-axis space: each unweighted child asks for the main-axis size
335    /// `main_size` gives it, up to what the children before it leave, and
336    /// the weighted children share the rest. `cross_size` gives a child's
337    /// cross size in the main-axis space it gets.
338    fn intrinsic_cross_size(
339        &self,
340        measurables: &[Box<dyn Measurable>],
341        main_available: f32,
342        main_size: impl Fn(&dyn Measurable, f32) -> f32,
343        cross_size: impl Fn(&dyn Measurable, f32) -> f32,
344    ) -> f32 {
345        if measurables.is_empty() {
346            return 0.0;
347        }
348        let mut fixed_space =
349            (self.get_spacing() * (measurables.len() - 1) as f32).min(main_available);
350        let mut cross = 0.0_f32;
351        let mut total_weight = 0.0_f32;
352        for measurable in measurables {
353            let measurable = measurable.as_ref();
354            if let Some(weight) = child_weight(measurable) {
355                total_weight += weight;
356                continue;
357            }
358            let space =
359                main_size(measurable, f32::INFINITY).min((main_available - fixed_space).max(0.0));
360            fixed_space += space;
361            cross = cross.max(cross_size(measurable, space));
362        }
363        if total_weight == 0.0 {
364            return cross;
365        }
366        let weight_unit = if main_available.is_finite() {
367            round_to_px(
368                (main_available - fixed_space).max(0.0) / total_weight,
369                self.density,
370            )
371        } else {
372            f32::INFINITY
373        };
374        for measurable in measurables {
375            let measurable = measurable.as_ref();
376            if let Some(weight) = child_weight(measurable) {
377                cross = cross.max(cross_size(
378                    measurable,
379                    round_to_px(weight_unit * weight, self.density),
380                ));
381            }
382        }
383        cross
384    }
385
386    fn get_spacing(&self) -> f32 {
387        self.main_axis_arrangement.spacing(self.density)
388    }
389
390    fn measure_weighted_children(
391        &self,
392        measurables: &[Box<dyn Measurable>],
393        children: &mut [FlexChild],
394        (max_main, max_cross): (f32, f32),
395        (fixed_space, spacing): (f32, f32),
396    ) -> f32 {
397        let weights = children
398            .iter()
399            .filter(|child| child.data.has_weight())
400            .map(|child| child.data.weight);
401        let weighted = weights.clone().count();
402        if weighted == 0 {
403            return 0.0;
404        }
405        let remaining_main = (max_main - fixed_space - spacing * (weighted - 1) as f32).max(0.0);
406        let mut shares = WeightShares::new(remaining_main, weights, self.density);
407        let mut max_cross_size = 0.0_f32;
408        for (child, measurable) in children.iter_mut().zip(measurables) {
409            let data = &child.data;
410            if !data.has_weight() {
411                continue;
412            }
413            let constraints = if max_main.is_finite() {
414                let allocated = shares.next_share(data.weight);
415                let minimum = if data.fill { allocated } else { 0.0 };
416                self.make_constraints(minimum, allocated, 0.0, max_cross)
417            } else {
418                self.make_constraints(0.0, max_main, 0.0, max_cross)
419            };
420            let placeable = measurable.measure(constraints);
421            max_cross_size =
422                max_cross_size.max(self.get_cross_axis_size(placeable.width(), placeable.height()));
423            child.placeable = Some(placeable);
424        }
425        max_cross_size
426    }
427
428    fn measured_extents(&self, children: &[FlexChild]) -> (f32, f32, f32) {
429        let mut main = 0.0;
430        let mut before = 0.0_f32;
431        let mut after = 0.0_f32;
432        for (placeable, data) in measured(children) {
433            main += self.get_main_axis_size(placeable.width(), placeable.height());
434            if self.axis == Axis::Horizontal
435                && data.row_baseline
436                && let Some(baseline) = placeable.alignment_lines().first_baseline()
437            {
438                before = before.max(baseline);
439                after = after.max(placeable.height() - baseline);
440            }
441        }
442        (main, before, after)
443    }
444
445    fn cross_axis_position(
446        &self,
447        placeable: &cranpose_ui_layout::Placeable,
448        parent_data: &ParentData,
449        container_cross: f32,
450        before_baseline: f32,
451    ) -> f32 {
452        if self.axis == Axis::Horizontal && parent_data.row_baseline {
453            return placeable
454                .alignment_lines()
455                .first_baseline()
456                .map_or(0.0, |baseline| before_baseline - baseline);
457        }
458        let alignment = match self.axis {
459            Axis::Horizontal => parent_data
460                .row_alignment
461                .map_or(self.cross_axis_alignment, Into::into),
462            Axis::Vertical => parent_data
463                .column_alignment
464                .map_or(self.cross_axis_alignment, Into::into),
465        };
466        let child_cross = self.get_cross_axis_size(placeable.width(), placeable.height());
467        alignment.align(container_cross, child_cross, self.density)
468    }
469}
470
471/// The parent main-axis max range over which a held child keeps its size
472/// after `fixed_space` taken by the children before it, with `spacing`
473/// still fitting after it.
474fn child_main_range(
475    child_max: cranpose_ui_layout::BoundRange,
476    max_main: f32,
477    fixed_space: f32,
478    child_main: f32,
479    spacing: f32,
480) -> Option<cranpose_ui_layout::BoundRange> {
481    use cranpose_ui_layout::BoundRange;
482    if !max_main.is_finite() {
483        return child_max
484            .contains(f32::INFINITY)
485            .then_some(BoundRange::exactly(f32::INFINITY));
486    }
487    if (max_main - fixed_space).max(0.0) - child_main < spacing {
488        return None;
489    }
490    child_max
491        .outset(fixed_space)
492        .intersect(BoundRange::from(fixed_space + child_main + spacing))
493}
494
495/// The weight of a child that shares the main axis by weight.
496fn child_weight(measurable: &dyn Measurable) -> Option<f32> {
497    measurable
498        .flex_parent_data()
499        .map(|data| data.weight)
500        .filter(|weight| *weight > 0.0)
501}
502
503/// A child of a row or column while the row measures it: its parent data
504/// and, once measured, its placeable.
505struct FlexChild {
506    data: ParentData,
507    placeable: Option<cranpose_ui_layout::Placeable>,
508}
509
510// The child lists of a thread's rows and columns, kept between measures. A
511// list on the stack took 548 bytes on the ESP32 at every nesting level a
512// measure passes through, eight children inline.
513pub(crate) struct SpareFlexChildren(RefCell<Vec<Vec<FlexChild>>>);
514
515// Nested rows and columns each hold one list while they measure; a list over
516// this many spares goes back to the heap.
517const SPARE_LISTS: usize = 32;
518
519impl SpareFlexChildren {
520    pub(crate) const fn new() -> Self {
521        Self(RefCell::new(Vec::new()))
522    }
523
524    fn take() -> Vec<FlexChild> {
525        thread_state::with(|state| state.spare_flex_children.0.borrow_mut().pop())
526            .unwrap_or_default()
527    }
528
529    fn give(mut children: Vec<FlexChild>) {
530        children.clear();
531        thread_state::with(|state| {
532            let mut spares = state.spare_flex_children.0.borrow_mut();
533            if spares.len() < SPARE_LISTS {
534                spares.push(children);
535            }
536        });
537    }
538}
539
540/// The measured children with their parent data: every child, once both
541/// measuring passes ran, since each child is fixed or weighted.
542fn measured(
543    children: &[FlexChild],
544) -> impl Iterator<Item = (&cranpose_ui_layout::Placeable, &ParentData)> {
545    children
546        .iter()
547        .filter_map(|child| Some((child.placeable.as_ref()?, &child.data)))
548}
549
550impl MeasurePolicy for FlexMeasurePolicy {
551    fn measure(
552        &self,
553        scope: &dyn MeasureScope,
554        measurables: &[Box<dyn Measurable>],
555        constraints: Constraints,
556    ) -> MeasureResult {
557        let mut placements = Vec::new();
558        let measurement = self.measure_into(scope, measurables, constraints, &mut placements);
559        MeasureResult::new(measurement, placements)
560    }
561
562    /// A row or column of unweighted children holds while each child holds
563    /// under the main-axis space the ones before it leave, the spacing
564    /// after each keeps its full width, and the bounds still fit the sum.
565    fn measure_hold(
566        &self,
567        measurables: &[Box<dyn Measurable>],
568        constraints: Constraints,
569        size: crate::modifier::Size,
570    ) -> Option<cranpose_ui_layout::ConstraintsHold> {
571        use cranpose_ui_layout::{AxisHold, BoundRange, ConstraintsHold};
572        if measurables.is_empty() {
573            return empty_layout_hold(constraints, size);
574        }
575        if measurables
576            .iter()
577            .any(|measurable| measurable.parent_data().has_weight())
578        {
579            return None;
580        }
581        let (_, max_main, _, _) = self.get_axis_constraints(constraints);
582        let spacing = self.get_spacing();
583        let mut main = BoundRange::ANY;
584        let mut cross = BoundRange::ANY;
585        let mut fixed_space = 0.0_f32;
586        let mut total_main = 0.0_f32;
587        let mut cross_size = 0.0_f32;
588        for measurable in measurables {
589            let (child, hold) = measurable.measured_hold()?;
590            let (main_hold, cross_hold) = match self.axis {
591                Axis::Horizontal => (hold.width, hold.height),
592                Axis::Vertical => (hold.height, hold.width),
593            };
594            let child_main = self.get_main_axis_size(child.width, child.height);
595            main = main.intersect(child_main_range(
596                main_hold.max,
597                max_main,
598                fixed_space,
599                child_main,
600                spacing,
601            )?)?;
602            cross = cross.intersect(cross_hold.max)?;
603            fixed_space += child_main + spacing;
604            total_main += child_main;
605            cross_size = cross_size.max(self.get_cross_axis_size(child.width, child.height));
606        }
607        total_main += spacing * (measurables.len() - 1) as f32;
608        let size_main = self.get_main_axis_size(size.width, size.height);
609        let size_cross = self.get_cross_axis_size(size.width, size.height);
610        // Bounds that cut or stretched the layout size it from themselves.
611        if size_main != total_main || size_cross != cross_size {
612            return None;
613        }
614        let main = AxisHold {
615            min: BoundRange::up_to(size_main),
616            max: main.intersect(BoundRange::from(size_main))?,
617        };
618        let cross = AxisHold {
619            min: BoundRange::up_to(size_cross),
620            max: cross.intersect(BoundRange::from(size_cross))?,
621        };
622        Some(match self.axis {
623            Axis::Horizontal => ConstraintsHold {
624                width: main,
625                height: cross,
626            },
627            Axis::Vertical => ConstraintsHold {
628                width: cross,
629                height: main,
630            },
631        })
632    }
633
634    fn measure_into(
635        &self,
636        _scope: &dyn MeasureScope,
637        measurables: &[Box<dyn Measurable>],
638        constraints: Constraints,
639        placements: &mut Vec<Placement>,
640    ) -> cranpose_ui_layout::Measurement {
641        placements.clear();
642        if measurables.is_empty() {
643            let (width, height) = constraints.constrain(0.0, 0.0);
644            return crate::modifier::Size { width, height }.into();
645        }
646
647        let (min_main, max_main, min_cross, max_cross) = self.get_axis_constraints(constraints);
648        let spacing = self.get_spacing();
649
650        let mut children = SpareFlexChildren::take();
651        children.extend(measurables.iter().map(|child| FlexChild {
652            data: child.parent_data(),
653            placeable: None,
654        }));
655        let (fixed_space, mut max_cross_size) =
656            self.measure_fixed_children(measurables, &mut children, (max_main, max_cross), spacing);
657
658        let num_children = measurables.len();
659        let total_spacing = if num_children > 1 {
660            spacing * (num_children - 1) as f32
661        } else {
662            0.0
663        };
664
665        max_cross_size = max_cross_size.max(self.measure_weighted_children(
666            measurables,
667            &mut children,
668            (max_main, max_cross),
669            (fixed_space, spacing),
670        ));
671
672        let (total_main, before_baseline, after_baseline) = self.measured_extents(&children);
673        let total_main = total_main + total_spacing;
674        max_cross_size = max_cross_size.max(before_baseline + after_baseline);
675
676        let container_main = total_main.clamp(min_main, max_main);
677        let container_cross = max_cross_size.clamp(min_cross, max_cross);
678
679        let child_main_sizes: SmallVec<[f32; 8]> = measured(&children)
680            .map(|(p, _)| self.get_main_axis_size(p.width(), p.height()))
681            .collect();
682
683        let mut main_positions: SmallVec<[f32; 8]> =
684            SmallVec::with_capacity(child_main_sizes.len());
685        main_positions.resize(child_main_sizes.len(), 0.0);
686
687        let arrangement = if total_main > container_main && !self.main_axis_arrangement.is_spaced()
688        {
689            LinearArrangement::Start
690        } else {
691            self.main_axis_arrangement
692        };
693        arrangement.arrange(
694            self.density,
695            container_main,
696            &child_main_sizes,
697            &mut main_positions,
698        );
699
700        placements.reserve(children.len());
701        for ((placeable, data), &main_pos) in measured(&children).zip(&main_positions) {
702            let cross_pos =
703                self.cross_axis_position(placeable, data, container_cross, before_baseline);
704
705            let (x, y) = match self.axis {
706                Axis::Horizontal => (main_pos, cross_pos),
707                Axis::Vertical => (cross_pos, main_pos),
708            };
709            let width = match self.axis {
710                Axis::Horizontal => container_main,
711                Axis::Vertical => container_cross,
712            };
713            let x = self.layout_direction.place_x(x, width, placeable.width());
714
715            placeable.place(x, y);
716            placements.push(Placement::new(placeable.node_id(), x, y, 0.0));
717        }
718        SpareFlexChildren::give(children);
719
720        let (width, height) = match self.axis {
721            Axis::Horizontal => (container_main, container_cross),
722            Axis::Vertical => (container_cross, container_main),
723        };
724
725        crate::modifier::Size { width, height }.into()
726    }
727
728    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
729        match self.axis {
730            Axis::Horizontal => {
731                self.intrinsic_main_size(measurables, |m| m.min_intrinsic_width(height))
732            }
733            Axis::Vertical => self.intrinsic_cross_size(
734                measurables,
735                height,
736                |m, width| m.max_intrinsic_height(width),
737                |m, main| m.min_intrinsic_width(main),
738            ),
739        }
740    }
741
742    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
743        match self.axis {
744            Axis::Horizontal => {
745                self.intrinsic_main_size(measurables, |m| m.max_intrinsic_width(height))
746            }
747            Axis::Vertical => self.intrinsic_cross_size(
748                measurables,
749                height,
750                |m, width| m.max_intrinsic_height(width),
751                |m, main| m.max_intrinsic_width(main),
752            ),
753        }
754    }
755
756    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
757        match self.axis {
758            Axis::Horizontal => self.intrinsic_cross_size(
759                measurables,
760                width,
761                |m, height| m.max_intrinsic_width(height),
762                |m, main| m.min_intrinsic_height(main),
763            ),
764            Axis::Vertical => {
765                self.intrinsic_main_size(measurables, |m| m.min_intrinsic_height(width))
766            }
767        }
768    }
769
770    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
771        match self.axis {
772            Axis::Horizontal => self.intrinsic_cross_size(
773                measurables,
774                width,
775                |m, height| m.max_intrinsic_width(height),
776                |m, main| m.max_intrinsic_height(main),
777            ),
778            Axis::Vertical => {
779                self.intrinsic_main_size(measurables, |m| m.max_intrinsic_height(width))
780            }
781        }
782    }
783}