tessera_ui/component_tree/
constraint.rs

1//! # Layout Constraint System
2//!
3//! This module provides the core constraint system for Tessera's layout engine.
4//! It defines how components specify their sizing requirements and how these
5//! constraints are resolved in a component hierarchy.
6//!
7//! ## Overview
8//!
9//! The constraint system is built around two main concepts:
10//!
11//! - **[`DimensionValue`]**: Specifies how a single dimension (width or height) should be calculated
12//! - **[`Constraint`]**: Combines width and height dimension values for complete layout specification
13//!
14//! ## Dimension Types
15//!
16//! There are three fundamental ways a component can specify its size:
17//!
18//! ### Fixed
19//! The component has a specific, unchanging size:
20//! ```
21//! # use tessera_ui::Px;
22//! # use tessera_ui::DimensionValue;
23//! let fixed_width = DimensionValue::Fixed(Px(100));
24//! ```
25//!
26//! ### Wrap
27//! The component sizes itself to fit its content, with optional bounds:
28//! ```
29//! # use tessera_ui::Px;
30//! # use tessera_ui::DimensionValue;
31//! // Wrap content with no limits
32//! let wrap_content = DimensionValue::Wrap { min: None, max: None };
33//!
34//! // Wrap content but ensure at least 50px wide
35//! let wrap_with_min = DimensionValue::Wrap { min: Some(Px(50)), max: None };
36//!
37//! // Wrap content but never exceed 200px
38//! let wrap_with_max = DimensionValue::Wrap { min: None, max: Some(Px(200)) };
39//!
40//! // Wrap content within bounds
41//! let wrap_bounded = DimensionValue::Wrap {
42//!     min: Some(Px(50)),
43//!     max: Some(Px(200))
44//! };
45//! ```
46//!
47//! ### Fill
48//! The component expands to fill available space, with optional bounds:
49//! ```
50//! # use tessera_ui::Px;
51//! # use tessera_ui::DimensionValue;
52//! // Fill all available space
53//! let fill_all = DimensionValue::Fill { min: None, max: None };
54//!
55//! // Fill space but ensure at least 100px
56//! let fill_with_min = DimensionValue::Fill { min: Some(Px(100)), max: None };
57//!
58//! // Fill space but never exceed 300px
59//! let fill_with_max = DimensionValue::Fill { min: None, max: Some(Px(300)) };
60//! ```
61//!
62//! ## Constraint Merging
63//!
64//! When components are nested, their constraints must be merged to resolve conflicts
65//! and ensure consistent layout. The [`Constraint::merge`] method implements this
66//! logic with the following rules:
67//!
68//! - **Fixed always wins**: A fixed constraint cannot be overridden by its parent
69//! - **Wrap preserves content sizing**: Wrap constraints maintain their intrinsic sizing behavior
70//! - **Fill adapts to available space**: Fill constraints expand within parent bounds
71//!
72//! ### Merge Examples
73//!
74//! ```
75//! # use tessera_ui::Px;
76//! # use tessera_ui::{Constraint, DimensionValue};
77//! // Parent provides 200px of space
78//! let parent = Constraint::new(
79//!     DimensionValue::Fixed(Px(200)),
80//!     DimensionValue::Fixed(Px(200))
81//! );
82//!
83//! // Child wants to fill with minimum 50px
84//! let child = Constraint::new(
85//!     DimensionValue::Fill { min: Some(Px(50)), max: None },
86//!     DimensionValue::Fill { min: Some(Px(50)), max: None }
87//! );
88//!
89//! // Result: Child fills parent's 200px space, respecting its 50px minimum
90//! let merged = child.merge(&parent);
91//! assert_eq!(merged.width, DimensionValue::Fill {
92//!     min: Some(Px(50)),
93//!     max: Some(Px(200))
94//! });
95//! ```
96//!
97//! ## Usage in Components
98//!
99//! Components typically specify their constraints during the measurement phase:
100//!
101//! ```rust,ignore
102//! #[tessera]
103//! fn my_component() {
104//!     measure(|constraints| {
105//!         // This component wants to be exactly 100x50 pixels
106//!         let my_constraint = Constraint::new(
107//!             DimensionValue::Fixed(Px(100)),
108//!             DimensionValue::Fixed(Px(50))
109//!         );
110//!         
111//!         // Measure children with merged constraints
112//!         let child_constraint = my_constraint.merge(&constraints);
113//!         // ... measure children ...
114//!         
115//!         ComputedData::new(Size::new(Px(100), Px(50)))
116//!     });
117//! }
118//! ```
119
120use crate::Px;
121
122/// Defines how a dimension (width or height) should be calculated.
123///
124/// This enum represents the three fundamental sizing strategies available
125/// in Tessera's layout system. Each variant provides different behavior
126/// for how a component determines its size in a given dimension.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128pub enum DimensionValue {
129    /// The dimension is a fixed value in logical pixels.
130    ///
131    /// This variant represents a component that has a specific, unchanging size.
132    /// Fixed dimensions cannot be overridden by parent constraints and will
133    /// always maintain their specified size regardless of available space.
134    ///
135    /// # Example
136    /// ```
137    /// # use tessera_ui::Px;
138    /// # use tessera_ui::DimensionValue;
139    /// let button_width = DimensionValue::Fixed(Px(120));
140    /// ```
141    Fixed(Px),
142
143    /// The dimension should wrap its content, optionally bounded by min and/or max logical pixels.
144    ///
145    /// This variant represents a component that sizes itself based on its content.
146    /// The component will be as small as possible while still containing all its content,
147    /// but can be constrained by optional minimum and maximum bounds.
148    ///
149    /// # Parameters
150    /// - `min`: Optional minimum size - the component will never be smaller than this
151    /// - `max`: Optional maximum size - the component will never be larger than this
152    ///
153    /// # Examples
154    /// ```
155    /// # use tessera_ui::Px;
156    /// # use tessera_ui::DimensionValue;
157    /// // Text that wraps to its content size
158    /// let text_width = DimensionValue::Wrap { min: None, max: None };
159    ///
160    /// // Text with minimum width to prevent being too narrow
161    /// let min_text_width = DimensionValue::Wrap { min: Some(Px(100)), max: None };
162    ///
163    /// // Text that wraps but never exceeds container width
164    /// let bounded_text = DimensionValue::Wrap { min: Some(Px(50)), max: Some(Px(300)) };
165    /// ```
166    Wrap { min: Option<Px>, max: Option<Px> },
167
168    /// The dimension should fill the available space, optionally bounded by min and/or max logical pixels.
169    ///
170    /// This variant represents a component that expands to use all available space
171    /// provided by its parent. The expansion can be constrained by optional minimum
172    /// and maximum bounds.
173    ///
174    /// # Parameters
175    /// - `min`: Optional minimum size - the component will never be smaller than this
176    /// - `max`: Optional maximum size - the component will never be larger than this
177    ///
178    /// # Examples
179    /// ```
180    /// # use tessera_ui::Px;
181    /// # use tessera_ui::DimensionValue;
182    /// // Fill all available space
183    /// let flexible_width = DimensionValue::Fill { min: None, max: None };
184    ///
185    /// // Fill space but ensure minimum usability
186    /// let min_fill_width = DimensionValue::Fill { min: Some(Px(200)), max: None };
187    ///
188    /// // Fill space but cap maximum size for readability
189    /// let capped_fill = DimensionValue::Fill { min: Some(Px(100)), max: Some(Px(800)) };
190    /// ```
191    Fill { min: Option<Px>, max: Option<Px> },
192}
193
194impl Default for DimensionValue {
195    /// Returns the default dimension value: `Wrap { min: None, max: None }`.
196    ///
197    /// This default represents a component that sizes itself to its content
198    /// without any constraints, which is the most flexible and commonly used
199    /// sizing behavior.
200    fn default() -> Self {
201        DimensionValue::Wrap {
202            min: None,
203            max: None,
204        }
205    }
206}
207
208impl DimensionValue {
209    /// Converts this dimension value to a maximum pixel value.
210    ///
211    /// This method is useful during layout calculation when you need to determine
212    /// the maximum space a component might occupy.
213    ///
214    /// # Parameters
215    /// - `default`: The value to use when no maximum is specified
216    ///
217    /// # Returns
218    /// - For `Fixed`: Returns the fixed value
219    /// - For `Wrap` and `Fill`: Returns the `max` value if specified, otherwise the `default`
220    ///
221    /// # Example
222    /// ```
223    /// # use tessera_ui::Px;
224    /// # use tessera_ui::DimensionValue;
225    /// let fixed = DimensionValue::Fixed(Px(100));
226    /// assert_eq!(fixed.to_max_px(Px(200)), Px(100));
227    ///
228    /// let wrap_unbounded = DimensionValue::Wrap { min: None, max: None };
229    /// assert_eq!(wrap_unbounded.to_max_px(Px(200)), Px(200));
230    ///
231    /// let wrap_bounded = DimensionValue::Wrap { min: None, max: Some(Px(150)) };
232    /// assert_eq!(wrap_bounded.to_max_px(Px(200)), Px(150));
233    /// ```
234    pub fn to_max_px(&self, default: Px) -> Px {
235        match self {
236            DimensionValue::Fixed(value) => *value,
237            DimensionValue::Wrap { max, .. } => max.unwrap_or(default),
238            DimensionValue::Fill { max, .. } => max.unwrap_or(default),
239        }
240    }
241
242    /// Returns the maximum value of this dimension, if defined.
243    ///
244    /// This method extracts the maximum constraint from a dimension value,
245    /// which is useful for layout calculations and constraint validation.
246    ///
247    /// # Returns
248    /// - For `Fixed`: Returns `Some(fixed_value)` since fixed dimensions have an implicit maximum
249    /// - For `Wrap` and `Fill`: Returns the `max` value if specified, otherwise `None`
250    ///
251    /// # Example
252    /// ```
253    /// # use tessera_ui::Px;
254    /// # use tessera_ui::DimensionValue;
255    /// let fixed = DimensionValue::Fixed(Px(100));
256    /// assert_eq!(fixed.get_max(), Some(Px(100)));
257    ///
258    /// let wrap_bounded = DimensionValue::Wrap { min: Some(Px(50)), max: Some(Px(200)) };
259    /// assert_eq!(wrap_bounded.get_max(), Some(Px(200)));
260    ///
261    /// let wrap_unbounded = DimensionValue::Wrap { min: None, max: None };
262    /// assert_eq!(wrap_unbounded.get_max(), None);
263    /// ```
264    pub fn get_max(&self) -> Option<Px> {
265        match self {
266            DimensionValue::Fixed(value) => Some(*value),
267            DimensionValue::Wrap { max, .. } => *max,
268            DimensionValue::Fill { max, .. } => *max,
269        }
270    }
271
272    /// Returns the minimum value of this dimension, if defined.
273    ///
274    /// This method extracts the minimum constraint from a dimension value,
275    /// which is useful for layout calculations and ensuring components
276    /// maintain their minimum required size.
277    ///
278    /// # Returns
279    /// - For `Fixed`: Returns `Some(fixed_value)` since fixed dimensions have an implicit minimum
280    /// - For `Wrap` and `Fill`: Returns the `min` value if specified, otherwise `None`
281    ///
282    /// # Example
283    /// ```
284    /// # use tessera_ui::Px;
285    /// # use tessera_ui::DimensionValue;
286    /// let fixed = DimensionValue::Fixed(Px(100));
287    /// assert_eq!(fixed.get_min(), Some(Px(100)));
288    ///
289    /// let fill_bounded = DimensionValue::Fill { min: Some(Px(50)), max: Some(Px(200)) };
290    /// assert_eq!(fill_bounded.get_min(), Some(Px(50)));
291    ///
292    /// let fill_unbounded = DimensionValue::Fill { min: None, max: None };
293    /// assert_eq!(fill_unbounded.get_min(), None);
294    /// ```
295    pub fn get_min(&self) -> Option<Px> {
296        match self {
297            DimensionValue::Fixed(value) => Some(*value),
298            DimensionValue::Wrap { min, .. } => *min,
299            DimensionValue::Fill { min, .. } => *min,
300        }
301    }
302}
303
304/// Represents layout constraints for a component node.
305///
306/// A `Constraint` combines width and height dimension values to provide
307/// complete layout specification for a component. It defines how a component
308/// should size itself in both dimensions and provides methods for merging
309/// constraints in a component hierarchy.
310///
311/// # Examples
312///
313/// ```
314/// # use tessera_ui::Px;
315/// # use tessera_ui::{Constraint, DimensionValue};
316/// // A button with fixed size
317/// let button_constraint = Constraint::new(
318///     DimensionValue::Fixed(Px(120)),
319///     DimensionValue::Fixed(Px(40))
320/// );
321///
322/// // A flexible container that fills width but wraps height
323/// let container_constraint = Constraint::new(
324///     DimensionValue::Fill { min: Some(Px(200)), max: None },
325///     DimensionValue::Wrap { min: None, max: None }
326/// );
327///
328/// // A text component with bounded wrapping
329/// let text_constraint = Constraint::new(
330///     DimensionValue::Wrap { min: Some(Px(100)), max: Some(Px(400)) },
331///     DimensionValue::Wrap { min: None, max: None }
332/// );
333/// ```
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335pub struct Constraint {
336    /// The width dimension constraint
337    pub width: DimensionValue,
338    /// The height dimension constraint
339    pub height: DimensionValue,
340}
341
342impl Constraint {
343    /// A constraint that specifies no preference (Wrap { None, None } for both width and height).
344    ///
345    /// This constant represents the most flexible constraint possible, where a component
346    /// will size itself to its content without any bounds. It's equivalent to the default
347    /// constraint and is useful as a starting point for constraint calculations.
348    ///
349    /// # Example
350    /// ```
351    /// # use tessera_ui::{Constraint, DimensionValue};
352    /// let flexible = Constraint::NONE;
353    /// assert_eq!(flexible.width, DimensionValue::Wrap { min: None, max: None });
354    /// assert_eq!(flexible.height, DimensionValue::Wrap { min: None, max: None });
355    /// ```
356    pub const NONE: Self = Self {
357        width: DimensionValue::Wrap {
358            min: None,
359            max: None,
360        },
361        height: DimensionValue::Wrap {
362            min: None,
363            max: None,
364        },
365    };
366
367    /// Creates a new constraint with the specified width and height dimensions.
368    ///
369    /// This is the primary constructor for creating constraint instances.
370    ///
371    /// # Parameters
372    /// - `width`: The dimension value for the width constraint
373    /// - `height`: The dimension value for the height constraint
374    ///
375    /// # Example
376    /// ```
377    /// # use tessera_ui::Px;
378    /// # use tessera_ui::{Constraint, DimensionValue};
379    /// let constraint = Constraint::new(
380    ///     DimensionValue::Fixed(Px(100)),
381    ///     DimensionValue::Fill { min: Some(Px(50)), max: None }
382    /// );
383    /// ```
384    pub fn new(width: DimensionValue, height: DimensionValue) -> Self {
385        Self { width, height }
386    }
387
388    /// Merges this constraint with a parent constraint to resolve layout conflicts.
389    ///
390    /// This method implements the core constraint resolution algorithm used throughout
391    /// Tessera's layout system. When components are nested, their constraints must be
392    /// merged to ensure consistent and predictable layout behavior.
393    ///
394    /// # Merge Rules
395    ///
396    /// The merging follows a priority system designed to respect component intentions
397    /// while ensuring layout consistency:
398    ///
399    /// ## Fixed Constraints (Highest Priority)
400    /// - **Fixed always wins**: A fixed constraint cannot be overridden by its parent
401    /// - Fixed dimensions maintain their exact size regardless of available space
402    ///
403    /// ## Wrap Constraints (Content-Based)
404    /// - **Preserves content sizing**: Wrap constraints maintain their intrinsic sizing behavior
405    /// - When parent is Fixed: Child wraps within parent's fixed bounds
406    /// - When parent is Wrap: Child combines min/max constraints with parent
407    /// - When parent is Fill: Child wraps within parent's fill bounds
408    ///
409    /// ## Fill Constraints (Space-Filling)
410    /// - **Adapts to available space**: Fill constraints expand within parent bounds
411    /// - When parent is Fixed: Child fills parent's fixed space (respecting own min/max)
412    /// - When parent is Wrap: Child fills available space within parent's wrap bounds
413    /// - When parent is Fill: Child combines fill constraints with parent
414    ///
415    /// # Parameters
416    /// - `parent_constraint`: The constraint from the parent component
417    ///
418    /// # Returns
419    /// A new constraint that represents the resolved layout requirements
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// # use tessera_ui::Px;
425    /// # use tessera_ui::{Constraint, DimensionValue};
426    /// // Fixed child in fixed parent - child wins
427    /// let parent = Constraint::new(
428    ///     DimensionValue::Fixed(Px(200)),
429    ///     DimensionValue::Fixed(Px(200))
430    /// );
431    /// let child = Constraint::new(
432    ///     DimensionValue::Fixed(Px(100)),
433    ///     DimensionValue::Fixed(Px(100))
434    /// );
435    /// let merged = child.merge(&parent);
436    /// assert_eq!(merged.width, DimensionValue::Fixed(Px(100)));
437    ///
438    /// // Fill child in fixed parent - child fills parent's space
439    /// let child_fill = Constraint::new(
440    ///     DimensionValue::Fill { min: Some(Px(50)), max: None },
441    ///     DimensionValue::Fill { min: Some(Px(50)), max: None }
442    /// );
443    /// let merged_fill = child_fill.merge(&parent);
444    /// assert_eq!(merged_fill.width, DimensionValue::Fill {
445    ///     min: Some(Px(50)),
446    ///     max: Some(Px(200))
447    /// });
448    /// ```
449    pub fn merge(&self, parent_constraint: &Constraint) -> Self {
450        let new_width = Self::merge_dimension(self.width, parent_constraint.width);
451        let new_height = Self::merge_dimension(self.height, parent_constraint.height);
452        Constraint::new(new_width, new_height)
453    }
454
455    /// Internal helper method that merges two dimension values according to the constraint rules.
456    ///
457    /// This method implements the detailed logic for merging individual dimension constraints.
458    /// It's called by the public `merge` method to handle width and height dimensions separately.
459    ///
460    /// # Parameters
461    /// - `child_dim`: The dimension constraint from the child component
462    /// - `parent_dim`: The dimension constraint from the parent component
463    ///
464    /// # Returns
465    /// The merged dimension value that respects both constraints appropriately
466    fn merge_dimension(child_dim: DimensionValue, parent_dim: DimensionValue) -> DimensionValue {
467        match child_dim {
468            DimensionValue::Fixed(cv) => DimensionValue::Fixed(cv), // Child's Fixed overrides
469            DimensionValue::Wrap {
470                min: c_min,
471                max: c_max,
472            } => match parent_dim {
473                DimensionValue::Fixed(pv) => DimensionValue::Wrap {
474                    // Wrap stays as Wrap, but constrained by parent's fixed size
475                    min: c_min, // Keep child's own min
476                    max: match c_max {
477                        Some(c) => Some(c.min(pv)), // Child's max capped by parent's fixed size
478                        None => Some(pv),           // Parent's fixed size becomes the max
479                    },
480                },
481                DimensionValue::Wrap {
482                    min: _p_min,
483                    max: p_max,
484                } => DimensionValue::Wrap {
485                    // Combine min/max from parent and child for Wrap
486                    min: c_min, // Wrap always keeps its own min, never inherits from parent
487                    max: match (c_max, p_max) {
488                        (Some(c), Some(p)) => Some(c.min(p)), // Take the more restrictive max
489                        (Some(c), None) => Some(c),
490                        (None, Some(p)) => Some(p),
491                        (None, None) => None,
492                    },
493                },
494                DimensionValue::Fill {
495                    min: _p_fill_min,
496                    max: p_fill_max,
497                } => DimensionValue::Wrap {
498                    // Child wants to wrap, so it stays as Wrap
499                    min: c_min, // Keep child's own min, don't inherit from parent's Fill
500                    max: match (c_max, p_fill_max) {
501                        (Some(c), Some(p)) => Some(c.min(p)), // Child's max should cap parent's fill max
502                        (Some(c), None) => Some(c),
503                        (None, Some(p)) => Some(p),
504                        (None, None) => None,
505                    },
506                },
507            },
508            DimensionValue::Fill {
509                min: c_fill_min,
510                max: c_fill_max,
511            } => match parent_dim {
512                DimensionValue::Fixed(pv) => {
513                    // Child wants to fill, parent is fixed. Result is Fill with parent's fixed size as max.
514                    DimensionValue::Fill {
515                        min: c_fill_min, // Keep child's own min
516                        max: match c_fill_max {
517                            Some(c) => Some(c.min(pv)), // Child's max capped by parent's fixed size
518                            None => Some(pv),           // Parent's fixed size becomes the max
519                        },
520                    }
521                }
522                DimensionValue::Wrap {
523                    min: p_wrap_min,
524                    max: p_wrap_max,
525                } => DimensionValue::Fill {
526                    // Fill remains Fill, parent Wrap offers no concrete size unless it has max
527                    min: c_fill_min.or(p_wrap_min), // Child's fill min, or parent's wrap min
528                    max: match (c_fill_max, p_wrap_max) {
529                        // Child's fill max, potentially capped by parent's wrap max
530                        (Some(cf), Some(pw)) => Some(cf.min(pw)),
531                        (Some(cf), None) => Some(cf),
532                        (None, Some(pw)) => Some(pw),
533                        (None, None) => None,
534                    },
535                },
536                DimensionValue::Fill {
537                    min: p_fill_min,
538                    max: p_fill_max,
539                } => {
540                    // Both are Fill. Combine min and max.
541                    // New min is the greater of the two mins (or the existing one).
542                    // New max is the smaller of the two maxes (or the existing one).
543                    let new_min = match (c_fill_min, p_fill_min) {
544                        (Some(cm), Some(pm)) => Some(cm.max(pm)),
545                        (Some(cm), None) => Some(cm),
546                        (None, Some(pm)) => Some(pm),
547                        (None, None) => None,
548                    };
549                    let new_max = match (c_fill_max, p_fill_max) {
550                        (Some(cm), Some(pm)) => Some(cm.min(pm)),
551                        (Some(cm), None) => Some(cm),
552                        (None, Some(pm)) => Some(pm),
553                        (None, None) => None,
554                    };
555                    // Ensure min <= max if both are Some
556                    let (final_min, final_max) = match (new_min, new_max) {
557                        (Some(n_min), Some(n_max)) if n_min > n_max => (Some(n_max), Some(n_max)), // Or handle error/warning
558                        _ => (new_min, new_max),
559                    };
560                    DimensionValue::Fill {
561                        min: final_min,
562                        max: final_max,
563                    }
564                }
565            },
566        }
567    }
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573
574    #[test]
575    fn test_fixed_parent_wrap_child_wrap_grandchild() {
576        // Test three-level hierarchy: Fixed(100) -> Wrap{20-80} -> Wrap{10-50}
577        // This tests constraint propagation through multiple levels
578
579        // Parent component with fixed 100x100 size
580        let parent = Constraint::new(
581            DimensionValue::Fixed(Px(100)),
582            DimensionValue::Fixed(Px(100)),
583        );
584
585        // Child component that wraps content with bounds 20-80
586        let child = Constraint::new(
587            DimensionValue::Wrap {
588                min: Some(Px(20)),
589                max: Some(Px(80)),
590            },
591            DimensionValue::Wrap {
592                min: Some(Px(20)),
593                max: Some(Px(80)),
594            },
595        );
596
597        // Grandchild component that wraps content with bounds 10-50
598        let grandchild = Constraint::new(
599            DimensionValue::Wrap {
600                min: Some(Px(10)),
601                max: Some(Px(50)),
602            },
603            DimensionValue::Wrap {
604                min: Some(Px(10)),
605                max: Some(Px(50)),
606            },
607        );
608
609        // First level merge: child merges with fixed parent
610        let merged_child = child.merge(&parent);
611
612        // Child is Wrap, parent is Fixed - result should be Wrap with child's constraints
613        // Since child's max (80) is less than parent's fixed size (100), child keeps its bounds
614        assert_eq!(
615            merged_child.width,
616            DimensionValue::Wrap {
617                min: Some(Px(20)),
618                max: Some(Px(80))
619            }
620        );
621        assert_eq!(
622            merged_child.height,
623            DimensionValue::Wrap {
624                min: Some(Px(20)),
625                max: Some(Px(80))
626            }
627        );
628
629        // Second level merge: grandchild merges with merged child
630        let final_result = grandchild.merge(&merged_child);
631
632        // Both are Wrap - result should be Wrap with the more restrictive constraints
633        // Grandchild's max (50) is smaller than merged child's max (80), so grandchild wins
634        assert_eq!(
635            final_result.width,
636            DimensionValue::Wrap {
637                min: Some(Px(10)),
638                max: Some(Px(50))
639            }
640        );
641        assert_eq!(
642            final_result.height,
643            DimensionValue::Wrap {
644                min: Some(Px(10)),
645                max: Some(Px(50))
646            }
647        );
648    }
649
650    #[test]
651    fn test_fill_parent_wrap_child() {
652        // Test Fill parent with Wrap child: Fill{50-200} -> Wrap{30-150}
653        // Child should remain Wrap and keep its own constraints
654
655        let parent = Constraint::new(
656            DimensionValue::Fill {
657                min: Some(Px(50)),
658                max: Some(Px(200)),
659            },
660            DimensionValue::Fill {
661                min: Some(Px(50)),
662                max: Some(Px(200)),
663            },
664        );
665
666        let child = Constraint::new(
667            DimensionValue::Wrap {
668                min: Some(Px(30)),
669                max: Some(Px(150)),
670            },
671            DimensionValue::Wrap {
672                min: Some(Px(30)),
673                max: Some(Px(150)),
674            },
675        );
676
677        let result = child.merge(&parent);
678
679        // Child is Wrap, parent is Fill - result should be Wrap
680        // Child keeps its own min (30px) and max (150px) since both are within parent's bounds
681        assert_eq!(
682            result.width,
683            DimensionValue::Wrap {
684                min: Some(Px(30)),
685                max: Some(Px(150))
686            }
687        );
688        assert_eq!(
689            result.height,
690            DimensionValue::Wrap {
691                min: Some(Px(30)),
692                max: Some(Px(150))
693            }
694        );
695    }
696
697    #[test]
698    fn test_fill_parent_wrap_child_no_child_min() {
699        // Test Fill parent with Wrap child that has no minimum: Fill{50-200} -> Wrap{None-150}
700        // Child should keep its own constraints and not inherit parent's minimum
701
702        let parent = Constraint::new(
703            DimensionValue::Fill {
704                min: Some(Px(50)),
705                max: Some(Px(200)),
706            },
707            DimensionValue::Fill {
708                min: Some(Px(50)),
709                max: Some(Px(200)),
710            },
711        );
712
713        let child = Constraint::new(
714            DimensionValue::Wrap {
715                min: None,
716                max: Some(Px(150)),
717            },
718            DimensionValue::Wrap {
719                min: None,
720                max: Some(Px(150)),
721            },
722        );
723
724        let result = child.merge(&parent);
725
726        // Child is Wrap and should keep its own min (None), not inherit from parent's Fill min
727        // This preserves the wrap behavior of sizing to content without artificial minimums
728        assert_eq!(
729            result.width,
730            DimensionValue::Wrap {
731                min: None,
732                max: Some(Px(150))
733            }
734        );
735        assert_eq!(
736            result.height,
737            DimensionValue::Wrap {
738                min: None,
739                max: Some(Px(150))
740            }
741        );
742    }
743
744    #[test]
745    fn test_fill_parent_wrap_child_no_parent_max() {
746        // Test Fill parent with no maximum and Wrap child: Fill{50-None} -> Wrap{30-150}
747        // Child should keep its own constraints since parent has no upper bound
748
749        let parent = Constraint::new(
750            DimensionValue::Fill {
751                min: Some(Px(50)),
752                max: None,
753            },
754            DimensionValue::Fill {
755                min: Some(Px(50)),
756                max: None,
757            },
758        );
759
760        let child = Constraint::new(
761            DimensionValue::Wrap {
762                min: Some(Px(30)),
763                max: Some(Px(150)),
764            },
765            DimensionValue::Wrap {
766                min: Some(Px(30)),
767                max: Some(Px(150)),
768            },
769        );
770
771        let result = child.merge(&parent);
772
773        // Child should keep its own constraints since parent Fill has no max to constrain it
774        assert_eq!(
775            result.width,
776            DimensionValue::Wrap {
777                min: Some(Px(30)),
778                max: Some(Px(150))
779            }
780        );
781        assert_eq!(
782            result.height,
783            DimensionValue::Wrap {
784                min: Some(Px(30)),
785                max: Some(Px(150))
786            }
787        );
788    }
789
790    #[test]
791    fn test_fixed_parent_wrap_child() {
792        // Test Fixed parent with Wrap child: Fixed(100) -> Wrap{30-120}
793        // Child's max should be capped by parent's fixed size
794
795        let parent = Constraint::new(
796            DimensionValue::Fixed(Px(100)),
797            DimensionValue::Fixed(Px(100)),
798        );
799
800        let child = Constraint::new(
801            DimensionValue::Wrap {
802                min: Some(Px(30)),
803                max: Some(Px(120)),
804            },
805            DimensionValue::Wrap {
806                min: Some(Px(30)),
807                max: Some(Px(120)),
808            },
809        );
810
811        let result = child.merge(&parent);
812
813        // Child remains Wrap but max is limited by parent's fixed size
814        // min keeps child's own value (30px)
815        // max becomes the smaller of child's max (120px) and parent's fixed size (100px)
816        assert_eq!(
817            result.width,
818            DimensionValue::Wrap {
819                min: Some(Px(30)),
820                max: Some(Px(100))
821            }
822        );
823        assert_eq!(
824            result.height,
825            DimensionValue::Wrap {
826                min: Some(Px(30)),
827                max: Some(Px(100))
828            }
829        );
830    }
831
832    #[test]
833    fn test_fixed_parent_wrap_child_no_child_max() {
834        // Test Fixed parent with Wrap child that has no maximum: Fixed(100) -> Wrap{30-None}
835        // Parent's fixed size should become the child's maximum
836
837        let parent = Constraint::new(
838            DimensionValue::Fixed(Px(100)),
839            DimensionValue::Fixed(Px(100)),
840        );
841
842        let child = Constraint::new(
843            DimensionValue::Wrap {
844                min: Some(Px(30)),
845                max: None,
846            },
847            DimensionValue::Wrap {
848                min: Some(Px(30)),
849                max: None,
850            },
851        );
852
853        let result = child.merge(&parent);
854
855        // Child remains Wrap, parent's fixed size becomes the maximum constraint
856        // This prevents the child from growing beyond the parent's available space
857        assert_eq!(
858            result.width,
859            DimensionValue::Wrap {
860                min: Some(Px(30)),
861                max: Some(Px(100))
862            }
863        );
864        assert_eq!(
865            result.height,
866            DimensionValue::Wrap {
867                min: Some(Px(30)),
868                max: Some(Px(100))
869            }
870        );
871    }
872
873    #[test]
874    fn test_fixed_parent_fill_child() {
875        // Test Fixed parent with Fill child: Fixed(100) -> Fill{30-120}
876        // Child should fill parent's space but be capped by parent's fixed size
877
878        let parent = Constraint::new(
879            DimensionValue::Fixed(Px(100)),
880            DimensionValue::Fixed(Px(100)),
881        );
882
883        let child = Constraint::new(
884            DimensionValue::Fill {
885                min: Some(Px(30)),
886                max: Some(Px(120)),
887            },
888            DimensionValue::Fill {
889                min: Some(Px(30)),
890                max: Some(Px(120)),
891            },
892        );
893
894        let result = child.merge(&parent);
895
896        // Child remains Fill but max is limited by parent's fixed size
897        // min keeps child's own value (30px)
898        // max becomes the smaller of child's max (120px) and parent's fixed size (100px)
899        assert_eq!(
900            result.width,
901            DimensionValue::Fill {
902                min: Some(Px(30)),
903                max: Some(Px(100))
904            }
905        );
906        assert_eq!(
907            result.height,
908            DimensionValue::Fill {
909                min: Some(Px(30)),
910                max: Some(Px(100))
911            }
912        );
913    }
914
915    #[test]
916    fn test_fixed_parent_fill_child_no_child_max() {
917        // Test Fixed parent with Fill child that has no maximum: Fixed(100) -> Fill{30-None}
918        // Parent's fixed size should become the child's maximum
919
920        let parent = Constraint::new(
921            DimensionValue::Fixed(Px(100)),
922            DimensionValue::Fixed(Px(100)),
923        );
924
925        let child = Constraint::new(
926            DimensionValue::Fill {
927                min: Some(Px(30)),
928                max: None,
929            },
930            DimensionValue::Fill {
931                min: Some(Px(30)),
932                max: None,
933            },
934        );
935
936        let result = child.merge(&parent);
937
938        // Child remains Fill, parent's fixed size becomes the maximum constraint
939        // This ensures the child fills exactly the parent's available space
940        assert_eq!(
941            result.width,
942            DimensionValue::Fill {
943                min: Some(Px(30)),
944                max: Some(Px(100))
945            }
946        );
947        assert_eq!(
948            result.height,
949            DimensionValue::Fill {
950                min: Some(Px(30)),
951                max: Some(Px(100))
952            }
953        );
954    }
955
956    #[test]
957    fn test_fixed_parent_fill_child_no_child_min() {
958        // Test Fixed parent with Fill child that has no minimum: Fixed(100) -> Fill{None-120}
959        // Child should fill parent's space with no minimum constraint
960
961        let parent = Constraint::new(
962            DimensionValue::Fixed(Px(100)),
963            DimensionValue::Fixed(Px(100)),
964        );
965
966        let child = Constraint::new(
967            DimensionValue::Fill {
968                min: None,
969                max: Some(Px(120)),
970            },
971            DimensionValue::Fill {
972                min: None,
973                max: Some(Px(120)),
974            },
975        );
976
977        let result = child.merge(&parent);
978
979        // Child remains Fill, keeps its own min (None), max is limited by parent's fixed size
980        // This allows the child to fill the parent's space without any minimum size requirement
981        assert_eq!(
982            result.width,
983            DimensionValue::Fill {
984                min: None,
985                max: Some(Px(100))
986            }
987        );
988        assert_eq!(
989            result.height,
990            DimensionValue::Fill {
991                min: None,
992                max: Some(Px(100))
993            }
994        );
995    }
996}