taffy/style/
dimension.rs

1//! Style types for representing lengths / sizes
2use super::CompactLength;
3use crate::geometry::Rect;
4use crate::style_helpers::{FromLength, FromPercent, TaffyAuto, TaffyZero};
5#[cfg(feature = "parse")]
6use crate::util::parse::{from_str_from_css, parse_css_str_entirely, CssParseResult, FromCss, Parser, Token};
7
8/// A unit of linear measurement
9///
10/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
11#[derive(Copy, Clone, PartialEq, Debug)]
12#[cfg_attr(feature = "serde", derive(Serialize))]
13pub struct LengthPercentage(pub(crate) CompactLength);
14impl TaffyZero for LengthPercentage {
15    const ZERO: Self = Self(CompactLength::ZERO);
16}
17impl FromLength for LengthPercentage {
18    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
19        Self::length(value.into())
20    }
21}
22impl FromPercent for LengthPercentage {
23    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
24        Self::percent(value.into())
25    }
26}
27
28#[cfg(feature = "parse")]
29impl FromCss for LengthPercentage {
30    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
31        match parser.next()?.clone() {
32            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
33            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
34            token => Err(parser.new_unexpected_token_error(token))?,
35        }
36    }
37}
38#[cfg(feature = "parse")]
39from_str_from_css!(LengthPercentage);
40
41impl LengthPercentage {
42    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
43    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
44    #[inline(always)]
45    pub const fn length(val: f32) -> Self {
46        Self(CompactLength::length(val))
47    }
48
49    /// A percentage length relative to the size of the containing block.
50    ///
51    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
52    #[inline(always)]
53    pub const fn percent(val: f32) -> Self {
54        Self(CompactLength::percent(val))
55    }
56
57    /// A `calc()` value. The value passed here is treated as an opaque handle to
58    /// the actual calc representation and may be a pointer, index, etc.
59    ///
60    /// The low 3 bits are used as a tag value and will be returned as 0.
61    #[inline(always)]
62    #[cfg(feature = "calc")]
63    pub fn calc(ptr: *const ()) -> Self {
64        Self(CompactLength::calc(ptr))
65    }
66
67    /// Create a LengthPercentage from a raw `CompactLength`.
68    /// # Safety
69    /// CompactLength must represent a valid variant for LengthPercentage
70    #[allow(unsafe_code)]
71    pub const unsafe fn from_raw(val: CompactLength) -> Self {
72        Self(val)
73    }
74
75    /// Get the underlying `CompactLength` representation of the value
76    pub const fn into_raw(self) -> CompactLength {
77        self.0
78    }
79}
80
81#[cfg(feature = "serde")]
82impl<'de> serde::Deserialize<'de> for LengthPercentage {
83    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84    where
85        D: serde::Deserializer<'de>,
86    {
87        let inner = CompactLength::deserialize(deserializer)?;
88        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
89        if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG) {
90            Ok(Self(inner))
91        } else {
92            Err(serde::de::Error::custom("Invalid tag"))
93        }
94    }
95}
96
97/// A unit of linear measurement
98///
99/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
100#[derive(Copy, Clone, PartialEq, Debug)]
101#[cfg_attr(feature = "serde", derive(Serialize))]
102pub struct LengthPercentageAuto(pub(crate) CompactLength);
103impl TaffyZero for LengthPercentageAuto {
104    const ZERO: Self = Self(CompactLength::ZERO);
105}
106impl TaffyAuto for LengthPercentageAuto {
107    const AUTO: Self = Self(CompactLength::AUTO);
108}
109impl FromLength for LengthPercentageAuto {
110    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
111        Self::length(value.into())
112    }
113}
114impl FromPercent for LengthPercentageAuto {
115    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
116        Self::percent(value.into())
117    }
118}
119impl From<LengthPercentage> for LengthPercentageAuto {
120    fn from(input: LengthPercentage) -> Self {
121        Self(input.0)
122    }
123}
124
125#[cfg(feature = "parse")]
126impl FromCss for LengthPercentageAuto {
127    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
128        match parser.next()?.clone() {
129            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
130            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
131            Token::Ident(ident) if ident == "auto" => Ok(Self::auto()),
132            token => Err(parser.new_unexpected_token_error(token))?,
133        }
134    }
135}
136#[cfg(feature = "parse")]
137from_str_from_css!(LengthPercentageAuto);
138
139impl LengthPercentageAuto {
140    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
141    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
142    #[inline(always)]
143    pub const fn length(val: f32) -> Self {
144        Self(CompactLength::length(val))
145    }
146
147    /// A percentage length relative to the size of the containing block.
148    ///
149    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
150    #[inline(always)]
151    pub const fn percent(val: f32) -> Self {
152        Self(CompactLength::percent(val))
153    }
154
155    /// The dimension should be automatically computed according to algorithm-specific rules
156    /// regarding the default size of boxes.
157    #[inline(always)]
158    pub const fn auto() -> Self {
159        Self(CompactLength::auto())
160    }
161
162    /// A `calc()` value. The value passed here is treated as an opaque handle to
163    /// the actual calc representation and may be a pointer, index, etc.
164    ///
165    /// The low 3 bits are used as a tag value and will be returned as 0.
166    #[inline]
167    #[cfg(feature = "calc")]
168    pub fn calc(ptr: *const ()) -> Self {
169        Self(CompactLength::calc(ptr))
170    }
171
172    /// Create a LengthPercentageAuto from a raw `CompactLength`.
173    /// # Safety
174    /// CompactLength must represent a valid variant for LengthPercentageAuto
175    #[allow(unsafe_code)]
176    pub const unsafe fn from_raw(val: CompactLength) -> Self {
177        Self(val)
178    }
179
180    /// Get the underlying `CompactLength` representation of the value
181    pub const fn into_raw(self) -> CompactLength {
182        self.0
183    }
184
185    /// Returns:
186    ///   - Some(length) for Length variants
187    ///   - Some(resolved) using the provided context for Percent variants
188    ///   - None for Auto variants
189    #[inline(always)]
190    pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(*const (), f32) -> f32) -> Option<f32> {
191        match self.0.tag() {
192            CompactLength::LENGTH_TAG => Some(self.0.value()),
193            CompactLength::PERCENT_TAG => Some(context * self.0.value()),
194            CompactLength::AUTO_TAG => None,
195            #[cfg(feature = "calc")]
196            _ if self.0.is_calc() => Some(calc_resolver(self.0.calc_value(), context)),
197            _ => unreachable!("LengthPercentageAuto values cannot be constructed with other tags"),
198        }
199    }
200
201    /// Returns true if value is LengthPercentageAuto::Auto
202    #[inline(always)]
203    pub fn is_auto(self) -> bool {
204        self.0.is_auto()
205    }
206}
207
208#[cfg(feature = "serde")]
209impl<'de> serde::Deserialize<'de> for LengthPercentageAuto {
210    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
211    where
212        D: serde::Deserializer<'de>,
213    {
214        let inner = CompactLength::deserialize(deserializer)?;
215        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
216        if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG | CompactLength::AUTO_TAG) {
217            Ok(Self(inner))
218        } else {
219            Err(serde::de::Error::custom("Invalid tag"))
220        }
221    }
222}
223
224/// A unit of linear measurement
225///
226/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
227#[derive(Copy, Clone, PartialEq, Debug)]
228#[cfg_attr(feature = "serde", derive(Serialize))]
229pub struct Dimension(pub(crate) CompactLength);
230impl TaffyZero for Dimension {
231    const ZERO: Self = Self(CompactLength::ZERO);
232}
233impl TaffyAuto for Dimension {
234    const AUTO: Self = Self(CompactLength::AUTO);
235}
236impl FromLength for Dimension {
237    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
238        Self::length(value.into())
239    }
240}
241impl FromPercent for Dimension {
242    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
243        Self::percent(value.into())
244    }
245}
246impl From<LengthPercentage> for Dimension {
247    fn from(input: LengthPercentage) -> Self {
248        Self(input.0)
249    }
250}
251impl From<LengthPercentageAuto> for Dimension {
252    fn from(input: LengthPercentageAuto) -> Self {
253        Self(input.0)
254    }
255}
256
257#[cfg(feature = "parse")]
258impl FromCss for Dimension {
259    fn from_css<'i>(parser: &mut Parser<'i, '_>) -> CssParseResult<'i, Self> {
260        match parser.next()?.clone() {
261            Token::Percentage { unit_value, .. } => Ok(Self::percent(unit_value)),
262            Token::Dimension { unit, value, .. } if unit == "px" => Ok(Self::length(value)),
263            Token::Ident(ident) if ident == "auto" => Ok(Self::auto()),
264            token => Err(parser.new_unexpected_token_error(token))?,
265        }
266    }
267}
268#[cfg(feature = "parse")]
269from_str_from_css!(Dimension);
270
271impl Dimension {
272    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
273    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
274    #[inline(always)]
275    pub const fn length(val: f32) -> Self {
276        Self(CompactLength::length(val))
277    }
278
279    /// A percentage length relative to the size of the containing block.
280    ///
281    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
282    #[inline(always)]
283    pub const fn percent(val: f32) -> Self {
284        Self(CompactLength::percent(val))
285    }
286
287    /// The dimension should be automatically computed according to algorithm-specific rules
288    /// regarding the default size of boxes.
289    #[inline(always)]
290    pub const fn auto() -> Self {
291        Self(CompactLength::auto())
292    }
293
294    /// A `calc()` value. The value passed here is treated as an opaque handle to
295    /// the actual calc representation and may be a pointer, index, etc.
296    ///
297    /// The low 3 bits are used as a tag value and will be returned as 0.
298    #[inline]
299    #[cfg(feature = "calc")]
300    pub fn calc(ptr: *const ()) -> Self {
301        Self(CompactLength::calc(ptr))
302    }
303
304    /// Create a LengthPercentageAuto from a raw `CompactLength`.
305    /// # Safety
306    /// CompactLength must represent a valid variant for LengthPercentageAuto
307    #[allow(unsafe_code)]
308    pub const unsafe fn from_raw(val: CompactLength) -> Self {
309        Self(val)
310    }
311
312    /// Get the underlying `CompactLength` representation of the value
313    pub const fn into_raw(self) -> CompactLength {
314        self.0
315    }
316
317    /// Get Length value if value is Length variant
318    #[cfg(feature = "grid")]
319    pub fn into_option(self) -> Option<f32> {
320        match self.0.tag() {
321            CompactLength::LENGTH_TAG => Some(self.0.value()),
322            _ => None,
323        }
324    }
325    /// Returns true if value is Auto
326    #[inline(always)]
327    pub fn is_auto(self) -> bool {
328        self.0.is_auto()
329    }
330
331    /// Get the raw `CompactLength` tag
332    pub fn tag(self) -> usize {
333        self.0.tag()
334    }
335
336    /// Get the raw `CompactLength` value for non-calc variants that have a numeric parameter
337    pub fn value(self) -> f32 {
338        self.0.value()
339    }
340}
341
342#[cfg(feature = "serde")]
343impl<'de> serde::Deserialize<'de> for Dimension {
344    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
345    where
346        D: serde::Deserializer<'de>,
347    {
348        let inner = CompactLength::deserialize(deserializer)?;
349        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
350        if matches!(inner.tag(), CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG | CompactLength::AUTO_TAG) {
351            Ok(Self(inner))
352        } else {
353            Err(serde::de::Error::custom("Invalid tag"))
354        }
355    }
356}
357
358impl Rect<Dimension> {
359    /// Create a new Rect with length values
360    #[must_use]
361    pub const fn from_length(start: f32, end: f32, top: f32, bottom: f32) -> Self {
362        Rect {
363            left: Dimension(CompactLength::length(start)),
364            right: Dimension(CompactLength::length(end)),
365            top: Dimension(CompactLength::length(top)),
366            bottom: Dimension(CompactLength::length(bottom)),
367        }
368    }
369
370    /// Create a new Rect with percentage values
371    #[must_use]
372    pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
373        Rect {
374            left: Dimension(CompactLength::percent(start)),
375            right: Dimension(CompactLength::percent(end)),
376            top: Dimension(CompactLength::percent(top)),
377            bottom: Dimension(CompactLength::percent(bottom)),
378        }
379    }
380}