Skip to main content

cranpose_ui/layout/
policies.rs

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