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