Skip to main content

kaolin/style/
sizing.rs

1use typed_floats::tf64::{Positive, PositiveFinite};
2
3use crate::style::layout::Direction;
4
5/// Represents the preferred sizing behavior for a specific Dimension.
6#[derive(Clone, Copy, Debug)]
7pub enum PreferredSize {
8    // gravitates towards a fixed size
9    Fixed(PositiveFinite),
10    // no fixed size, grows indefenetly with a factor
11    Grow(PositiveFinite),
12}
13
14impl Default for PreferredSize {
15    fn default() -> Self {
16        PreferredSize::Fixed(PositiveFinite::new(0.0).unwrap()) // gravitate to 0.0 for fit sizing
17    }
18}
19
20/// Represents the sizing information for a UI element used while calculating layout.
21#[derive(Clone, Copy, Debug)]
22pub struct SizingDimensions {
23    pub min: PositiveFinite,      // Minimum size
24    pub preferred: PreferredSize, // Preferred size
25    pub max: Positive,            // Maximum size
26}
27
28impl Default for SizingDimensions {
29    fn default() -> Self {
30        SizingDimensions {
31            min: PositiveFinite::new(0.0).unwrap(),
32            preferred: PreferredSize::default(),
33            max: Positive::new(f64::INFINITY).unwrap(), // No maximum limit
34        }
35    }
36}
37
38impl SizingDimensions {
39    /// True if the sizing is fixed and layout calculations will not change it.
40    pub fn is_fixed(&self) -> bool {
41        matches!(self.preferred, PreferredSize::Fixed(_))
42    }
43
44    /// True if the sizing is growable and can expand to fill available space.
45    pub fn is_growable(&self) -> bool {
46        matches!(self.preferred, PreferredSize::Grow(_))
47    }
48
49    /// True if the sizing is shrinkable and can be reduced in size when
50    /// overflowing its container.
51    pub fn is_shrinkable(&self) -> bool {
52        self.max > self.min // Allow shrinking if not fixed
53    }
54
55    /// Returns the growth factor for the dimension.
56    pub fn get_grow_factor(&self) -> f64 {
57        match self.preferred {
58            PreferredSize::Grow(factor) => factor.into(),
59            _ => 0.0, // Default grow factor if not specified
60        }
61    }
62
63    /// Returns the dimension value clamped between min and max.
64    ///
65    /// ```
66    /// # use typed_floats::tf64::{Positive, PositiveFinite};
67    /// # use kaolin::style::sizing::{SizingDimensions, PreferredSize};
68    /// let sized = SizingDimensions {
69    ///     min: PositiveFinite::new(100.0).unwrap(),
70    ///     preferred: PreferredSize::Fixed(PositiveFinite::new(200.0).unwrap()),
71    ///     max: Positive::new(300.0).unwrap(),
72    /// };
73    /// assert_eq!(sized.clamped(250.0), 250.0);
74    /// assert_eq!(sized.clamped(50.0), 100.0);
75    /// assert_eq!(sized.clamped(350.0), 300.0);
76    ///
77    /// assert_eq!(sized.clamped(f64::NAN), 100.0); // NaN defaults to min
78    /// assert_eq!(sized.clamped(f64::INFINITY), 300.0);
79    /// assert_eq!(sized.clamped(f64::NEG_INFINITY), 100.0);
80    /// ```
81    pub fn clamped(&self, value: f64) -> f64 {
82        if value.is_nan() {
83            return self.min.into(); // Default to min if value is NaN
84        }
85        value.clamp(self.min.into(), self.max.into())
86    }
87
88    pub fn max(&self) -> f64 {
89        self.max.into()
90    }
91
92    pub fn min(&self) -> f64 {
93        self.min.into()
94    }
95}
96
97/// Represents the sizing behavior of a box.
98#[derive(Default, Clone, Copy)]
99pub struct BoxSizing {
100    pub width: Sizing,
101    pub height: Sizing,
102}
103
104impl BoxSizing {
105    pub fn main(&mut self, dir: Direction) -> &mut Sizing {
106        match dir {
107            Direction::LeftToRight | Direction::RightToLeft => &mut self.width,
108            Direction::TopToBottom | Direction::BottomToTop => &mut self.height,
109        }
110    }
111
112    pub fn cross(&mut self, dir: Direction) -> &mut Sizing {
113        match dir {
114            Direction::LeftToRight | Direction::RightToLeft => &mut self.height,
115            Direction::TopToBottom | Direction::BottomToTop => &mut self.width,
116        }
117    }
118}
119
120/// Defines the sizing behavior for a flex box.
121///
122/// - `sizing!(width, height)` will create a box sizing with the specified width and height behaviors.
123/// - `sizing!(size)` will create a box sizing with the same behavior for both axes.
124/// - `sizing!()` will define a box sizing behavior with default values (fit for both axes).
125///
126/// You can also use `sizing!(key: value)` to specify width or height behavior individually (where `key` is either `width` or `height`, duh).
127///
128/// Example:
129/// ```ignore
130/// // grow both width and height
131/// FlexStyle::new()
132///     .sizing(sizing!(grow!())),
133///
134/// // fit width (max 100.0) and fixed height (200.0)
135/// FlexStyle::new()
136///     .sizing(sizing!(fit!(100.0), fixed!(200.0))),
137/// ```
138#[macro_export]
139macro_rules! sizing {
140    ($width:expr, $height:expr) => {
141        $crate::style::sizing::BoxSizing {
142            width: $width,
143            height: $height,
144        }
145    };
146
147    ($size:expr) => {
148        $crate::style::sizing::BoxSizing {
149            width: $size,
150            height: $size,
151        }
152    };
153    (width: $width:expr, height: $height:expr) => {
154        $crate::style::sizing::BoxSizing {
155            width: $width,
156            height: $height,
157        }
158    };
159    ($key:ident : $value:expr$(,)?) => {
160        $crate::style::sizing::BoxSizing {
161            $key: $value,
162            ..$crate::style::sizing::BoxSizing::default()
163        }
164    };
165    ($($key:ident : $value:expr),* $(,)?) => {
166        $crate::style::sizing::BoxSizing {
167            $($key: $value,)*
168        }
169    };
170    () => {
171        $crate::style::sizing::BoxSizing::default()
172    };
173}
174
175/// Represents the Sizing behavior for a dimension.
176#[derive(Default, Clone, Copy)]
177pub enum Sizing {
178    #[default]
179    Default,
180    Fit {
181        min: Option<PositiveFinite>,
182        max: Option<Positive>,
183    },
184    Fixed(PositiveFinite),
185    Grow {
186        factor: Option<PositiveFinite>, // Growth factor
187        min: Option<PositiveFinite>,
188        max: Option<Positive>,
189    },
190}
191
192impl From<Sizing> for SizingDimensions {
193    fn from(sizing: Sizing) -> Self {
194        match sizing {
195            Sizing::Default => SizingDimensions::default(), // is actually just FIT with no limits
196            Sizing::Fit { min, max } => SizingDimensions {
197                min: min.unwrap_or_default(),
198                preferred: PreferredSize::Fixed(min.unwrap_or_default()), // prefers to stay at the min i guess
199                max: max.unwrap_or(Positive::new(f64::INFINITY).unwrap()),
200            },
201            Sizing::Fixed(size) => SizingDimensions {
202                min: size,
203                preferred: PreferredSize::Fixed(size),
204                max: size.into(),
205            },
206            Sizing::Grow { factor, min, max } => SizingDimensions {
207                min: min.unwrap_or_default(),
208                preferred: PreferredSize::Grow(factor.unwrap_or(PositiveFinite::new(1.0).unwrap())),
209                max: max.unwrap_or(Positive::new(f64::INFINITY).unwrap()),
210            },
211        }
212    }
213}
214
215/// Defines a fit sizing behavior.
216///
217/// - `fit!()` will create a fit behavior with no constraints.
218/// - `fit!(max)` will be interpreted as the maximum size, with no minimum constraint.
219/// - `fit!(min, max)` will set both the minimum and maximum size.
220#[macro_export]
221macro_rules! fit {
222    ($min:expr, $max:expr) => {
223        $crate::style::sizing::Sizing::Fit {
224            min: Some(typed_floats::tf64::PositiveFinite::new($min).unwrap()),
225            max: Some(typed_floats::tf64::Positive::new($max).unwrap()),
226        }
227    };
228
229    ($max:expr) => {
230        $crate::style::sizing::Sizing::Fit {
231            min: None,
232            max: Some(typed_floats::tf64::Positive::new($max).unwrap()),
233        }
234    };
235
236    () => {
237        $crate::style::sizing::Sizing::Fit {
238            min: None,
239            max: None,
240        }
241    };
242}
243
244/// Defines a fixed sizing behavior.
245///
246/// - `fixed!(size)` will define a fixed size. It's fixed. The constraints are that it's fixed.
247#[macro_export]
248macro_rules! fixed {
249    ($size:expr) => {
250        $crate::style::sizing::Sizing::Fixed(
251            typed_floats::tf64::PositiveFinite::new($size).unwrap(),
252        )
253    };
254}
255
256/// Defines a grow sizing behavior.
257///
258/// - `grow!()` will create a grow behavior with a default factor of `1.0`.
259/// - `grow!(factor)` will create a grow behavior with the specified factor, and
260///   no constraints. Different factors are useful for giving different
261///   proportions of space to sibling growable elements.
262/// - `grow!(factor, min, max)` will create a grow behavior with the specified constraints.
263#[macro_export]
264macro_rules! grow {
265    ($factor:expr, $min:expr, $max:expr) => {
266        $crate::style::sizing::Sizing::Grow {
267            factor: Some(typed_floats::tf64::PositiveFinite::new($factor).unwrap()),
268            min: Some(typed_floats::tf64::PositiveFinite::new($min).unwrap()),
269            max: Some(typed_floats::tf64::Positive::new($max).unwrap()),
270        }
271    };
272
273    ($factor:expr) => {
274        $crate::style::sizing::Sizing::Grow {
275            factor: Some(typed_floats::tf64::PositiveFinite::new($factor).unwrap()),
276            min: None,
277            max: None,
278        }
279    };
280
281    () => {
282        $crate::style::sizing::Sizing::Grow {
283            factor: None,
284            min: None,
285            max: None,
286        }
287    };
288}