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