gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 10TOML 3CSS 1350 Sutichaykuna

⎇ jk/positioning src/style/dimension.rs Rust · 497 siq'ikuna · 17.0 KB Ñawpa willay Pi imata llamkarqan ch'uya

fit content 303768a02 · Jonathan Kelley ·

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