taffy/style/
available_space.rs

1//! Style type for representing available space as a sizing constraint
2use crate::{
3    prelude::{FromLength, TaffyMaxContent, TaffyMinContent, TaffyZero},
4    sys::abs,
5    Size,
6};
7
8#[cfg(feature = "parse")]
9use crate::util::parse::{from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, Parser, Token};
10
11/// The amount of space available to a node in a given axis
12/// <https://www.w3.org/TR/css-sizing-3/#available>
13#[derive(Copy, Clone, Debug, PartialEq)]
14#[cfg_attr(feature = "serde", derive(Serialize))]
15pub enum AvailableSpace {
16    /// The amount of space available is the specified number of pixels
17    Definite(f32),
18    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
19    MinContent,
20    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
21    MaxContent,
22}
23impl TaffyZero for AvailableSpace {
24    const ZERO: Self = Self::Definite(0.0);
25}
26impl TaffyMaxContent for AvailableSpace {
27    const MAX_CONTENT: Self = Self::MaxContent;
28}
29impl TaffyMinContent for AvailableSpace {
30    const MIN_CONTENT: Self = Self::MinContent;
31}
32impl FromLength for AvailableSpace {
33    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
34        Self::Definite(value.into())
35    }
36}
37
38#[cfg(feature = "parse")]
39impl FromCss for AvailableSpace {
40    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
41        match parser.next()?.clone() {
42            Token::Number { value, .. } if value >= 0.0 => Ok(Self::Definite(value)),
43            Token::Dimension { value, .. } if value >= 0.0 => Ok(Self::Definite(value)),
44            Token::Ident(ident) if ident == "max-content" => Ok(Self::MaxContent),
45            Token::Ident(ident) if ident == "min-content" => Ok(Self::MinContent),
46            token => Err(parser.new_unexpected_token_error(token))?,
47        }
48    }
49}
50#[cfg(feature = "parse")]
51from_str_from_css!(AvailableSpace);
52
53impl AvailableSpace {
54    /// Returns true for definite values, else false
55    pub const fn is_definite(self) -> bool {
56        matches!(self, AvailableSpace::Definite(_))
57    }
58
59    /// Convert to Option
60    /// Definite values become Some(value). Constraints become None.
61    pub const fn into_option(self) -> Option<f32> {
62        match self {
63            AvailableSpace::Definite(value) => Some(value),
64            _ => None,
65        }
66    }
67
68    /// Return the definite value or a default value
69    pub fn unwrap_or(self, default: f32) -> f32 {
70        self.into_option().unwrap_or(default)
71    }
72
73    /// Return the definite value. Panic is the value is not definite.
74    #[track_caller]
75    pub fn unwrap(self) -> f32 {
76        self.into_option().unwrap()
77    }
78
79    /// Return self if definite or a default value
80    pub fn or(self, default: AvailableSpace) -> AvailableSpace {
81        match self {
82            AvailableSpace::Definite(_) => self,
83            _ => default,
84        }
85    }
86
87    /// Return self if definite or a the result of the default value callback
88    pub fn or_else(self, default_cb: impl FnOnce() -> AvailableSpace) -> AvailableSpace {
89        match self {
90            AvailableSpace::Definite(_) => self,
91            _ => default_cb(),
92        }
93    }
94
95    /// Return the definite value or the result of the default value callback
96    pub fn unwrap_or_else(self, default_cb: impl FnOnce() -> f32) -> f32 {
97        self.into_option().unwrap_or_else(default_cb)
98    }
99
100    /// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
101    pub fn maybe_set(self, value: Option<f32>) -> AvailableSpace {
102        match value {
103            Some(value) => AvailableSpace::Definite(value),
104            None => self,
105        }
106    }
107
108    /// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
109    pub fn map_definite_value(self, map_function: impl FnOnce(f32) -> f32) -> AvailableSpace {
110        match self {
111            AvailableSpace::Definite(value) => AvailableSpace::Definite(map_function(value)),
112            _ => self,
113        }
114    }
115
116    /// Compute free_space given the passed used_space
117    pub fn compute_free_space(&self, used_space: f32) -> f32 {
118        match self {
119            AvailableSpace::MaxContent => f32::INFINITY,
120            AvailableSpace::MinContent => 0.0,
121            AvailableSpace::Definite(available_space) => available_space - used_space,
122        }
123    }
124
125    /// Compare equality with another AvailableSpace, treating definite values
126    /// that are within f32::EPSILON of each other as equal
127    pub fn is_roughly_equal(self, other: AvailableSpace) -> bool {
128        use AvailableSpace::*;
129        match (self, other) {
130            (Definite(a), Definite(b)) => abs(a - b) < f32::EPSILON,
131            (MinContent, MinContent) => true,
132            (MaxContent, MaxContent) => true,
133            _ => false,
134        }
135    }
136}
137
138impl From<f32> for AvailableSpace {
139    fn from(value: f32) -> Self {
140        Self::Definite(value)
141    }
142}
143
144impl From<Option<f32>> for AvailableSpace {
145    fn from(option: Option<f32>) -> Self {
146        match option {
147            Some(value) => Self::Definite(value),
148            None => Self::MaxContent,
149        }
150    }
151}
152
153impl Size<AvailableSpace> {
154    /// Convert `Size<AvailableSpace>` into `Size<Option<f32>>`
155    pub fn into_options(self) -> Size<Option<f32>> {
156        Size { width: self.width.into_option(), height: self.height.into_option() }
157    }
158
159    /// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
160    pub fn maybe_set(self, value: Size<Option<f32>>) -> Size<AvailableSpace> {
161        Size { width: self.width.maybe_set(value.width), height: self.height.maybe_set(value.height) }
162    }
163}