Skip to main content

cranpose_ui/layout/
policies.rs

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