Skip to main content

torin/values/
size.rs

1use std::{
2    fmt::Debug,
3    hash::Hash,
4    sync::Arc,
5};
6
7pub use euclid::Rect;
8
9use crate::{
10    geometry::Length,
11    measure::Phase,
12    scaled::Scaled,
13};
14
15pub struct SizeFnContext {
16    pub parent: f32,
17    pub available_parent: f32,
18    pub parent_margin: f32,
19    pub root: f32,
20    pub phase: Phase,
21}
22
23#[cfg(feature = "serde")]
24pub use serde::*;
25
26#[derive(Clone)]
27pub struct SizeFn(Arc<dyn Fn(SizeFnContext) -> Option<f32> + Sync + Send>, u64);
28
29#[cfg(feature = "serde")]
30impl Serialize for SizeFn {
31    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32    where
33        S: Serializer,
34    {
35        serializer.serialize_str("Fn")
36    }
37}
38
39#[cfg(feature = "serde")]
40impl<'de> Deserialize<'de> for SizeFn {
41    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42    where
43        D: Deserializer<'de>,
44    {
45        struct FnVisitor;
46        use serde::de::Visitor;
47
48        impl Visitor<'_> for FnVisitor {
49            type Value = SizeFn;
50
51            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
52                formatter.write_str("\"Fn\"")
53            }
54
55            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
56            where
57                E: de::Error,
58            {
59                if v == "Fn" {
60                    Ok(SizeFn(Arc::new(|_ctx| None), 0))
61                } else {
62                    Err(E::custom(format!("expected \"Fn\", got {v}")))
63                }
64            }
65        }
66
67        deserializer.deserialize_str(FnVisitor)
68    }
69}
70
71impl SizeFn {
72    pub fn new(func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send) -> Self {
73        Self(Arc::new(func), 0)
74    }
75
76    pub fn new_data<D: Hash>(
77        func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send,
78        data: &D,
79    ) -> Self {
80        use std::hash::Hasher;
81        let mut hasher = std::hash::DefaultHasher::default();
82        data.hash(&mut hasher);
83        Self(Arc::new(func), hasher.finish())
84    }
85
86    pub fn call(&self, context: SizeFnContext) -> Option<f32> {
87        (self.0)(context)
88    }
89}
90
91impl Debug for SizeFn {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        f.write_str("SizeFn")
94    }
95}
96
97impl PartialEq for SizeFn {
98    fn eq(&self, other: &Self) -> bool {
99        self.1 == other.1
100    }
101}
102
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104#[derive(Default, PartialEq, Clone, Debug)]
105pub enum Size {
106    /// Sizes the element based on its content. This is the default.
107    ///
108    /// Can also be created with [`Size::auto`].
109    ///
110    /// ```
111    /// # use torin::prelude::*;
112    /// let size = Size::auto();
113    /// ```
114    #[default]
115    Inner,
116
117    /// Expands to fill all the available space from its parent.
118    ///
119    /// Can also be created with [`Size::fill`].
120    ///
121    /// ```
122    /// # use torin::prelude::*;
123    /// let size = Size::fill();
124    /// ```
125    Fill,
126
127    /// Expand to the biggest sibling when using [`Content::fit`](crate::content::Content::fit).
128    ///
129    /// Can also be created with [`Size::fill_minimum`].
130    ///
131    /// ```
132    /// # use torin::prelude::*;
133    /// let size = Size::fill_minimum();
134    /// ```
135    FillMinimum,
136
137    /// Sizes as a percentage relative to the parent's size.
138    ///
139    /// Can also be created with [`Size::percent`].
140    ///
141    /// ```
142    /// # use torin::prelude::*;
143    /// let size = Size::percent(50.0);
144    /// ```
145    Percentage(Length),
146
147    /// Fixed size in pixels.
148    ///
149    /// Can also be created with [`Size::px`].
150    ///
151    /// ```
152    /// # use torin::prelude::*;
153    /// let size = Size::px(200.0);
154    /// ```
155    Pixels(Length),
156
157    /// Sizes as a percentage relative to the root (window) size.
158    ///
159    /// Can also be created with [`Size::window_percent`].
160    ///
161    /// ```
162    /// # use torin::prelude::*;
163    /// let size = Size::window_percent(80.0);
164    /// ```
165    RootPercentage(Length),
166
167    /// Dynamic size computed by a closure at layout time.
168    ///
169    /// Can also be created with [`Size::func`] or [`Size::func_data`].
170    Fn(Box<SizeFn>),
171
172    /// Flex grow factor, fills the available space proportionally in the final layout phase.
173    ///
174    /// Can also be created with [`Size::flex`].
175    ///
176    /// ```
177    /// # use torin::prelude::*;
178    /// let size = Size::flex(1.0);
179    /// ```
180    Flex(Length),
181}
182
183impl Size {
184    /// Use an [`Inner`](Size::Inner) size.
185    pub fn auto() -> Size {
186        Size::Inner
187    }
188
189    /// Use a [`Fill`](Size::Fill) size.
190    pub fn fill() -> Size {
191        Size::Fill
192    }
193
194    /// Use a [`FillMinimum`](Size::FillMinimum) size.
195    pub fn fill_minimum() -> Size {
196        Size::FillMinimum
197    }
198
199    /// Use a [`Percentage`](Size::Percentage) size.
200    pub fn percent(percent: impl Into<f32>) -> Size {
201        Size::Percentage(Length::new(percent.into()))
202    }
203
204    /// Use a [`Pixels`](Size::Pixels) size.
205    pub fn px(px: impl Into<f32>) -> Size {
206        Size::Pixels(Length::new(px.into()))
207    }
208
209    /// Use a [`RootPercentage`](Size::RootPercentage) size.
210    pub fn window_percent(percent: impl Into<f32>) -> Size {
211        Size::RootPercentage(Length::new(percent.into()))
212    }
213
214    /// Use a [`Flex`](Size::Flex) size.
215    pub fn flex(flex: impl Into<f32>) -> Size {
216        Size::Flex(Length::new(flex.into()))
217    }
218
219    /// Use a dynamic [`Fn`](Size::Fn) size computed by the given closure.
220    pub fn func(func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send) -> Size {
221        Self::Fn(Box::new(SizeFn::new(func)))
222    }
223
224    /// Use a dynamic [`Fn`](Size::Fn) size with hashable data for equality checks.
225    pub fn func_data<D: Hash>(
226        func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send,
227        data: &D,
228    ) -> Size {
229        Self::Fn(Box::new(SizeFn::new_data(func, data)))
230    }
231
232    pub(crate) fn flex_grow(&self) -> Option<Length> {
233        match self {
234            Self::Flex(f) => Some(*f),
235            _ => None,
236        }
237    }
238
239    pub(crate) fn is_flex(&self) -> bool {
240        matches!(self, Self::Flex(_))
241    }
242
243    pub(crate) fn inner_sized(&self) -> bool {
244        matches!(self, Self::Inner | Self::FillMinimum)
245    }
246
247    pub fn pretty(&self) -> String {
248        match self {
249            Self::Inner => "auto".to_string(),
250            Self::Pixels(s) => format!("{}", s.get()),
251            Self::Fn(_) => "Fn".to_string(),
252            Self::Percentage(p) => format!("{}%", p.get()),
253            Self::Fill => "fill".to_string(),
254            Self::FillMinimum => "fill-min".to_string(),
255            Self::RootPercentage(p) => format!("{}% of root", p.get()),
256            Self::Flex(f) => format!("flex({})", f.get()),
257        }
258    }
259
260    pub(crate) fn eval(
261        &self,
262        parent: f32,
263        available_parent: f32,
264        parent_margin: f32,
265        root: f32,
266        phase: Phase,
267    ) -> Option<f32> {
268        match self {
269            Self::Pixels(px) => Some(px.get() + parent_margin),
270            Self::Percentage(per) => Some(parent / 100.0 * per.get()),
271            Self::Fill => Some(available_parent),
272            Self::RootPercentage(per) => Some(root / 100.0 * per.get()),
273            Self::Flex(_) | Self::FillMinimum if phase == Phase::Final => Some(available_parent),
274            Self::Fn(f) => f.call(SizeFnContext {
275                parent,
276                available_parent,
277                parent_margin,
278                root,
279                phase,
280            }),
281            _ => None,
282        }
283    }
284
285    #[allow(clippy::too_many_arguments)]
286    pub(crate) fn min_max(
287        &self,
288        value: f32,
289        parent_value: f32,
290        available_parent_value: f32,
291        single_margin: f32,
292        margin: f32,
293        minimum: &Self,
294        maximum: &Self,
295        root_value: f32,
296        phase: Phase,
297    ) -> f32 {
298        let value = self
299            .eval(
300                parent_value,
301                available_parent_value,
302                margin,
303                root_value,
304                phase,
305            )
306            .unwrap_or(value + margin);
307
308        let minimum_value = minimum
309            .eval(
310                parent_value,
311                available_parent_value,
312                margin,
313                root_value,
314                phase,
315            )
316            .map(|v| v + single_margin);
317        let maximum_value = maximum.eval(
318            parent_value,
319            available_parent_value,
320            margin,
321            root_value,
322            phase,
323        );
324
325        let mut final_value = value;
326
327        if let Some(minimum_value) = minimum_value
328            && minimum_value > final_value
329        {
330            final_value = minimum_value;
331        }
332
333        if let Some(maximum_value) = maximum_value
334            && final_value > maximum_value
335        {
336            final_value = maximum_value;
337        }
338
339        final_value
340    }
341
342    pub(crate) fn most_fitting_size<'a>(&self, size: &'a f32, available_size: &'a f32) -> &'a f32 {
343        match self {
344            Self::Inner => available_size,
345            _ => size,
346        }
347    }
348}
349
350impl Scaled for Size {
351    fn scale(&mut self, scale_factor: f32) {
352        if let Self::Pixels(s) = self {
353            *s *= scale_factor;
354        }
355    }
356}