Skip to main content

cranpose_ui/layout/
policies.rs

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