tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1118HTML 1044Markdown 12TOML 94781 Sutichaykuna

⎇ 177d645441e9256129192fdee0cab5d74540f041 src/style/dimension.rs Rust · 504 siq'ikuna · 17.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

Reinstate 1.65 MSRV (#781) 177d64544 · Nico Burns ·

1//! Style types for representing lengths / sizes
2use crate::geometry::Rect;
3use crate::style_helpers::{
4    FromFr, FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
5};
6
7// Note: these two functions are copied directly from the std (core) library. But by duplicating them
8// here we can reduce MSRV from 1.83 all the way down to 1.65 while retaining const constructors.
9
10/// Raw transmutation from `f32` to `u32`.
11const fn to_bits(val: f32) -> u32 {
12    // SAFETY: `u32` is a plain old datatype so we can always transmute to it.
13    #[allow(unsafe_code)]
14    unsafe {
15        core::mem::transmute(val)
16    }
17}
18/// Raw transmutation from `u32` to `f32`.
19const fn from_bits(v: u32) -> f32 {
20    // SAFETY: `u32` is a plain old datatype so we can always transmute from it.
21    #[allow(unsafe_code)]
22    unsafe {
23        core::mem::transmute(v)
24    }
25}
26
27/// A representation of a length as a compact 64-bit tagged pointer
28#[derive(Copy, Clone, PartialEq, Debug)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct CompactLength(u64);
31
32impl CompactLength {
33    // Masks
34
35    /// The low byte (8 bits)
36    pub const TAG_MASK: u64 = 0b11111111;
37    /// The low 3 bits
38    pub const CALC_TAG_MASK: u64 = 0b111;
39    /// The high 63 bits
40    pub const CALC_PTR_MASK: u64 = u64::MAX ^ 0b111;
41
42    // Primary tags
43
44    /// The tag indicating a calc() value
45    pub const CALC_TAG: u64 = 0b000;
46    /// The tag indicating a length value
47    pub const LENGTH_TAG: u64 = 0b0000_0001;
48    /// The tag indicating a percentage value
49    pub const PERCENT_TAG: u64 = 0b0000_0010;
50    /// The tag indicating an auto value
51    pub const AUTO_TAG: u64 = 0b0000_0011;
52    /// The tag indicating an fr value
53    pub const FR_TAG: u64 = 0b0000_0100;
54    /// The tag indicating a min-content value
55    pub const MIN_CONTENT_TAG: u64 = 0b00000111;
56    /// The tag indicating a max-content value
57    pub const MAX_CONTENT_TAG: u64 = 0b00001111;
58    /// The tag indicating a fit-content value with px limit
59    pub const FIT_CONTENT_PX_TAG: u64 = 0b00010111;
60    /// The tag indicating a fit-content value with percent limit
61    pub const FIT_CONTENT_PERCENT_TAG: u64 = 0b00011111;
62}
63
64impl CompactLength {
65    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
66    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
67    #[inline(always)]
68    pub const fn length(val: f32) -> Self {
69        Self(((to_bits(val) as u64) << 32) | Self::LENGTH_TAG)
70    }
71
72    /// A percentage length relative to the size of the containing block.
73    ///
74    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
75    #[inline(always)]
76    pub const fn percent(val: f32) -> Self {
77        Self(((to_bits(val) as u64) << 32) | Self::PERCENT_TAG)
78    }
79
80    /// A `calc()` value. The value passed here is treated as an opaque handle to
81    /// the actual calc representation and may be a pointer, index, etc.
82    ///
83    /// The low 3 bits are used as a tag value and will be returned as 0.
84    #[inline]
85    pub fn calc(ptr: *const ()) -> Self {
86        assert_ne!(ptr as u64, 0);
87        assert_eq!(ptr as u64 & 0b111, 0);
88        Self(ptr as u64 | Self::CALC_TAG)
89    }
90
91    /// The dimension should be automatically computed according to algorithm-specific rules
92    /// regarding the default size of boxes.
93    #[inline(always)]
94    pub const fn auto() -> Self {
95        Self(Self::AUTO_TAG)
96    }
97
98    /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
99    /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
100    /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
101    #[inline(always)]
102    pub const fn fr(val: f32) -> Self {
103        Self(((to_bits(val) as u64) << 32) | Self::FR_TAG)
104    }
105
106    /// The size should be the "min-content" size.
107    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
108    #[inline(always)]
109    pub const fn min_content() -> Self {
110        Self(Self::MIN_CONTENT_TAG)
111    }
112
113    /// The size should be the "max-content" size.
114    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
115    #[inline(always)]
116    pub const fn max_content() -> Self {
117        Self(Self::MAX_CONTENT_TAG)
118    }
119
120    /// The size should be computed according to the "fit content" formula:
121    ///    `max(min_content, min(max_content, limit))`
122    /// where:
123    ///    - `min_content` is the [min-content](Self::min_content) size
124    ///    - `max_content` is the [max-content](Self::max_content) size
125    ///    - `limit` is a LENGTH value passed to this function
126    ///
127    /// The effect of this is that the item takes the size of `limit` clamped
128    /// by the min-content and max-content sizes.
129    #[inline(always)]
130    pub const fn fit_content_px(limit: f32) -> Self {
131        Self(((to_bits(limit) as u64) << 32) | Self::FIT_CONTENT_PX_TAG)
132    }
133
134    /// The size should be computed according to the "fit content" formula:
135    ///    `max(min_content, min(max_content, limit))`
136    /// where:
137    ///    - `min_content` is the [min-content](Self::min_content) size
138    ///    - `max_content` is the [max-content](Self::max_content) size
139    ///    - `limit` is a PERCENTAGE value passed to this function
140    ///
141    /// The effect of this is that the item takes the size of `limit` clamped
142    /// by the min-content and max-content sizes.
143    #[inline(always)]
144    pub const fn fit_content_percent(limit: f32) -> Self {
145        Self(((to_bits(limit) as u64) << 32) | Self::FIT_CONTENT_PERCENT_TAG)
146    }
147
148    /// Get the primary tag
149    #[inline(always)]
150    pub const fn tag(self) -> u64 {
151        self.0 & Self::TAG_MASK
152    }
153
154    /// Get the numeric value associated with the `CompactLength`
155    /// (e.g. the pixel value for a LENGTH variant)
156    #[inline(always)]
157    pub const fn value(self) -> f32 {
158        from_bits((self.0 >> 32) as u32)
159    }
160
161    /// Get the numeric value associated with the `CompactLength`
162    /// (e.g. the pixel value for a LENGTH variant)
163    #[inline(always)]
164    pub const fn calc_value(self) -> u64 {
165        self.0
166    }
167
168    /// Returns true if the value is 0 px
169    #[inline(always)]
170    pub const fn is_calc(self) -> bool {
171        self.0 & Self::CALC_TAG_MASK == 0
172    }
173
174    /// Returns true if the value is 0 px
175    #[inline(always)]
176    pub const fn is_zero(self) -> bool {
177        self.0 == Self::ZERO.0
178    }
179
180    /// Returns true if the value is a length or percentage value
181    #[inline(always)]
182    pub const fn is_length_or_percentage(self) -> bool {
183        matches!(self.tag(), Self::LENGTH_TAG | Self::PERCENT_TAG)
184    }
185
186    /// Returns true if the value is auto
187    #[inline(always)]
188    pub const fn is_auto(self) -> bool {
189        self.tag() == Self::AUTO_TAG
190    }
191
192    /// Returns true if the value is min-content
193    #[inline(always)]
194    pub const fn is_min_content(self) -> bool {
195        matches!(self.tag(), Self::MIN_CONTENT_TAG)
196    }
197
198    /// Returns true if the value is max-content
199    #[inline(always)]
200    pub const fn is_max_content(self) -> bool {
201        matches!(self.tag(), Self::MAX_CONTENT_TAG)
202    }
203
204    /// Returns true if the value is a fit-content(...) value
205    #[inline(always)]
206    pub const fn is_fit_content(self) -> bool {
207        matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
208    }
209
210    /// Returns true if the value is max-content or a fit-content(...) value
211    #[inline(always)]
212    pub const fn is_max_or_fit_content(self) -> bool {
213        matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
214    }
215
216    /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
217    /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
218    /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
219    #[inline(always)]
220    pub fn is_max_content_alike(&self) -> bool {
221        matches!(
222            self.tag(),
223            CompactLength::AUTO_TAG
224                | CompactLength::MAX_CONTENT_TAG
225                | CompactLength::FIT_CONTENT_PX_TAG
226                | CompactLength::FIT_CONTENT_PERCENT_TAG
227        )
228    }
229
230    /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
231    #[inline(always)]
232    pub fn is_min_or_max_content(&self) -> bool {
233        matches!(self.tag(), Self::MIN_CONTENT_TAG | Self::MAX_CONTENT_TAG)
234    }
235
236    /// Returns true if the value is auto, min-content, max-content, or fit-content(...)
237    #[inline(always)]
238    pub const fn is_intrinsic(self) -> bool {
239        matches!(
240            self.tag(),
241            Self::AUTO_TAG
242                | Self::MIN_CONTENT_TAG
243                | Self::MAX_CONTENT_TAG
244                | Self::FIT_CONTENT_PX_TAG
245                | Self::FIT_CONTENT_PERCENT_TAG
246        )
247    }
248
249    /// Returns true if the value is and fr value
250    #[inline(always)]
251    pub const fn is_fr(self) -> bool {
252        self.tag() == Self::FR_TAG
253    }
254
255    /// Whether the track sizing functions depends on the size of the parent node
256    #[inline(always)]
257    pub const fn uses_percentage(self) -> bool {
258        // TODO: handle calc() values
259        matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc()
260    }
261
262    /// Resolve percentage values against the passed parent_size, returning Some(value)
263    /// Non-percentage values always return None.
264    #[inline(always)]
265    pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
266        match self.tag() {
267            CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
268            _ if self.is_calc() => Some(calc_resolver(self.0, parent_size)),
269            _ => None,
270        }
271    }
272}
273
274impl TaffyZero for CompactLength {
275    const ZERO: Self = Self::length(0.0);
276}
277impl TaffyAuto for CompactLength {
278    const AUTO: Self = Self::auto();
279}
280impl TaffyMinContent for CompactLength {
281    const MIN_CONTENT: Self = Self::min_content();
282}
283impl TaffyMaxContent for CompactLength {
284    const MAX_CONTENT: Self = Self::max_content();
285}
286impl FromLength for CompactLength {
287    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
288        Self::length(value.into())
289    }
290}
291impl FromPercent for CompactLength {
292    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
293        Self::percent(value.into())
294    }
295}
296impl FromFr for CompactLength {
297    fn from_fr<Input: Into<f32> + Copy>(value: Input) -> Self {
298        Self::fr(value.into())
299    }
300}
301impl TaffyFitContent for CompactLength {
302    fn fit_content(lp: LengthPercentage) -> Self {
303        let value = lp.0.value();
304        match lp.0.tag() {
305            Self::LENGTH_TAG => Self::fit_content_px(value),
306            Self::PERCENT_TAG => Self::fit_content_percent(value),
307            _ => unreachable!(),
308        }
309    }
310}
311
312/// A unit of linear measurement
313///
314/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
315#[derive(Copy, Clone, PartialEq, Debug)]
316#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
317pub struct LengthPercentage(pub(crate) CompactLength);
318impl TaffyZero for LengthPercentage {
319    const ZERO: Self = Self(CompactLength::ZERO);
320}
321impl FromLength for LengthPercentage {
322    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
323        Self(CompactLength::from_length(value))
324    }
325}
326impl FromPercent for LengthPercentage {
327    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
328        Self(CompactLength::from_percent(percent))
329    }
330}
331impl LengthPercentage {
332    /// Get the underlying `CompactLength` representation of the value
333    pub fn into_raw(self) -> CompactLength {
334        self.0
335    }
336
337    /// Create a LengthPercentage from a raw `CompactLength`.
338    /// # Safety
339    /// CompactLength must represent a valid variant for LengthPercentage
340    #[allow(unsafe_code)]
341    pub unsafe fn from_raw(val: CompactLength) -> Self {
342        Self(val)
343    }
344}
345
346/// A unit of linear measurement
347///
348/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
349#[derive(Copy, Clone, PartialEq, Debug)]
350#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
351pub struct LengthPercentageAuto(pub(crate) CompactLength);
352impl TaffyZero for LengthPercentageAuto {
353    const ZERO: Self = Self(CompactLength::ZERO);
354}
355impl TaffyAuto for LengthPercentageAuto {
356    const AUTO: Self = Self(CompactLength::AUTO);
357}
358impl FromLength for LengthPercentageAuto {
359    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
360        Self(CompactLength::from_length(value))
361    }
362}
363impl FromPercent for LengthPercentageAuto {
364    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
365        Self(CompactLength::from_percent(percent))
366    }
367}
368impl From<LengthPercentage> for LengthPercentageAuto {
369    fn from(input: LengthPercentage) -> Self {
370        Self(input.0)
371    }
372}
373
374impl LengthPercentageAuto {
375    /// Returns:
376    ///   - Some(length) for Length variants
377    ///   - Some(resolved) using the provided context for Percent variants
378    ///   - None for Auto variants
379    #[inline(always)]
380    pub fn resolve_to_option(self, context: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
381        match self.0.tag() {
382            CompactLength::LENGTH_TAG => Some(self.0.value()),
383            CompactLength::PERCENT_TAG => Some(context * self.0.value()),
384            CompactLength::AUTO_TAG => None,
385            _ if self.0.is_calc() => Some(calc_resolver(self.0.calc_value(), context)),
386            _ => unreachable!("LengthPercentageAuto values cannot be constructed with other tags"),
387        }
388    }
389
390    /// Returns true if value is LengthPercentageAuto::Auto
391    #[inline(always)]
392    pub fn is_auto(self) -> bool {
393        self.0.is_auto()
394    }
395
396    /// Get the underlying `CompactLength` representation of the value
397    pub fn into_raw(self) -> CompactLength {
398        self.0
399    }
400
401    /// Create a LengthPercentageAuto from a raw `CompactLength`.
402    /// # Safety
403    /// CompactLength must represent a valid variant for LengthPercentageAuto
404    #[allow(unsafe_code)]
405    pub unsafe fn from_raw(val: CompactLength) -> Self {
406        Self(val)
407    }
408}
409
410/// A unit of linear measurement
411///
412/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`](crate::geometry::Size).
413#[derive(Copy, Clone, PartialEq, Debug)]
414#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
415pub struct Dimension(pub(crate) CompactLength);
416impl TaffyZero for Dimension {
417    const ZERO: Self = Self(CompactLength::ZERO);
418}
419impl TaffyAuto for Dimension {
420    const AUTO: Self = Self(CompactLength::AUTO);
421}
422impl FromLength for Dimension {
423    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
424        Self(CompactLength::from_length(value))
425    }
426}
427impl FromPercent for Dimension {
428    fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
429        Self(CompactLength::from_percent(percent))
430    }
431}
432impl From<LengthPercentage> for Dimension {
433    fn from(input: LengthPercentage) -> Self {
434        Self(input.0)
435    }
436}
437impl From<LengthPercentageAuto> for Dimension {
438    fn from(input: LengthPercentageAuto) -> Self {
439        Self(input.0)
440    }
441}
442
443impl Dimension {
444    /// Get Length value if value is Length variant
445    #[cfg(feature = "grid")]
446    pub fn into_option(self) -> Option<f32> {
447        match self.0.tag() {
448            CompactLength::LENGTH_TAG => Some(self.0.value()),
449            _ => None,
450        }
451    }
452    /// Returns true if value is Auto
453    #[inline(always)]
454    pub fn is_auto(self) -> bool {
455        self.0.is_auto()
456    }
457
458    /// Get the raw `CompactLength` tag
459    pub fn tag(self) -> u64 {
460        self.0.tag()
461    }
462
463    /// Get the raw `CompactLength` value for non-calc variants that have a numeric parameter
464    pub fn value(self) -> f32 {
465        self.0.value()
466    }
467
468    /// Get the underlying `CompactLength` representation of the value
469    pub fn into_raw(self) -> CompactLength {
470        self.0
471    }
472
473    /// Create a Dimension from a raw `CompactLength`.
474    /// # Safety
475    ///  CompactLength must represent a valid variant for Dimension
476    #[allow(unsafe_code)]
477    pub unsafe fn from_raw(val: CompactLength) -> Self {
478        Self(val)
479    }
480}
481
482impl Rect<Dimension> {
483    /// Create a new Rect with length values
484    #[must_use]
485    pub const fn from_length(start: f32, end: f32, top: f32, bottom: f32) -> Self {
486        Rect {
487            left: Dimension(CompactLength::length(start)),
488            right: Dimension(CompactLength::length(end)),
489            top: Dimension(CompactLength::length(top)),
490            bottom: Dimension(CompactLength::length(bottom)),
491        }
492    }
493
494    /// Create a new Rect with percentage values
495    #[must_use]
496    pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
497        Rect {
498            left: Dimension(CompactLength::percent(start)),
499            right: Dimension(CompactLength::percent(end)),
500            top: Dimension(CompactLength::percent(top)),
501            bottom: Dimension(CompactLength::percent(bottom)),
502        }
503    }
504}