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