Skip to main content

cranpose_ui/layout/
policies.rs

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