⎇ 527e99e059c327b6c6be5a24c210dcba3e658fdf
src/style/dimension.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
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}