Skip to main content

cranpose_ui/layout/
policies.rs

1use cranpose_ui_graphics::whole;
2use cranpose_ui_layout::{
3    Axis, Constraints, FlexParentData, MeasurePolicy, MeasureResult, MeasureScope, ParentData,
4    Placement, bias_offset, round_to_px,
5};
6use smallvec::SmallVec;
7
8use crate::layout::core::{
9    Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
10};
11
12/// MeasurePolicy for Box layout - overlays children according to alignment.
13#[derive(Clone, Debug, PartialEq)]
14pub struct BoxMeasurePolicy {
15    /// Where children sit inside the box when they are smaller than it.
16    pub content_alignment: Alignment,
17    /// Whether the box's minimum constraints pass on to its children. When `false`, children may
18    /// measure smaller than the box's minimum size.
19    pub propagate_min_constraints: bool,
20    /// Direction used to resolve start and end alignment.
21    pub layout_direction: crate::LayoutDirection,
22}
23
24impl BoxMeasurePolicy {
25    /// Creates a policy that places children by `content_alignment` in a left-to-right layout
26    /// direction.
27    pub fn new(content_alignment: Alignment, propagate_min_constraints: bool) -> Self {
28        Self {
29            content_alignment,
30            propagate_min_constraints,
31            layout_direction: crate::LayoutDirection::Ltr,
32        }
33    }
34}
35
36impl MeasurePolicy for BoxMeasurePolicy {
37    fn measure(
38        &self,
39        scope: &dyn MeasureScope,
40        measurables: &[Box<dyn Measurable>],
41        constraints: Constraints,
42    ) -> MeasureResult {
43        let mut placements = Vec::new();
44        let measurement = self.measure_into(scope, measurables, constraints, &mut placements);
45        MeasureResult::new(measurement, placements)
46    }
47
48    /// A box holds while every child holds under the constraints it hands
49    /// on and the largest child, which sizes the box, still fits.
50    fn measure_hold(
51        &self,
52        measurables: &[Box<dyn Measurable>],
53        constraints: Constraints,
54        size: crate::modifier::Size,
55    ) -> Option<cranpose_ui_layout::ConstraintsHold> {
56        if measurables.is_empty() {
57            return empty_layout_hold(constraints, size);
58        }
59        let mut width = cranpose_ui_layout::BoundRange::ANY;
60        let mut height = cranpose_ui_layout::BoundRange::ANY;
61        let mut held = cranpose_ui_layout::ConstraintsHold::sized(size.width, size.height);
62        let mut largest = crate::modifier::Size::default();
63        for measurable in measurables {
64            let (child, hold) = measurable.measured_hold()?;
65            if self.propagate_min_constraints {
66                held = held.intersect(hold)?;
67            } else {
68                width = width.intersect(hold.width.max)?;
69                height = height.intersect(hold.height.max)?;
70            }
71            largest.width = largest.width.max(child.width);
72            largest.height = largest.height.max(child.height);
73        }
74        // Bounds that cut or stretched the box size it from themselves.
75        if size != largest {
76            return None;
77        }
78        held.width.max = held.width.max.intersect(width)?;
79        held.height.max = held.height.max.intersect(height)?;
80        Some(held)
81    }
82
83    fn measure_into(
84        &self,
85        scope: &dyn MeasureScope,
86        measurables: &[Box<dyn Measurable>],
87        constraints: Constraints,
88        placements: &mut Vec<Placement>,
89    ) -> cranpose_ui_layout::Measurement {
90        placements.clear();
91        let child_constraints = if self.propagate_min_constraints {
92            constraints
93        } else {
94            Constraints {
95                min_width: 0.0,
96                max_width: constraints.max_width,
97                min_height: 0.0,
98                max_height: constraints.max_height,
99            }
100        };
101
102        let mut max_width = 0.0_f32;
103        let mut max_height = 0.0_f32;
104        let mut placeables: SmallVec<[(cranpose_ui_layout::Placeable, Alignment); 8]> =
105            SmallVec::new();
106
107        for measurable in measurables {
108            let placeable = measurable.measure(child_constraints);
109            max_width = max_width.max(placeable.width());
110            max_height = max_height.max(placeable.height());
111            let alignment = measurable
112                .parent_data()
113                .box_alignment
114                .unwrap_or(self.content_alignment);
115            placeables.push((placeable, alignment));
116        }
117
118        let width = max_width.clamp(constraints.min_width, constraints.max_width);
119        let height = max_height.clamp(constraints.min_height, constraints.max_height);
120
121        placements.reserve(placeables.len());
122        // In place: a by-value loop moves the list's inline storage, hundreds
123        // of bytes, before its first step.
124        for (placeable, alignment) in &placeables {
125            let child_width = placeable.width();
126            let child_height = placeable.height();
127
128            let x = alignment
129                .horizontal
130                .align(width, child_width, scope.density());
131            let x = self.layout_direction.place_x(x, width, child_width);
132            let y = alignment
133                .vertical
134                .align(height, child_height, scope.density());
135
136            placeable.place(x, y);
137            placements.push(Placement::new(placeable.node_id(), x, y, 0));
138        }
139
140        crate::modifier::Size { width, height }.into()
141    }
142
143    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
144        measurables
145            .iter()
146            .map(|m| m.min_intrinsic_width(height))
147            .fold(0.0, f32::max)
148    }
149
150    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
151        measurables
152            .iter()
153            .map(|m| m.max_intrinsic_width(height))
154            .fold(0.0, f32::max)
155    }
156
157    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
158        measurables
159            .iter()
160            .map(|m| m.min_intrinsic_height(width))
161            .fold(0.0, f32::max)
162    }
163
164    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
165        measurables
166            .iter()
167            .map(|m| m.max_intrinsic_height(width))
168            .fold(0.0, f32::max)
169    }
170}
171
172/// Unified Flex layout policy that powers both Row and Column.
173///
174/// This policy implements Jetpack Compose's flex layout semantics:
175/// - Measures children with proper loose constraints (min = 0 on both axes)
176/// - Supports weighted distribution of remaining space
177/// - Handles bounded/unbounded main axis correctly
178/// - Implements correct intrinsics for both axes
179///
180/// ## Overflow Behavior
181///
182/// Like Jetpack Compose, this policy **allows children to overflow** their container bounds:
183/// - Children can be positioned outside the parent's measured size
184/// - Overflowing content is rendered (unless clipped by a modifier)
185/// - When content overflows, distribution arrangements switch to `Start` to avoid negative spacing
186/// - `SpacedBy` keeps its fixed inter-child spacing even when content overflows
187///
188/// Example: A Row with 300px of content in a 200px container will:
189/// 1. Measure children at their natural sizes
190/// 2. Detect overflow (300px > 200px)
191/// 3. Switch to Start arrangement (pack children at the start)
192/// 4. Position last children beyond the 200px boundary
193///
194/// To prevent overflow:
195/// - Use weights for flexible sizing: `.weight(1.0, true)`
196/// - Use `fillMaxWidth()`/`fillMaxHeight()` modifiers
197/// - Design UI to fit within available space
198/// - Add a clip modifier to hide overflowing content
199///
200/// ## Weighted Children
201///
202/// When the main axis is bounded and children have weights:
203/// 1. Fixed children (no weight) are measured first
204/// 2. Remaining space is distributed proportionally to weights
205/// 3. Each weighted child gets: `remaining * (weight / total_weight)`
206/// 4. If `fill=true`, child gets tight constraints; if `fill=false`, loose constraints
207///
208/// When the main axis is unbounded, weights are ignored (all children wrap content).
209#[derive(Clone, Debug, PartialEq)]
210pub struct FlexMeasurePolicy {
211    /// Main axis direction (Horizontal for Row, Vertical for Column)
212    pub axis: Axis,
213    /// Arrangement along the main axis
214    pub main_axis_arrangement: LinearArrangement,
215    /// Alignment along the cross axis (used as default for children without explicit alignment)
216    pub cross_axis_alignment: CrossAxisAlignment,
217    /// The device pixel grid children are spaced and placed on, the
218    /// composition's density.
219    pub density: f32,
220    /// Direction used to resolve horizontal order and start/end alignment.
221    pub layout_direction: crate::LayoutDirection,
222}
223
224/// Compose's weight distribution in whole device pixels: each weighted
225/// child's share of the remaining space rounded half up, and the pixels
226/// that rounding gained or lost handed back one per child from the first.
227pub(crate) struct WeightShares {
228    density: f32,
229    unit_px: f32,
230    remainder_px: f32,
231}
232
233impl WeightShares {
234    pub(crate) fn new(
235        remaining: f32,
236        weights: impl Iterator<Item = f32> + Clone,
237        density: f32,
238    ) -> Self {
239        let density = if density > 0.0 && density.is_finite() {
240            density
241        } else {
242            1.0
243        };
244        let total_weight: f32 = weights.clone().sum();
245        let remaining_px = whole::round(remaining * density);
246        let unit_px = if total_weight > 0.0 {
247            remaining_px / total_weight
248        } else {
249            0.0
250        };
251        let rounded_px: f32 = weights
252            .map(|weight| whole::floor(unit_px * weight + 0.5))
253            .sum();
254        Self {
255            density,
256            unit_px,
257            remainder_px: remaining_px - rounded_px,
258        }
259    }
260
261    /// The main-axis size of the next weighted child, which weighs `weight`.
262    pub(crate) fn next_share(&mut self, weight: f32) -> f32 {
263        let step = if self.remainder_px > 0.0 {
264            1.0
265        } else if self.remainder_px < 0.0 {
266            -1.0
267        } else {
268            0.0
269        };
270        self.remainder_px -= step;
271        (whole::floor(self.unit_px * weight + 0.5) + step).max(0.0) / self.density
272    }
273}
274
275/// Cross-axis alignment for flex layouts.
276/// This is axis-agnostic and gets interpreted based on the flex axis.
277#[derive(Clone, Copy, Debug, PartialEq)]
278pub enum CrossAxisAlignment {
279    /// Align to the start of the cross axis (Top for Row, Start for Column)
280    Start,
281    /// Align to the center of the cross axis
282    Center,
283    /// Align to the end of the cross axis (Bottom for Row, End for Column)
284    End,
285}
286
287impl CrossAxisAlignment {
288    /// Where a child sits in `available` space: see [`bias_offset`].
289    fn align(&self, available: f32, child: f32, density: f32) -> f32 {
290        let bias = match self {
291            CrossAxisAlignment::Start => -1.0,
292            CrossAxisAlignment::Center => 0.0,
293            CrossAxisAlignment::End => 1.0,
294        };
295        bias_offset(bias, available, child, density)
296    }
297}
298
299impl From<HorizontalAlignment> for CrossAxisAlignment {
300    fn from(alignment: HorizontalAlignment) -> Self {
301        match alignment {
302            HorizontalAlignment::Start => CrossAxisAlignment::Start,
303            HorizontalAlignment::CenterHorizontally => CrossAxisAlignment::Center,
304            HorizontalAlignment::End => CrossAxisAlignment::End,
305        }
306    }
307}
308
309impl From<VerticalAlignment> for CrossAxisAlignment {
310    fn from(alignment: VerticalAlignment) -> Self {
311        match alignment {
312            VerticalAlignment::Top => CrossAxisAlignment::Start,
313            VerticalAlignment::CenterVertically => CrossAxisAlignment::Center,
314            VerticalAlignment::Bottom => CrossAxisAlignment::End,
315        }
316    }
317}
318
319impl FlexMeasurePolicy {
320    /// A flex layout along `axis` on the device pixel grid of `density`.
321    pub fn new(
322        axis: Axis,
323        main_axis_arrangement: LinearArrangement,
324        cross_axis_alignment: CrossAxisAlignment,
325        density: f32,
326    ) -> Self {
327        Self {
328            axis,
329            main_axis_arrangement,
330            cross_axis_alignment,
331            density,
332            layout_direction: crate::LayoutDirection::Ltr,
333        }
334    }
335
336    /// Creates a FlexMeasurePolicy for Row (horizontal main axis).
337    pub fn row(
338        horizontal_arrangement: LinearArrangement,
339        vertical_alignment: VerticalAlignment,
340        density: f32,
341    ) -> Self {
342        Self::new(
343            Axis::Horizontal,
344            horizontal_arrangement,
345            vertical_alignment.into(),
346            density,
347        )
348    }
349
350    /// Creates a FlexMeasurePolicy for Column (vertical main axis).
351    pub fn column(
352        vertical_arrangement: LinearArrangement,
353        horizontal_alignment: HorizontalAlignment,
354        density: f32,
355    ) -> Self {
356        Self::new(
357            Axis::Vertical,
358            vertical_arrangement,
359            horizontal_alignment.into(),
360            density,
361        )
362    }
363
364    fn get_axis_constraints(&self, constraints: Constraints) -> (f32, f32, f32, f32) {
365        match self.axis {
366            Axis::Horizontal => (
367                constraints.min_width,
368                constraints.max_width,
369                constraints.min_height,
370                constraints.max_height,
371            ),
372            Axis::Vertical => (
373                constraints.min_height,
374                constraints.max_height,
375                constraints.min_width,
376                constraints.max_width,
377            ),
378        }
379    }
380
381    fn make_constraints(
382        &self,
383        min_main: f32,
384        max_main: f32,
385        min_cross: f32,
386        max_cross: f32,
387    ) -> Constraints {
388        match self.axis {
389            Axis::Horizontal => Constraints {
390                min_width: min_main,
391                max_width: max_main,
392                min_height: min_cross,
393                max_height: max_cross,
394            },
395            Axis::Vertical => Constraints {
396                min_width: min_cross,
397                max_width: max_cross,
398                min_height: min_main,
399                max_height: max_main,
400            },
401        }
402    }
403
404    fn get_main_axis_size(&self, width: f32, height: f32) -> f32 {
405        match self.axis {
406            Axis::Horizontal => width,
407            Axis::Vertical => height,
408        }
409    }
410
411    fn get_cross_axis_size(&self, width: f32, height: f32) -> f32 {
412        match self.axis {
413            Axis::Horizontal => height,
414            Axis::Vertical => width,
415        }
416    }
417
418    /// Measures the unweighted children in order and returns Compose's
419    /// `fixedSpace` with the largest cross size. Each child is offered only
420    /// the main-axis space the ones before it leave, as
421    /// `RowColumnMeasurementHelper` offers it, and the spacing after a child
422    /// never runs past the main axis.
423    fn measure_fixed_children(
424        &self,
425        measurables: &[Box<dyn Measurable>],
426        fixed_children: &[usize],
427        (max_main, max_cross): (f32, f32),
428        spacing: f32,
429        placeables: &mut [Option<cranpose_ui_layout::Placeable>],
430    ) -> (f32, f32) {
431        let main_axis_bounded = max_main.is_finite();
432        let mut fixed_space = 0.0_f32;
433        let mut max_cross_size = 0.0_f32;
434        for &idx in fixed_children {
435            let available_main = if main_axis_bounded {
436                (max_main - fixed_space).max(0.0)
437            } else {
438                max_main
439            };
440            let placeable = measurables[idx].measure(self.make_constraints(
441                0.0,
442                available_main,
443                0.0,
444                max_cross,
445            ));
446            let main_size = self.get_main_axis_size(placeable.width(), placeable.height());
447            let spacing_after = if main_axis_bounded {
448                spacing.min(available_main - main_size).max(0.0)
449            } else {
450                spacing
451            };
452            fixed_space += main_size + spacing_after;
453            max_cross_size =
454                max_cross_size.max(self.get_cross_axis_size(placeable.width(), placeable.height()));
455            placeables[idx] = Some(placeable);
456        }
457        (fixed_space, max_cross_size)
458    }
459
460    /// Compose's intrinsic size along the main axis: the sizes `main_size`
461    /// gives of the unweighted children, the space in which each weighted
462    /// child gets its own size, and the spacing.
463    fn intrinsic_main_size(
464        &self,
465        measurables: &[Box<dyn Measurable>],
466        main_size: impl Fn(&dyn Measurable) -> f32,
467    ) -> f32 {
468        if measurables.is_empty() {
469            return 0.0;
470        }
471        let mut fixed_space = 0.0_f32;
472        let mut weight_unit = 0.0_f32;
473        let mut total_weight = 0.0_f32;
474        for measurable in measurables {
475            let size = main_size(measurable.as_ref());
476            match child_weight(measurable.as_ref()) {
477                Some(weight) => {
478                    total_weight += weight;
479                    weight_unit = weight_unit.max(round_to_px(size / weight, self.density));
480                }
481                None => fixed_space += size,
482            }
483        }
484        round_to_px(weight_unit * total_weight, self.density)
485            + fixed_space
486            + self.get_spacing() * (measurables.len() - 1) as f32
487    }
488
489    /// Compose's intrinsic size across the main axis, in `main_available`
490    /// main-axis space: each unweighted child asks for the main-axis size
491    /// `main_size` gives it, up to what the children before it leave, and
492    /// the weighted children share the rest. `cross_size` gives a child's
493    /// cross size in the main-axis space it gets.
494    fn intrinsic_cross_size(
495        &self,
496        measurables: &[Box<dyn Measurable>],
497        main_available: f32,
498        main_size: impl Fn(&dyn Measurable, f32) -> f32,
499        cross_size: impl Fn(&dyn Measurable, f32) -> f32,
500    ) -> f32 {
501        if measurables.is_empty() {
502            return 0.0;
503        }
504        let mut fixed_space =
505            (self.get_spacing() * (measurables.len() - 1) as f32).min(main_available);
506        let mut cross = 0.0_f32;
507        let mut total_weight = 0.0_f32;
508        for measurable in measurables {
509            let measurable = measurable.as_ref();
510            if let Some(weight) = child_weight(measurable) {
511                total_weight += weight;
512                continue;
513            }
514            let space =
515                main_size(measurable, f32::INFINITY).min((main_available - fixed_space).max(0.0));
516            fixed_space += space;
517            cross = cross.max(cross_size(measurable, space));
518        }
519        if total_weight == 0.0 {
520            return cross;
521        }
522        let weight_unit = if main_available.is_finite() {
523            round_to_px(
524                (main_available - fixed_space).max(0.0) / total_weight,
525                self.density,
526            )
527        } else {
528            f32::INFINITY
529        };
530        for measurable in measurables {
531            let measurable = measurable.as_ref();
532            if let Some(weight) = child_weight(measurable) {
533                cross = cross.max(cross_size(
534                    measurable,
535                    round_to_px(weight_unit * weight, self.density),
536                ));
537            }
538        }
539        cross
540    }
541
542    fn get_spacing(&self) -> f32 {
543        self.main_axis_arrangement.spacing(self.density)
544    }
545
546    fn measure_weighted_children(
547        &self,
548        measurables: &[Box<dyn Measurable>],
549        weighted_children: &[(usize, FlexParentData)],
550        (max_main, max_cross): (f32, f32),
551        (fixed_space, spacing): (f32, f32),
552        placeables: &mut [Option<cranpose_ui_layout::Placeable>],
553    ) -> f32 {
554        let remaining_main =
555            (max_main - fixed_space - spacing * weighted_children.len().saturating_sub(1) as f32)
556                .max(0.0);
557        let mut shares = WeightShares::new(
558            remaining_main,
559            weighted_children.iter().map(|(_, data)| data.weight),
560            self.density,
561        );
562        let mut max_cross_size = 0.0_f32;
563        for &(idx, data) in weighted_children {
564            let constraints = if max_main.is_finite() {
565                let allocated = shares.next_share(data.weight);
566                let minimum = if data.fill { allocated } else { 0.0 };
567                self.make_constraints(minimum, allocated, 0.0, max_cross)
568            } else {
569                self.make_constraints(0.0, max_main, 0.0, max_cross)
570            };
571            let placeable = measurables[idx].measure(constraints);
572            max_cross_size =
573                max_cross_size.max(self.get_cross_axis_size(placeable.width(), placeable.height()));
574            placeables[idx] = Some(placeable);
575        }
576        max_cross_size
577    }
578
579    fn measured_extents(
580        &self,
581        placeables: &[cranpose_ui_layout::Placeable],
582        parent_data: &[cranpose_ui_layout::ParentData],
583    ) -> (f32, f32, f32) {
584        let mut main = 0.0;
585        let mut before = 0.0_f32;
586        let mut after = 0.0_f32;
587        for (placeable, data) in placeables.iter().zip(parent_data) {
588            main += self.get_main_axis_size(placeable.width(), placeable.height());
589            if self.axis == Axis::Horizontal
590                && data.row_baseline
591                && let Some(baseline) = placeable.alignment_lines().first_baseline()
592            {
593                before = before.max(baseline);
594                after = after.max(placeable.height() - baseline);
595            }
596        }
597        (main, before, after)
598    }
599
600    fn cross_axis_position(
601        &self,
602        placeable: &cranpose_ui_layout::Placeable,
603        parent_data: &ParentData,
604        container_cross: f32,
605        before_baseline: f32,
606    ) -> f32 {
607        if self.axis == Axis::Horizontal && parent_data.row_baseline {
608            return placeable
609                .alignment_lines()
610                .first_baseline()
611                .map_or(0.0, |baseline| before_baseline - baseline);
612        }
613        let alignment = match self.axis {
614            Axis::Horizontal => parent_data
615                .row_alignment
616                .map_or(self.cross_axis_alignment, Into::into),
617            Axis::Vertical => parent_data
618                .column_alignment
619                .map_or(self.cross_axis_alignment, Into::into),
620        };
621        let child_cross = self.get_cross_axis_size(placeable.width(), placeable.height());
622        alignment.align(container_cross, child_cross, self.density)
623    }
624}
625
626/// The parent main-axis max range over which a held child keeps its size
627/// after `fixed_space` taken by the children before it, with `spacing`
628/// still fitting after it.
629fn child_main_range(
630    child_max: cranpose_ui_layout::BoundRange,
631    max_main: f32,
632    fixed_space: f32,
633    child_main: f32,
634    spacing: f32,
635) -> Option<cranpose_ui_layout::BoundRange> {
636    use cranpose_ui_layout::BoundRange;
637    if !max_main.is_finite() {
638        return child_max
639            .contains(f32::INFINITY)
640            .then_some(BoundRange::exactly(f32::INFINITY));
641    }
642    if (max_main - fixed_space).max(0.0) - child_main < spacing {
643        return None;
644    }
645    child_max
646        .outset(fixed_space)
647        .intersect(BoundRange::from(fixed_space + child_main + spacing))
648}
649
650/// The weight of a child that shares the main axis by weight.
651fn child_weight(measurable: &dyn Measurable) -> Option<f32> {
652    measurable
653        .flex_parent_data()
654        .map(|data| data.weight)
655        .filter(|weight| *weight > 0.0)
656}
657
658impl MeasurePolicy for FlexMeasurePolicy {
659    fn measure(
660        &self,
661        scope: &dyn MeasureScope,
662        measurables: &[Box<dyn Measurable>],
663        constraints: Constraints,
664    ) -> MeasureResult {
665        let mut placements = Vec::new();
666        let measurement = self.measure_into(scope, measurables, constraints, &mut placements);
667        MeasureResult::new(measurement, placements)
668    }
669
670    /// A row or column of unweighted children holds while each child holds
671    /// under the main-axis space the ones before it leave, the spacing
672    /// after each keeps its full width, and the bounds still fit the sum.
673    fn measure_hold(
674        &self,
675        measurables: &[Box<dyn Measurable>],
676        constraints: Constraints,
677        size: crate::modifier::Size,
678    ) -> Option<cranpose_ui_layout::ConstraintsHold> {
679        use cranpose_ui_layout::{AxisHold, BoundRange, ConstraintsHold};
680        if measurables.is_empty() {
681            return empty_layout_hold(constraints, size);
682        }
683        if measurables
684            .iter()
685            .any(|measurable| measurable.parent_data().has_weight())
686        {
687            return None;
688        }
689        let (_, max_main, _, _) = self.get_axis_constraints(constraints);
690        let spacing = self.get_spacing();
691        let mut main = BoundRange::ANY;
692        let mut cross = BoundRange::ANY;
693        let mut fixed_space = 0.0_f32;
694        let mut total_main = 0.0_f32;
695        let mut cross_size = 0.0_f32;
696        for measurable in measurables {
697            let (child, hold) = measurable.measured_hold()?;
698            let (main_hold, cross_hold) = match self.axis {
699                Axis::Horizontal => (hold.width, hold.height),
700                Axis::Vertical => (hold.height, hold.width),
701            };
702            let child_main = self.get_main_axis_size(child.width, child.height);
703            main = main.intersect(child_main_range(
704                main_hold.max,
705                max_main,
706                fixed_space,
707                child_main,
708                spacing,
709            )?)?;
710            cross = cross.intersect(cross_hold.max)?;
711            fixed_space += child_main + spacing;
712            total_main += child_main;
713            cross_size = cross_size.max(self.get_cross_axis_size(child.width, child.height));
714        }
715        total_main += spacing * (measurables.len() - 1) as f32;
716        let size_main = self.get_main_axis_size(size.width, size.height);
717        let size_cross = self.get_cross_axis_size(size.width, size.height);
718        // Bounds that cut or stretched the layout size it from themselves.
719        if size_main != total_main || size_cross != cross_size {
720            return None;
721        }
722        let main = AxisHold {
723            min: BoundRange::up_to(size_main),
724            max: main.intersect(BoundRange::from(size_main))?,
725        };
726        let cross = AxisHold {
727            min: BoundRange::up_to(size_cross),
728            max: cross.intersect(BoundRange::from(size_cross))?,
729        };
730        Some(match self.axis {
731            Axis::Horizontal => ConstraintsHold {
732                width: main,
733                height: cross,
734            },
735            Axis::Vertical => ConstraintsHold {
736                width: cross,
737                height: main,
738            },
739        })
740    }
741
742    fn measure_into(
743        &self,
744        _scope: &dyn MeasureScope,
745        measurables: &[Box<dyn Measurable>],
746        constraints: Constraints,
747        placements: &mut Vec<Placement>,
748    ) -> cranpose_ui_layout::Measurement {
749        placements.clear();
750        if measurables.is_empty() {
751            let (width, height) = constraints.constrain(0.0, 0.0);
752            return crate::modifier::Size { width, height }.into();
753        }
754
755        let (min_main, max_main, min_cross, max_cross) = self.get_axis_constraints(constraints);
756        let spacing = self.get_spacing();
757
758        let mut fixed_children: SmallVec<[usize; 8]> = SmallVec::new();
759        let parent_data: SmallVec<[ParentData; 8]> = measurables
760            .iter()
761            .map(|child| child.parent_data())
762            .collect();
763        let mut weighted_children: SmallVec<[(usize, FlexParentData); 8]> = SmallVec::new();
764
765        for (idx, data) in parent_data.iter().enumerate() {
766            if data.has_weight() {
767                weighted_children.push((idx, FlexParentData::new(data.weight, data.fill)));
768            } else {
769                fixed_children.push(idx);
770            }
771        }
772
773        let child_constraints = self.make_constraints(0.0, max_main, 0.0, max_cross);
774
775        let mut placeables: SmallVec<[Option<cranpose_ui_layout::Placeable>; 8]> = SmallVec::new();
776        placeables.resize_with(measurables.len(), || None);
777        let (fixed_space, mut max_cross_size) = self.measure_fixed_children(
778            measurables,
779            &fixed_children,
780            (max_main, max_cross),
781            spacing,
782            &mut placeables,
783        );
784
785        let num_children = measurables.len();
786        let total_spacing = if num_children > 1 {
787            spacing * (num_children - 1) as f32
788        } else {
789            0.0
790        };
791
792        max_cross_size = max_cross_size.max(self.measure_weighted_children(
793            measurables,
794            &weighted_children,
795            (max_main, max_cross),
796            (fixed_space, spacing),
797            &mut placeables,
798        ));
799
800        let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = placeables
801            .drain(..)
802            .enumerate()
803            .map(|(idx, placeable)| {
804                placeable.unwrap_or_else(|| measurables[idx].measure(child_constraints))
805            })
806            .collect();
807
808        let (total_main, before_baseline, after_baseline) =
809            self.measured_extents(&placeables, &parent_data);
810        let total_main = total_main + total_spacing;
811        max_cross_size = max_cross_size.max(before_baseline + after_baseline);
812
813        let container_main = total_main.clamp(min_main, max_main);
814        let container_cross = max_cross_size.clamp(min_cross, max_cross);
815
816        let child_main_sizes: SmallVec<[f32; 8]> = placeables
817            .iter()
818            .map(|p| self.get_main_axis_size(p.width(), p.height()))
819            .collect();
820
821        let mut main_positions: SmallVec<[f32; 8]> =
822            SmallVec::with_capacity(child_main_sizes.len());
823        main_positions.resize(child_main_sizes.len(), 0.0);
824
825        let arrangement = if total_main > container_main && !self.main_axis_arrangement.is_spaced()
826        {
827            LinearArrangement::Start
828        } else {
829            self.main_axis_arrangement
830        };
831        arrangement.arrange(
832            self.density,
833            container_main,
834            &child_main_sizes,
835            &mut main_positions,
836        );
837
838        placements.reserve(placeables.len());
839        for (idx, (placeable, &main_pos)) in placeables.iter().zip(&main_positions).enumerate() {
840            let cross_pos = self.cross_axis_position(
841                placeable,
842                &parent_data[idx],
843                container_cross,
844                before_baseline,
845            );
846
847            let (x, y) = match self.axis {
848                Axis::Horizontal => (main_pos, cross_pos),
849                Axis::Vertical => (cross_pos, main_pos),
850            };
851            let width = match self.axis {
852                Axis::Horizontal => container_main,
853                Axis::Vertical => container_cross,
854            };
855            let x = self.layout_direction.place_x(x, width, placeable.width());
856
857            placeable.place(x, y);
858            placements.push(Placement::new(placeable.node_id(), x, y, 0));
859        }
860
861        let (width, height) = match self.axis {
862            Axis::Horizontal => (container_main, container_cross),
863            Axis::Vertical => (container_cross, container_main),
864        };
865
866        crate::modifier::Size { width, height }.into()
867    }
868
869    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
870        match self.axis {
871            Axis::Horizontal => {
872                self.intrinsic_main_size(measurables, |m| m.min_intrinsic_width(height))
873            }
874            Axis::Vertical => self.intrinsic_cross_size(
875                measurables,
876                height,
877                |m, width| m.max_intrinsic_height(width),
878                |m, main| m.min_intrinsic_width(main),
879            ),
880        }
881    }
882
883    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
884        match self.axis {
885            Axis::Horizontal => {
886                self.intrinsic_main_size(measurables, |m| m.max_intrinsic_width(height))
887            }
888            Axis::Vertical => self.intrinsic_cross_size(
889                measurables,
890                height,
891                |m, width| m.max_intrinsic_height(width),
892                |m, main| m.max_intrinsic_width(main),
893            ),
894        }
895    }
896
897    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
898        match self.axis {
899            Axis::Horizontal => self.intrinsic_cross_size(
900                measurables,
901                width,
902                |m, height| m.max_intrinsic_width(height),
903                |m, main| m.min_intrinsic_height(main),
904            ),
905            Axis::Vertical => {
906                self.intrinsic_main_size(measurables, |m| m.min_intrinsic_height(width))
907            }
908        }
909    }
910
911    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
912        match self.axis {
913            Axis::Horizontal => self.intrinsic_cross_size(
914                measurables,
915                width,
916                |m, height| m.max_intrinsic_width(height),
917                |m, main| m.max_intrinsic_height(main),
918            ),
919            Axis::Vertical => {
920                self.intrinsic_main_size(measurables, |m| m.max_intrinsic_height(width))
921            }
922        }
923    }
924}
925
926/// Which way a flow layout places its children before it wraps.
927#[derive(Clone, Copy, Debug, PartialEq, Eq)]
928pub enum FlowAxis {
929    /// Left to right (right to left in a right-to-left layout), wrapping
930    /// onto the next row: Compose's `FlowRow`.
931    Horizontal,
932    /// Top to bottom, wrapping onto the next column, which starts at the
933    /// end side in a right-to-left layout: Compose's `FlowColumn`.
934    Vertical,
935}
936
937/// MeasurePolicy for `FlowRow` and `FlowColumn`: children go along the main
938/// axis and wrap onto the next line when the available length runs out
939/// (Jetpack Compose `FlowRow`, `FlowColumn`).
940///
941/// - Children are measured with loose constraints (min = 0) capped at the
942///   incoming max width and height, then packed along the main axis.
943/// - A child that no longer fits on the current line starts a new line; a
944///   child longer than the whole line gets a line of its own (and may
945///   overflow, like Compose).
946/// - `main_axis_spacing` separates children on the same line and
947///   `cross_axis_spacing` separates lines; children align to the start of
948///   their line.
949/// - With an unbounded main axis everything stays on one line.
950#[derive(Clone, Debug, PartialEq)]
951pub struct FlowMeasurePolicy {
952    /// Which way children go before they wrap.
953    pub axis: FlowAxis,
954    /// Gap between adjacent children on the same line, in dp.
955    pub main_axis_spacing: f32,
956    /// Gap between consecutive lines, in dp.
957    pub cross_axis_spacing: f32,
958    /// Which side the horizontal axis starts at.
959    pub layout_direction: crate::LayoutDirection,
960}
961
962impl FlowMeasurePolicy {
963    /// Creates a policy along `axis` with the given gaps in logical pixels.
964    /// Negative gaps count as zero. The layout direction is left to right.
965    pub fn new(axis: FlowAxis, main_axis_spacing: f32, cross_axis_spacing: f32) -> Self {
966        Self {
967            axis,
968            main_axis_spacing: main_axis_spacing.max(0.0),
969            cross_axis_spacing: cross_axis_spacing.max(0.0),
970            layout_direction: crate::LayoutDirection::Ltr,
971        }
972    }
973
974    /// `(main, cross)` of a width and height, and back: the same swap.
975    fn along(&self, width: f32, height: f32) -> (f32, f32) {
976        match self.axis {
977            FlowAxis::Horizontal => (width, height),
978            FlowAxis::Vertical => (height, width),
979        }
980    }
981
982    /// A child's intrinsic length along the main axis when the cross axis
983    /// has `cross` room.
984    fn main_intrinsic(&self, measurable: &dyn Measurable, cross: f32, min: bool) -> f32 {
985        match (self.axis, min) {
986            (FlowAxis::Horizontal, true) => measurable.min_intrinsic_width(cross),
987            (FlowAxis::Horizontal, false) => measurable.max_intrinsic_width(cross),
988            (FlowAxis::Vertical, true) => measurable.min_intrinsic_height(cross),
989            (FlowAxis::Vertical, false) => measurable.max_intrinsic_height(cross),
990        }
991    }
992
993    /// A child's intrinsic length across the main axis when it takes `main`.
994    fn cross_intrinsic(&self, measurable: &dyn Measurable, main: f32, min: bool) -> f32 {
995        match (self.axis, min) {
996            (FlowAxis::Horizontal, true) => measurable.min_intrinsic_height(main),
997            (FlowAxis::Horizontal, false) => measurable.max_intrinsic_height(main),
998            (FlowAxis::Vertical, true) => measurable.min_intrinsic_width(main),
999            (FlowAxis::Vertical, false) => measurable.max_intrinsic_width(main),
1000        }
1001    }
1002
1003    /// The cross-axis length the children take when they wrap at
1004    /// `available_main`.
1005    fn wrapped_cross(
1006        &self,
1007        measurables: &[Box<dyn Measurable>],
1008        available_main: f32,
1009        min: bool,
1010    ) -> f32 {
1011        let mut cursor = 0.0_f32;
1012        let mut line_start = 0.0_f32;
1013        let mut line_cross = 0.0_f32;
1014
1015        for measurable in measurables {
1016            let child_main = self.main_intrinsic(measurable.as_ref(), f32::INFINITY, false);
1017            let child_cross = self.cross_intrinsic(measurable.as_ref(), child_main, min);
1018
1019            if cursor > 0.0 && cursor + self.main_axis_spacing + child_main > available_main {
1020                line_start += line_cross + self.cross_axis_spacing;
1021                cursor = 0.0;
1022                line_cross = 0.0;
1023            }
1024            cursor += if cursor > 0.0 {
1025                self.main_axis_spacing + child_main
1026            } else {
1027                child_main
1028            };
1029            line_cross = line_cross.max(child_cross);
1030        }
1031
1032        line_start + line_cross
1033    }
1034
1035    /// The main-axis length of every child on one line.
1036    fn unwrapped_main(&self, measurables: &[Box<dyn Measurable>], cross: f32) -> f32 {
1037        let total_spacing = self.main_axis_spacing * measurables.len().saturating_sub(1) as f32;
1038        measurables
1039            .iter()
1040            .map(|m| self.main_intrinsic(m.as_ref(), cross, false))
1041            .sum::<f32>()
1042            + total_spacing
1043    }
1044
1045    /// The longest child's smallest main-axis length: a line each.
1046    fn narrowest_main(&self, measurables: &[Box<dyn Measurable>], cross: f32) -> f32 {
1047        measurables
1048            .iter()
1049            .map(|m| self.main_intrinsic(m.as_ref(), cross, true))
1050            .fold(0.0, f32::max)
1051    }
1052}
1053
1054impl MeasurePolicy for FlowMeasurePolicy {
1055    fn measure(
1056        &self,
1057        scope: &dyn MeasureScope,
1058        measurables: &[Box<dyn Measurable>],
1059        constraints: Constraints,
1060    ) -> MeasureResult {
1061        let mut placements = Vec::new();
1062        let measurement = self.measure_into(scope, measurables, constraints, &mut placements);
1063        MeasureResult::new(measurement, placements)
1064    }
1065
1066    fn measure_into(
1067        &self,
1068        _scope: &dyn MeasureScope,
1069        measurables: &[Box<dyn Measurable>],
1070        constraints: Constraints,
1071        placements: &mut Vec<Placement>,
1072    ) -> cranpose_ui_layout::Measurement {
1073        placements.clear();
1074        if measurables.is_empty() {
1075            let (width, height) = constraints.constrain(0.0, 0.0);
1076            return crate::modifier::Size { width, height }.into();
1077        }
1078
1079        let child_constraints = Constraints {
1080            min_width: 0.0,
1081            max_width: constraints.max_width,
1082            min_height: 0.0,
1083            max_height: constraints.max_height,
1084        };
1085
1086        let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = measurables
1087            .iter()
1088            .map(|measurable| measurable.measure(child_constraints))
1089            .collect();
1090
1091        let (max_main, _) = self.along(constraints.max_width, constraints.max_height);
1092        let mut cursor = 0.0_f32;
1093        let mut line_start = 0.0_f32;
1094        let mut line_cross = 0.0_f32;
1095        let mut longest_line = 0.0_f32;
1096
1097        placements.reserve(placeables.len());
1098        for placeable in &placeables {
1099            let (child_main, child_cross) = self.along(placeable.width(), placeable.height());
1100
1101            if cursor > 0.0 && cursor + self.main_axis_spacing + child_main > max_main {
1102                longest_line = longest_line.max(cursor);
1103                line_start += line_cross + self.cross_axis_spacing;
1104                cursor = 0.0;
1105                line_cross = 0.0;
1106            }
1107
1108            let main = if cursor > 0.0 {
1109                cursor + self.main_axis_spacing
1110            } else {
1111                0.0
1112            };
1113            let (x, y) = self.along(main, line_start);
1114            if !self.layout_direction.is_rtl() {
1115                placeable.place(x, y);
1116            }
1117            placements.push(Placement::new(placeable.node_id(), x, y, 0));
1118
1119            cursor = main + child_main;
1120            line_cross = line_cross.max(child_cross);
1121        }
1122        longest_line = longest_line.max(cursor);
1123
1124        let (width, height) = self.along(longest_line, line_start + line_cross);
1125        let width = width.clamp(constraints.min_width, constraints.max_width);
1126        let height = height.clamp(constraints.min_height, constraints.max_height);
1127        if self.layout_direction.is_rtl() {
1128            for (placement, placeable) in placements.iter_mut().zip(&placeables) {
1129                placement.x = self
1130                    .layout_direction
1131                    .place_x(placement.x, width, placeable.width());
1132                placeable.place(placement.x, placement.y);
1133            }
1134        }
1135        crate::modifier::Size { width, height }.into()
1136    }
1137
1138    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
1139        match self.axis {
1140            FlowAxis::Horizontal => self.narrowest_main(measurables, height),
1141            FlowAxis::Vertical => self.wrapped_cross(measurables, height, true),
1142        }
1143    }
1144
1145    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
1146        match self.axis {
1147            FlowAxis::Horizontal => self.unwrapped_main(measurables, height),
1148            FlowAxis::Vertical => self.wrapped_cross(measurables, height, false),
1149        }
1150    }
1151
1152    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
1153        match self.axis {
1154            FlowAxis::Horizontal => self.wrapped_cross(measurables, width, true),
1155            FlowAxis::Vertical => self.narrowest_main(measurables, width),
1156        }
1157    }
1158
1159    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
1160        match self.axis {
1161            FlowAxis::Horizontal => self.wrapped_cross(measurables, width, false),
1162            FlowAxis::Vertical => self.unwrapped_main(measurables, width),
1163        }
1164    }
1165}
1166
1167/// MeasurePolicy for leaf nodes with fixed intrinsic size (like Spacer).
1168/// This policy respects the provided constraints but has a preferred intrinsic size.
1169#[derive(Clone, Debug, PartialEq)]
1170pub struct LeafMeasurePolicy {
1171    /// Preferred size in logical pixels. The measured size is this size limited by the incoming
1172    /// constraints.
1173    pub intrinsic_size: crate::modifier::Size,
1174}
1175
1176impl LeafMeasurePolicy {
1177    /// Creates a policy whose preferred size is `intrinsic_size`.
1178    pub fn new(intrinsic_size: crate::modifier::Size) -> Self {
1179        Self { intrinsic_size }
1180    }
1181}
1182
1183impl MeasurePolicy for LeafMeasurePolicy {
1184    fn measure(
1185        &self,
1186        scope: &dyn MeasureScope,
1187        _measurables: &[Box<dyn Measurable>],
1188        constraints: Constraints,
1189    ) -> MeasureResult {
1190        let mut placements = Vec::new();
1191        let measurement = self.measure_into(scope, &[], constraints, &mut placements);
1192        MeasureResult::new(measurement, placements)
1193    }
1194
1195    fn measure_into(
1196        &self,
1197        _scope: &dyn MeasureScope,
1198        _measurables: &[Box<dyn Measurable>],
1199        constraints: Constraints,
1200        placements: &mut Vec<Placement>,
1201    ) -> cranpose_ui_layout::Measurement {
1202        placements.clear();
1203        let (width, height) =
1204            constraints.constrain(self.intrinsic_size.width, self.intrinsic_size.height);
1205
1206        crate::modifier::Size { width, height }.into()
1207    }
1208
1209    fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
1210        self.intrinsic_size.width
1211    }
1212
1213    fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
1214        self.intrinsic_size.width
1215    }
1216
1217    fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
1218        self.intrinsic_size.height
1219    }
1220
1221    fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
1222        self.intrinsic_size.height
1223    }
1224}
1225
1226/// EmptyMeasurePolicy that delegates all measurement to modifier nodes.
1227///
1228/// This is used when a Layout has no child layout logic - all measurement
1229/// is handled by modifier nodes (e.g., TextModifierNode for Text widgets).
1230/// Matches Jetpack Compose's EmptyMeasurePolicy pattern used in BasicText.
1231#[derive(Clone, Debug, PartialEq)]
1232pub struct EmptyMeasurePolicy;
1233
1234impl EmptyMeasurePolicy {
1235    /// Creates the policy.
1236    pub fn new() -> Self {
1237        Self
1238    }
1239}
1240
1241impl Default for EmptyMeasurePolicy {
1242    fn default() -> Self {
1243        Self::new()
1244    }
1245}
1246
1247impl MeasurePolicy for EmptyMeasurePolicy {
1248    fn measure(
1249        &self,
1250        scope: &dyn MeasureScope,
1251        _measurables: &[Box<dyn Measurable>],
1252        constraints: Constraints,
1253    ) -> MeasureResult {
1254        let mut placements = Vec::new();
1255        let measurement = self.measure_into(scope, &[], constraints, &mut placements);
1256        MeasureResult::new(measurement, placements)
1257    }
1258
1259    fn measure_into(
1260        &self,
1261        _scope: &dyn MeasureScope,
1262        _measurables: &[Box<dyn Measurable>],
1263        constraints: Constraints,
1264        placements: &mut Vec<Placement>,
1265    ) -> cranpose_ui_layout::Measurement {
1266        placements.clear();
1267        let (width, height) = constraints.constrain(0.0, 0.0);
1268
1269        crate::modifier::Size { width, height }.into()
1270    }
1271
1272    fn measure_hold(
1273        &self,
1274        _measurables: &[Box<dyn Measurable>],
1275        constraints: Constraints,
1276        size: crate::modifier::Size,
1277    ) -> Option<cranpose_ui_layout::ConstraintsHold> {
1278        empty_layout_hold(constraints, size)
1279    }
1280
1281    fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
1282        0.0
1283    }
1284
1285    fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
1286        0.0
1287    }
1288
1289    fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
1290        0.0
1291    }
1292
1293    fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
1294        0.0
1295    }
1296}
1297
1298#[cfg(test)]
1299#[path = "tests/policies_tests.rs"]
1300mod tests;
1301
1302/// What a layout with nothing in it holds for: it takes its min
1303/// constraints, so any constraints with the same mins and a max that allows
1304/// them size it the same.
1305fn empty_layout_hold(
1306    constraints: Constraints,
1307    size: crate::modifier::Size,
1308) -> Option<cranpose_ui_layout::ConstraintsHold> {
1309    (size.width == constraints.min_width && size.height == constraints.min_height)
1310        .then(|| cranpose_ui_layout::ConstraintsHold::at_min(size.width, size.height))
1311}