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