Skip to main content

cranpose_ui/layout/
policies.rs

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