ailloli_ui_core/style/length.rs
1//! CSS-like intrinsic, fixed, fill, and percentage axis lengths.
2
3/// CSS-like length for layout axes (width, height, min/max).
4///
5/// Possible values are intrinsic [`Length::Auto`], fixed [`Length::Px`], parent
6/// fill, and [`Length::Percent`].
7///
8/// # Examples
9///
10/// ```
11/// use ailloli_ui_core::style::Length;
12/// assert_eq!(Length::percent(50.0).resolve(200.0), Some(100.0));
13/// ```
14#[derive(Clone, Copy, Debug, Default, PartialEq)]
15pub enum Length {
16 /// Intrinsic size (children, text measure).
17 #[default]
18 Auto,
19 /// Fixed logical pixels; resolution floors negative values and NaN at zero.
20 /// Positive infinity remains infinite.
21 Px(f32),
22 /// Fill the parent on this axis when resolved via [`Length::resolve`].
23 ///
24 /// In a flex main axis, parent layout treats `Fill` as **remaining space**
25 /// (grow participant) instead of calling `resolve` with the full parent max.
26 Fill,
27 /// Fraction of parent available space.
28 ///
29 /// Direct construction stores the fraction verbatim; [`Length::percent`]
30 /// accepts either fractional or percentage-style input.
31 Percent(f32),
32}
33
34impl Length {
35 /// Creates a fixed logical-pixel length without immediate normalization.
36 ///
37 /// # Examples
38 ///
39 /// ```
40 /// use ailloli_ui_core::style::Length;
41 /// assert_eq!(Length::px(24.0), Length::Px(24.0));
42 /// ```
43 pub fn px(value: f32) -> Self {
44 Self::Px(value)
45 }
46
47 /// Fill parent on this axis (see [`Length::Fill`]).
48 ///
49 /// # Examples
50 ///
51 /// ```
52 /// use ailloli_ui_core::style::Length;
53 /// assert_eq!(Length::fill(), Length::Fill);
54 /// ```
55 pub fn fill() -> Self {
56 Self::Fill
57 }
58
59 /// Creates a percentage/fraction of parent available space.
60 ///
61 /// Values with absolute magnitude at most `1.0` are treated as fractions,
62 /// so `0.5` and `1.0` mean 50% and 100%. Larger magnitudes are divided by
63 /// 100, so `50.0` also means 50%. Negative fractions resolve to zero.
64 ///
65 /// # Examples
66 ///
67 /// ```
68 /// use ailloli_ui_core::style::Length;
69 /// assert_eq!(Length::percent(50.0), Length::Percent(0.5));
70 /// assert_eq!(Length::percent(0.5), Length::Percent(0.5));
71 /// ```
72 pub fn percent(value: f32) -> Self {
73 let fraction = if value.abs() <= 1.0 {
74 value
75 } else {
76 value / 100.0
77 };
78 Self::Percent(fraction)
79 }
80
81 /// `true` when this axis uses intrinsic sizing.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// use ailloli_ui_core::style::Length;
87 /// assert!(Length::Auto.is_auto());
88 /// assert!(!Length::Fill.is_auto());
89 /// ```
90 pub fn is_auto(self) -> bool {
91 matches!(self, Self::Auto)
92 }
93
94 /// Resolves against parent `available` logical pixels.
95 ///
96 /// [`Self::Auto`] returns `None`. Fixed, fill, and percentage results are
97 /// floored at zero; they are not capped to `available`. Non-finite results
98 /// follow floating-point `max` semantics.
99 ///
100 /// # Examples
101 ///
102 /// ```
103 /// use ailloli_ui_core::style::Length;
104 ///
105 /// assert_eq!(Length::Auto.resolve(200.0), None);
106 /// assert_eq!(Length::px(24.0).resolve(200.0), Some(24.0));
107 /// assert_eq!(Length::percent(50.0).resolve(200.0), Some(100.0));
108 /// assert_eq!(Length::fill().resolve(200.0), Some(200.0));
109 /// assert_eq!(Length::px(-1.0).resolve(200.0), Some(0.0));
110 /// ```
111 pub fn resolve(self, available: f32) -> Option<f32> {
112 match self {
113 Self::Auto => None,
114 Self::Px(value) => Some(value.max(0.0)),
115 Self::Fill => Some(available.max(0.0)),
116 Self::Percent(value) => Some((available * value).max(0.0)),
117 }
118 }
119}
120
121impl From<f32> for Length {
122 fn from(value: f32) -> Self {
123 Self::Px(value)
124 }
125}