Skip to main content

cranpose_ui/layout/
policies.rs

1use cranpose_ui_layout::{
2    Axis, Constraints, MeasurePolicy, MeasureResult, MeasureScope, ParentData, Placement,
3};
4use smallvec::SmallVec;
5
6use crate::layout::core::{
7    Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
8};
9
10/// MeasurePolicy for Box layout - overlays children according to alignment.
11#[derive(Clone, Debug, PartialEq)]
12pub struct BoxMeasurePolicy {
13    pub content_alignment: Alignment,
14    pub propagate_min_constraints: bool,
15}
16
17impl BoxMeasurePolicy {
18    pub fn new(content_alignment: Alignment, propagate_min_constraints: bool) -> Self {
19        Self {
20            content_alignment,
21            propagate_min_constraints,
22        }
23    }
24}
25
26impl MeasurePolicy for BoxMeasurePolicy {
27    fn measure(
28        &self,
29        scope: &dyn MeasureScope,
30        measurables: &[Box<dyn Measurable>],
31        constraints: Constraints,
32    ) -> MeasureResult {
33        let mut placements = Vec::new();
34        let size = self.measure_into(scope, measurables, constraints, &mut placements);
35        MeasureResult::new(size, placements)
36    }
37
38    fn measure_into(
39        &self,
40        _scope: &dyn MeasureScope,
41        measurables: &[Box<dyn Measurable>],
42        constraints: Constraints,
43        placements: &mut Vec<Placement>,
44    ) -> crate::modifier::Size {
45        placements.clear();
46        let child_constraints = if self.propagate_min_constraints {
47            constraints
48        } else {
49            Constraints {
50                min_width: 0.0,
51                max_width: constraints.max_width,
52                min_height: 0.0,
53                max_height: constraints.max_height,
54            }
55        };
56
57        let mut max_width = 0.0_f32;
58        let mut max_height = 0.0_f32;
59        let mut placeables: SmallVec<[(cranpose_ui_layout::Placeable, Alignment); 8]> =
60            SmallVec::new();
61
62        for measurable in measurables {
63            let placeable = measurable.measure(child_constraints);
64            max_width = max_width.max(placeable.width());
65            max_height = max_height.max(placeable.height());
66            let alignment = measurable
67                .parent_data()
68                .box_alignment
69                .unwrap_or(self.content_alignment);
70            placeables.push((placeable, alignment));
71        }
72
73        let width = max_width.clamp(constraints.min_width, constraints.max_width);
74        let height = max_height.clamp(constraints.min_height, constraints.max_height);
75
76        placements.reserve(placeables.len());
77        for (placeable, alignment) in placeables {
78            let child_width = placeable.width();
79            let child_height = placeable.height();
80
81            let x = match alignment.horizontal {
82                HorizontalAlignment::Start => 0.0,
83                HorizontalAlignment::CenterHorizontally => ((width - child_width) / 2.0).max(0.0),
84                HorizontalAlignment::End => (width - child_width).max(0.0),
85            };
86
87            let y = match alignment.vertical {
88                VerticalAlignment::Top => 0.0,
89                VerticalAlignment::CenterVertically => ((height - child_height) / 2.0).max(0.0),
90                VerticalAlignment::Bottom => (height - child_height).max(0.0),
91            };
92
93            placeable.place(x, y);
94            placements.push(Placement::new(placeable.node_id(), x, y, 0));
95        }
96
97        crate::modifier::Size { width, height }
98    }
99
100    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
101        measurables
102            .iter()
103            .map(|m| m.min_intrinsic_width(height))
104            .fold(0.0, f32::max)
105    }
106
107    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
108        measurables
109            .iter()
110            .map(|m| m.max_intrinsic_width(height))
111            .fold(0.0, f32::max)
112    }
113
114    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
115        measurables
116            .iter()
117            .map(|m| m.min_intrinsic_height(width))
118            .fold(0.0, f32::max)
119    }
120
121    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
122        measurables
123            .iter()
124            .map(|m| m.max_intrinsic_height(width))
125            .fold(0.0, f32::max)
126    }
127}
128
129// Row and Column use FlexMeasurePolicy with axis-specific configuration.
130
131/// Unified Flex layout policy that powers both Row and Column.
132///
133/// This policy implements Jetpack Compose's flex layout semantics:
134/// - Measures children with proper loose constraints (min = 0 on both axes)
135/// - Supports weighted distribution of remaining space
136/// - Handles bounded/unbounded main axis correctly
137/// - Implements correct intrinsics for both axes
138///
139/// ## Overflow Behavior
140///
141/// Like Jetpack Compose, this policy **allows children to overflow** their container bounds:
142/// - Children can be positioned outside the parent's measured size
143/// - Overflowing content is rendered (unless clipped by a modifier)
144/// - When content overflows, distribution arrangements switch to `Start` to avoid negative spacing
145/// - `SpacedBy` keeps its fixed inter-child spacing even when content overflows
146///
147/// Example: A Row with 300px of content in a 200px container will:
148/// 1. Measure children at their natural sizes
149/// 2. Detect overflow (300px > 200px)
150/// 3. Switch to Start arrangement (pack children at the start)
151/// 4. Position last children beyond the 200px boundary
152///
153/// To prevent overflow:
154/// - Use weights for flexible sizing: `.weight(1.0, true)`
155/// - Use `fillMaxWidth()`/`fillMaxHeight()` modifiers
156/// - Design UI to fit within available space
157/// - Add a clip modifier to hide overflowing content
158///
159/// ## Weighted Children
160///
161/// When the main axis is bounded and children have weights:
162/// 1. Fixed children (no weight) are measured first
163/// 2. Remaining space is distributed proportionally to weights
164/// 3. Each weighted child gets: `remaining * (weight / total_weight)`
165/// 4. If `fill=true`, child gets tight constraints; if `fill=false`, loose constraints
166///
167/// When the main axis is unbounded, weights are ignored (all children wrap content).
168#[derive(Clone, Debug, PartialEq)]
169pub struct FlexMeasurePolicy {
170    /// Main axis direction (Horizontal for Row, Vertical for Column)
171    pub axis: Axis,
172    /// Arrangement along the main axis
173    pub main_axis_arrangement: LinearArrangement,
174    /// Alignment along the cross axis (used as default for children without explicit alignment)
175    pub cross_axis_alignment: CrossAxisAlignment,
176}
177
178/// Cross-axis alignment for flex layouts.
179/// This is axis-agnostic and gets interpreted based on the flex axis.
180#[derive(Clone, Copy, Debug, PartialEq)]
181pub enum CrossAxisAlignment {
182    /// Align to the start of the cross axis (Top for Row, Start for Column)
183    Start,
184    /// Align to the center of the cross axis
185    Center,
186    /// Align to the end of the cross axis (Bottom for Row, End for Column)
187    End,
188}
189
190impl CrossAxisAlignment {
191    /// Calculate the offset for positioning a child on the cross axis.
192    fn align(&self, available: f32, child: f32) -> f32 {
193        match self {
194            CrossAxisAlignment::Start => 0.0,
195            CrossAxisAlignment::Center => ((available - child) / 2.0).max(0.0),
196            CrossAxisAlignment::End => (available - child).max(0.0),
197        }
198    }
199}
200
201impl From<HorizontalAlignment> for CrossAxisAlignment {
202    fn from(alignment: HorizontalAlignment) -> Self {
203        match alignment {
204            HorizontalAlignment::Start => CrossAxisAlignment::Start,
205            HorizontalAlignment::CenterHorizontally => CrossAxisAlignment::Center,
206            HorizontalAlignment::End => CrossAxisAlignment::End,
207        }
208    }
209}
210
211impl From<VerticalAlignment> for CrossAxisAlignment {
212    fn from(alignment: VerticalAlignment) -> Self {
213        match alignment {
214            VerticalAlignment::Top => CrossAxisAlignment::Start,
215            VerticalAlignment::CenterVertically => CrossAxisAlignment::Center,
216            VerticalAlignment::Bottom => CrossAxisAlignment::End,
217        }
218    }
219}
220
221impl FlexMeasurePolicy {
222    pub fn new(
223        axis: Axis,
224        main_axis_arrangement: LinearArrangement,
225        cross_axis_alignment: CrossAxisAlignment,
226    ) -> Self {
227        Self {
228            axis,
229            main_axis_arrangement,
230            cross_axis_alignment,
231        }
232    }
233
234    /// Creates a FlexMeasurePolicy for Row (horizontal main axis).
235    pub fn row(
236        horizontal_arrangement: LinearArrangement,
237        vertical_alignment: VerticalAlignment,
238    ) -> Self {
239        Self::new(
240            Axis::Horizontal,
241            horizontal_arrangement,
242            vertical_alignment.into(),
243        )
244    }
245
246    /// Creates a FlexMeasurePolicy for Column (vertical main axis).
247    pub fn column(
248        vertical_arrangement: LinearArrangement,
249        horizontal_alignment: HorizontalAlignment,
250    ) -> Self {
251        Self::new(
252            Axis::Vertical,
253            vertical_arrangement,
254            horizontal_alignment.into(),
255        )
256    }
257
258    /// Extract main and cross axis values from constraints.
259    fn get_axis_constraints(&self, constraints: Constraints) -> (f32, f32, f32, f32) {
260        match self.axis {
261            Axis::Horizontal => (
262                constraints.min_width,
263                constraints.max_width,
264                constraints.min_height,
265                constraints.max_height,
266            ),
267            Axis::Vertical => (
268                constraints.min_height,
269                constraints.max_height,
270                constraints.min_width,
271                constraints.max_width,
272            ),
273        }
274    }
275
276    /// Create constraints from main and cross axis values.
277    fn make_constraints(
278        &self,
279        min_main: f32,
280        max_main: f32,
281        min_cross: f32,
282        max_cross: f32,
283    ) -> Constraints {
284        match self.axis {
285            Axis::Horizontal => Constraints {
286                min_width: min_main,
287                max_width: max_main,
288                min_height: min_cross,
289                max_height: max_cross,
290            },
291            Axis::Vertical => Constraints {
292                min_width: min_cross,
293                max_width: max_cross,
294                min_height: min_main,
295                max_height: max_main,
296            },
297        }
298    }
299
300    /// Get the main axis size from width/height.
301    fn get_main_axis_size(&self, width: f32, height: f32) -> f32 {
302        match self.axis {
303            Axis::Horizontal => width,
304            Axis::Vertical => height,
305        }
306    }
307
308    /// Get the cross axis size from width/height.
309    fn get_cross_axis_size(&self, width: f32, height: f32) -> f32 {
310        match self.axis {
311            Axis::Horizontal => height,
312            Axis::Vertical => width,
313        }
314    }
315
316    /// Calculate spacing between children based on arrangement.
317    fn get_spacing(&self) -> f32 {
318        match self.main_axis_arrangement {
319            LinearArrangement::SpacedBy(value) => value.max(0.0),
320            _ => 0.0,
321        }
322    }
323}
324
325impl MeasurePolicy for FlexMeasurePolicy {
326    fn measure(
327        &self,
328        scope: &dyn MeasureScope,
329        measurables: &[Box<dyn Measurable>],
330        constraints: Constraints,
331    ) -> MeasureResult {
332        let mut placements = Vec::new();
333        let size = self.measure_into(scope, measurables, constraints, &mut placements);
334        MeasureResult::new(size, placements)
335    }
336
337    fn measure_into(
338        &self,
339        _scope: &dyn MeasureScope,
340        measurables: &[Box<dyn Measurable>],
341        constraints: Constraints,
342        placements: &mut Vec<Placement>,
343    ) -> crate::modifier::Size {
344        placements.clear();
345        if measurables.is_empty() {
346            let (width, height) = constraints.constrain(0.0, 0.0);
347            return crate::modifier::Size { width, height };
348        }
349
350        let (min_main, max_main, min_cross, max_cross) = self.get_axis_constraints(constraints);
351        let main_axis_bounded = max_main.is_finite();
352        let spacing = self.get_spacing();
353
354        // Separate children into fixed and weighted
355        let mut fixed_children: SmallVec<[usize; 8]> = SmallVec::new();
356        let parent_data: SmallVec<[ParentData; 8]> = measurables
357            .iter()
358            .map(|child| child.parent_data())
359            .collect();
360        let mut weighted_children: SmallVec<[(usize, ParentData); 8]> = SmallVec::new();
361
362        for (idx, data) in parent_data.iter().copied().enumerate() {
363            if data.has_weight() {
364                weighted_children.push((idx, data));
365            } else {
366                fixed_children.push(idx);
367            }
368        }
369
370        // Measure fixed children first
371        // Children get loose constraints on both axes (min = 0)
372        let child_constraints = self.make_constraints(0.0, max_main, 0.0, max_cross);
373
374        let mut placeables: SmallVec<[Option<cranpose_ui_layout::Placeable>; 8]> = SmallVec::new();
375        placeables.resize_with(measurables.len(), || None);
376        let mut fixed_main_size = 0.0_f32;
377        let mut max_cross_size = 0.0_f32;
378
379        for &idx in &fixed_children {
380            let measurable = &measurables[idx];
381            let placeable = measurable.measure(child_constraints);
382            let main_size = self.get_main_axis_size(placeable.width(), placeable.height());
383            let cross_size = self.get_cross_axis_size(placeable.width(), placeable.height());
384
385            fixed_main_size += main_size;
386            max_cross_size = max_cross_size.max(cross_size);
387            placeables[idx] = Some(placeable);
388        }
389
390        // Calculate spacing
391        let num_children = measurables.len();
392        let total_spacing = if num_children > 1 {
393            spacing * (num_children - 1) as f32
394        } else {
395            0.0
396        };
397
398        // Measure weighted children
399        if !weighted_children.is_empty() {
400            if main_axis_bounded {
401                // Calculate remaining space for weighted children
402                let used_main = fixed_main_size + total_spacing;
403                let remaining_main = (max_main - used_main).max(0.0);
404
405                // Calculate total weight
406                let total_weight: f32 = weighted_children.iter().map(|(_, data)| data.weight).sum();
407
408                // Measure each weighted child with its allocated space
409                for &(idx, parent_data) in &weighted_children {
410                    let measurable = &measurables[idx];
411                    let allocated = if total_weight > 0.0 {
412                        remaining_main * (parent_data.weight / total_weight)
413                    } else {
414                        0.0
415                    };
416
417                    let weighted_constraints = if parent_data.fill {
418                        // fill=true: child gets tight constraints on main axis
419                        self.make_constraints(allocated, allocated, 0.0, max_cross)
420                    } else {
421                        // fill=false: child gets loose constraints on main axis
422                        self.make_constraints(0.0, allocated, 0.0, max_cross)
423                    };
424
425                    let placeable = measurable.measure(weighted_constraints);
426                    let cross_size =
427                        self.get_cross_axis_size(placeable.width(), placeable.height());
428                    max_cross_size = max_cross_size.max(cross_size);
429                    placeables[idx] = Some(placeable);
430                }
431            } else {
432                // Main axis unbounded: ignore weights, measure like fixed children
433                for &(idx, _) in &weighted_children {
434                    let measurable = &measurables[idx];
435                    let placeable = measurable.measure(child_constraints);
436                    let cross_size =
437                        self.get_cross_axis_size(placeable.width(), placeable.height());
438                    max_cross_size = max_cross_size.max(cross_size);
439                    placeables[idx] = Some(placeable);
440                }
441            }
442        }
443
444        let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = placeables
445            .into_iter()
446            .enumerate()
447            .map(|(idx, placeable)| {
448                placeable.unwrap_or_else(|| measurables[idx].measure(child_constraints))
449            })
450            .collect();
451
452        // Calculate total main size
453        let total_main: f32 = placeables
454            .iter()
455            .map(|p| self.get_main_axis_size(p.width(), p.height()))
456            .sum::<f32>()
457            + total_spacing;
458
459        // Container size
460        let container_main = total_main.clamp(min_main, max_main);
461        let container_cross = max_cross_size.clamp(min_cross, max_cross);
462
463        // Arrange children along main axis
464        let child_main_sizes: SmallVec<[f32; 8]> = placeables
465            .iter()
466            .map(|p| self.get_main_axis_size(p.width(), p.height()))
467            .collect();
468
469        let mut main_positions: SmallVec<[f32; 8]> =
470            SmallVec::with_capacity(child_main_sizes.len());
471        main_positions.resize(child_main_sizes.len(), 0.0);
472
473        // If distribution arrangements overflow, use Start arrangement to avoid negative spacing.
474        // Fixed SpacedBy gaps stay valid under overflow and must remain part of layout.
475        let arrangement = if total_main > container_main
476            && !matches!(self.main_axis_arrangement, LinearArrangement::SpacedBy(_))
477        {
478            LinearArrangement::Start
479        } else {
480            self.main_axis_arrangement
481        };
482        arrangement.arrange(container_main, &child_main_sizes, &mut main_positions);
483
484        // Place children
485        placements.reserve(placeables.len());
486        for (idx, (placeable, main_pos)) in placeables.into_iter().zip(main_positions).enumerate() {
487            let child_cross = self.get_cross_axis_size(placeable.width(), placeable.height());
488            let cross_axis_alignment = match self.axis {
489                Axis::Horizontal => parent_data[idx]
490                    .row_alignment
491                    .map(Into::into)
492                    .unwrap_or(self.cross_axis_alignment),
493                Axis::Vertical => parent_data[idx]
494                    .column_alignment
495                    .map(Into::into)
496                    .unwrap_or(self.cross_axis_alignment),
497            };
498            let cross_pos = cross_axis_alignment.align(container_cross, child_cross);
499
500            let (x, y) = match self.axis {
501                Axis::Horizontal => (main_pos, cross_pos),
502                Axis::Vertical => (cross_pos, main_pos),
503            };
504
505            placeable.place(x, y);
506            placements.push(Placement::new(placeable.node_id(), x, y, 0));
507        }
508
509        // Create final size
510        let (width, height) = match self.axis {
511            Axis::Horizontal => (container_main, container_cross),
512            Axis::Vertical => (container_cross, container_main),
513        };
514
515        crate::modifier::Size { width, height }
516    }
517
518    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
519        let spacing = self.get_spacing();
520        let total_spacing = if measurables.len() > 1 {
521            spacing * (measurables.len() - 1) as f32
522        } else {
523            0.0
524        };
525
526        match self.axis {
527            Axis::Horizontal => {
528                // Row: sum of children's min intrinsic widths + spacing
529                measurables
530                    .iter()
531                    .map(|m| m.min_intrinsic_width(height))
532                    .sum::<f32>()
533                    + total_spacing
534            }
535            Axis::Vertical => {
536                // Column: max of children's min intrinsic widths
537                measurables
538                    .iter()
539                    .map(|m| m.min_intrinsic_width(height))
540                    .fold(0.0, f32::max)
541            }
542        }
543    }
544
545    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
546        let spacing = self.get_spacing();
547        let total_spacing = if measurables.len() > 1 {
548            spacing * (measurables.len() - 1) as f32
549        } else {
550            0.0
551        };
552
553        match self.axis {
554            Axis::Horizontal => {
555                // Row: sum of children's max intrinsic widths + spacing
556                measurables
557                    .iter()
558                    .map(|m| m.max_intrinsic_width(height))
559                    .sum::<f32>()
560                    + total_spacing
561            }
562            Axis::Vertical => {
563                // Column: max of children's max intrinsic widths
564                measurables
565                    .iter()
566                    .map(|m| m.max_intrinsic_width(height))
567                    .fold(0.0, f32::max)
568            }
569        }
570    }
571
572    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
573        let spacing = self.get_spacing();
574        let total_spacing = if measurables.len() > 1 {
575            spacing * (measurables.len() - 1) as f32
576        } else {
577            0.0
578        };
579
580        match self.axis {
581            Axis::Horizontal => {
582                // Row: max of children's min intrinsic heights
583                measurables
584                    .iter()
585                    .map(|m| m.min_intrinsic_height(width))
586                    .fold(0.0, f32::max)
587            }
588            Axis::Vertical => {
589                // Column: sum of children's min intrinsic heights + spacing
590                measurables
591                    .iter()
592                    .map(|m| m.min_intrinsic_height(width))
593                    .sum::<f32>()
594                    + total_spacing
595            }
596        }
597    }
598
599    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
600        let spacing = self.get_spacing();
601        let total_spacing = if measurables.len() > 1 {
602            spacing * (measurables.len() - 1) as f32
603        } else {
604            0.0
605        };
606
607        match self.axis {
608            Axis::Horizontal => {
609                // Row: max of children's max intrinsic heights
610                measurables
611                    .iter()
612                    .map(|m| m.max_intrinsic_height(width))
613                    .fold(0.0, f32::max)
614            }
615            Axis::Vertical => {
616                // Column: sum of children's max intrinsic heights + spacing
617                measurables
618                    .iter()
619                    .map(|m| m.max_intrinsic_height(width))
620                    .sum::<f32>()
621                    + total_spacing
622            }
623        }
624    }
625}
626
627/// MeasurePolicy for FlowRow: children flow horizontally and wrap onto the
628/// next line when the available width runs out (Jetpack Compose `FlowRow`).
629///
630/// - Children are measured with loose constraints (min = 0) capped at the
631///   incoming max width/height, then packed left-to-right.
632/// - A child that no longer fits on the current line starts a new line; a
633///   child wider than the whole line gets a line of its own (and may
634///   overflow, like Compose).
635/// - `main_axis_spacing` separates children on the same line and
636///   `cross_axis_spacing` separates lines; children are top-aligned within
637///   their line.
638/// - With an unbounded max width everything stays on one line.
639#[derive(Clone, Debug, PartialEq)]
640pub struct FlowRowMeasurePolicy {
641    /// Horizontal gap between adjacent children on the same line, in dp.
642    pub main_axis_spacing: f32,
643    /// Vertical gap between consecutive lines, in dp.
644    pub cross_axis_spacing: f32,
645}
646
647impl FlowRowMeasurePolicy {
648    pub fn new(main_axis_spacing: f32, cross_axis_spacing: f32) -> Self {
649        Self {
650            main_axis_spacing: main_axis_spacing.max(0.0),
651            cross_axis_spacing: cross_axis_spacing.max(0.0),
652        }
653    }
654
655    /// Simulates the wrapping flow for intrinsic height queries.
656    fn wrapped_intrinsic_height(
657        &self,
658        measurables: &[Box<dyn Measurable>],
659        available_width: f32,
660        use_min_height: bool,
661    ) -> f32 {
662        let mut cursor_x = 0.0_f32;
663        let mut line_top = 0.0_f32;
664        let mut line_height = 0.0_f32;
665
666        for measurable in measurables {
667            let child_width = measurable.max_intrinsic_width(f32::INFINITY);
668            let child_height = if use_min_height {
669                measurable.min_intrinsic_height(child_width)
670            } else {
671                measurable.max_intrinsic_height(child_width)
672            };
673
674            if cursor_x > 0.0 && cursor_x + self.main_axis_spacing + child_width > available_width {
675                line_top += line_height + self.cross_axis_spacing;
676                cursor_x = 0.0;
677                line_height = 0.0;
678            }
679            cursor_x += if cursor_x > 0.0 {
680                self.main_axis_spacing + child_width
681            } else {
682                child_width
683            };
684            line_height = line_height.max(child_height);
685        }
686
687        line_top + line_height
688    }
689}
690
691impl MeasurePolicy for FlowRowMeasurePolicy {
692    fn measure(
693        &self,
694        scope: &dyn MeasureScope,
695        measurables: &[Box<dyn Measurable>],
696        constraints: Constraints,
697    ) -> MeasureResult {
698        let mut placements = Vec::new();
699        let size = self.measure_into(scope, measurables, constraints, &mut placements);
700        MeasureResult::new(size, placements)
701    }
702
703    fn measure_into(
704        &self,
705        _scope: &dyn MeasureScope,
706        measurables: &[Box<dyn Measurable>],
707        constraints: Constraints,
708        placements: &mut Vec<Placement>,
709    ) -> crate::modifier::Size {
710        placements.clear();
711        if measurables.is_empty() {
712            let (width, height) = constraints.constrain(0.0, 0.0);
713            return crate::modifier::Size { width, height };
714        }
715
716        // Children get loose constraints capped at the incoming maximums;
717        // wrapping happens on their measured sizes.
718        let child_constraints = Constraints {
719            min_width: 0.0,
720            max_width: constraints.max_width,
721            min_height: 0.0,
722            max_height: constraints.max_height,
723        };
724
725        let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = measurables
726            .iter()
727            .map(|measurable| measurable.measure(child_constraints))
728            .collect();
729
730        let mut cursor_x = 0.0_f32; // end of the current line's content
731        let mut line_top = 0.0_f32; // y of the current line
732        let mut line_height = 0.0_f32;
733        let mut max_line_width = 0.0_f32;
734
735        placements.reserve(placeables.len());
736        for placeable in placeables {
737            let child_width = placeable.width();
738            let child_height = placeable.height();
739
740            // Wrap when the child (plus the gap separating it from the
741            // previous child) no longer fits. The first child of a line
742            // never wraps, so an oversized child overflows instead of
743            // looping.
744            if cursor_x > 0.0
745                && cursor_x + self.main_axis_spacing + child_width > constraints.max_width
746            {
747                max_line_width = max_line_width.max(cursor_x);
748                line_top += line_height + self.cross_axis_spacing;
749                cursor_x = 0.0;
750                line_height = 0.0;
751            }
752
753            let x = if cursor_x > 0.0 {
754                cursor_x + self.main_axis_spacing
755            } else {
756                0.0
757            };
758            placeable.place(x, line_top);
759            placements.push(Placement::new(placeable.node_id(), x, line_top, 0));
760
761            cursor_x = x + child_width;
762            line_height = line_height.max(child_height);
763        }
764        max_line_width = max_line_width.max(cursor_x);
765
766        let width = max_line_width.clamp(constraints.min_width, constraints.max_width);
767        let height = (line_top + line_height).clamp(constraints.min_height, constraints.max_height);
768        crate::modifier::Size { width, height }
769    }
770
771    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
772        // Narrowest sensible layout: one child per line.
773        measurables
774            .iter()
775            .map(|m| m.min_intrinsic_width(height))
776            .fold(0.0, f32::max)
777    }
778
779    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
780        // Widest layout: everything on a single line.
781        let total_spacing = if measurables.len() > 1 {
782            self.main_axis_spacing * (measurables.len() - 1) as f32
783        } else {
784            0.0
785        };
786        measurables
787            .iter()
788            .map(|m| m.max_intrinsic_width(height))
789            .sum::<f32>()
790            + total_spacing
791    }
792
793    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
794        self.wrapped_intrinsic_height(measurables, width, true)
795    }
796
797    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
798        self.wrapped_intrinsic_height(measurables, width, false)
799    }
800}
801
802/// MeasurePolicy for leaf nodes with fixed intrinsic size (like Spacer).
803/// This policy respects the provided constraints but has a preferred intrinsic size.
804#[derive(Clone, Debug, PartialEq)]
805pub struct LeafMeasurePolicy {
806    pub intrinsic_size: crate::modifier::Size,
807}
808
809impl LeafMeasurePolicy {
810    pub fn new(intrinsic_size: crate::modifier::Size) -> Self {
811        Self { intrinsic_size }
812    }
813}
814
815impl MeasurePolicy for LeafMeasurePolicy {
816    fn measure(
817        &self,
818        scope: &dyn MeasureScope,
819        _measurables: &[Box<dyn Measurable>],
820        constraints: Constraints,
821    ) -> MeasureResult {
822        let mut placements = Vec::new();
823        let size = self.measure_into(scope, &[], constraints, &mut placements);
824        MeasureResult::new(size, placements)
825    }
826
827    fn measure_into(
828        &self,
829        _scope: &dyn MeasureScope,
830        _measurables: &[Box<dyn Measurable>],
831        constraints: Constraints,
832        placements: &mut Vec<Placement>,
833    ) -> crate::modifier::Size {
834        placements.clear();
835        // Use intrinsic size but constrain to provided constraints
836        let (width, height) =
837            constraints.constrain(self.intrinsic_size.width, self.intrinsic_size.height);
838
839        crate::modifier::Size { width, height }
840    }
841
842    fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
843        self.intrinsic_size.width
844    }
845
846    fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
847        self.intrinsic_size.width
848    }
849
850    fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
851        self.intrinsic_size.height
852    }
853
854    fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
855        self.intrinsic_size.height
856    }
857}
858
859/// EmptyMeasurePolicy that delegates all measurement to modifier nodes.
860///
861/// This is used when a Layout has no child layout logic - all measurement
862/// is handled by modifier nodes (e.g., TextModifierNode for Text widgets).
863/// Matches Jetpack Compose's EmptyMeasurePolicy pattern used in BasicText.
864#[derive(Clone, Debug, PartialEq)]
865pub struct EmptyMeasurePolicy;
866
867impl EmptyMeasurePolicy {
868    pub fn new() -> Self {
869        Self
870    }
871}
872
873impl Default for EmptyMeasurePolicy {
874    fn default() -> Self {
875        Self::new()
876    }
877}
878
879impl MeasurePolicy for EmptyMeasurePolicy {
880    fn measure(
881        &self,
882        scope: &dyn MeasureScope,
883        _measurables: &[Box<dyn Measurable>],
884        constraints: Constraints,
885    ) -> MeasureResult {
886        let mut placements = Vec::new();
887        let size = self.measure_into(scope, &[], constraints, &mut placements);
888        MeasureResult::new(size, placements)
889    }
890
891    fn measure_into(
892        &self,
893        _scope: &dyn MeasureScope,
894        _measurables: &[Box<dyn Measurable>],
895        constraints: Constraints,
896        placements: &mut Vec<Placement>,
897    ) -> crate::modifier::Size {
898        placements.clear();
899        // Empty policy returns the maximum available space
900        // The actual measurement is handled by modifier nodes in the chain
901        let (width, height) = constraints.constrain(0.0, 0.0);
902
903        crate::modifier::Size { width, height }
904    }
905
906    fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
907        0.0
908    }
909
910    fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
911        0.0
912    }
913
914    fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
915        0.0
916    }
917
918    fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
919        0.0
920    }
921}
922
923#[cfg(test)]
924#[path = "tests/policies_tests.rs"]
925mod tests;