Move CompactLength into it's own module (#787)
1//! A tagged-pointer abstraction that allows size styles in Taffy to be represented
2//! in just 64 bits. Wrapped by types in the `super::dimension` and `super::grid` modules.
3use super::LengthPercentage;
4use crate::style_helpers::{
5 FromFr, FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
6};
7use compat::{f32_from_bits, f32_to_bits};
8
9/// A representation of a length as a compact 64-bit tagged pointer
10#[derive(Copy, Clone, PartialEq, Debug)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub struct CompactLength(u64);
13
14impl CompactLength {
15 // Masks
16
17 /// The low byte (8 bits)
18 pub const TAG_MASK: u64 = 0b11111111;
19 /// The low 3 bits
20 pub const CALC_TAG_MASK: u64 = 0b111;
21 /// The high 63 bits
22 pub const CALC_PTR_MASK: u64 = u64::MAX ^ 0b111;
23
24 // Primary tags
25
26 /// The tag indicating a calc() value
27 pub const CALC_TAG: u64 = 0b000;
28 /// The tag indicating a length value
29 pub const LENGTH_TAG: u64 = 0b0000_0001;
30 /// The tag indicating a percentage value
31 pub const PERCENT_TAG: u64 = 0b0000_0010;
32 /// The tag indicating an auto value
33 pub const AUTO_TAG: u64 = 0b0000_0011;
34 /// The tag indicating an fr value
35 pub const FR_TAG: u64 = 0b0000_0100;
36 /// The tag indicating a min-content value
37 pub const MIN_CONTENT_TAG: u64 = 0b00000111;
38 /// The tag indicating a max-content value
39 pub const MAX_CONTENT_TAG: u64 = 0b00001111;
40 /// The tag indicating a fit-content value with px limit
41 pub const FIT_CONTENT_PX_TAG: u64 = 0b00010111;
42 /// The tag indicating a fit-content value with percent limit
43 pub const FIT_CONTENT_PERCENT_TAG: u64 = 0b00011111;
44}
45
46impl CompactLength {
47 /// An absolute length in some abstract units. Users of Taffy may define what they correspond
48 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
49 #[inline(always)]
50 pub const fn length(val: f32) -> Self {
51 Self(((f32_to_bits(val) as u64) << 32) | Self::LENGTH_TAG)
52 }
53
54 /// A percentage length relative to the size of the containing block.
55 ///
56 /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
57 #[inline(always)]
58 pub const fn percent(val: f32) -> Self {
59 Self(((f32_to_bits(val) as u64) << 32) | Self::PERCENT_TAG)
60 }
61
62 /// A `calc()` value. The value passed here is treated as an opaque handle to
63 /// the actual calc representation and may be a pointer, index, etc.
64 ///
65 /// The low 3 bits are used as a tag value and will be returned as 0.
66 #[inline]
67 pub fn calc(ptr: *const ()) -> Self {
68 assert_ne!(ptr as u64, 0);
69 assert_eq!(ptr as u64 & 0b111, 0);
70 Self(ptr as u64 | Self::CALC_TAG)
71 }
72
73 /// The dimension should be automatically computed according to algorithm-specific rules
74 /// regarding the default size of boxes.
75 #[inline(always)]
76 pub const fn auto() -> Self {
77 Self(Self::AUTO_TAG)
78 }
79
80 /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
81 /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
82 /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
83 #[inline(always)]
84 pub const fn fr(val: f32) -> Self {
85 Self(((f32_to_bits(val) as u64) << 32) | Self::FR_TAG)
86 }
87
88 /// The size should be the "min-content" size.
89 /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
90 #[inline(always)]
91 pub const fn min_content() -> Self {
92 Self(Self::MIN_CONTENT_TAG)
93 }
94
95 /// The size should be the "max-content" size.
96 /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
97 #[inline(always)]
98 pub const fn max_content() -> Self {
99 Self(Self::MAX_CONTENT_TAG)
100 }
101
102 /// The size should be computed according to the "fit content" formula:
103 /// `max(min_content, min(max_content, limit))`
104 /// where:
105 /// - `min_content` is the [min-content](Self::min_content) size
106 /// - `max_content` is the [max-content](Self::max_content) size
107 /// - `limit` is a LENGTH value passed to this function
108 ///
109 /// The effect of this is that the item takes the size of `limit` clamped
110 /// by the min-content and max-content sizes.
111 #[inline(always)]
112 pub const fn fit_content_px(limit: f32) -> Self {
113 Self(((f32_to_bits(limit) as u64) << 32) | Self::FIT_CONTENT_PX_TAG)
114 }
115
116 /// The size should be computed according to the "fit content" formula:
117 /// `max(min_content, min(max_content, limit))`
118 /// where:
119 /// - `min_content` is the [min-content](Self::min_content) size
120 /// - `max_content` is the [max-content](Self::max_content) size
121 /// - `limit` is a PERCENTAGE value passed to this function
122 ///
123 /// The effect of this is that the item takes the size of `limit` clamped
124 /// by the min-content and max-content sizes.
125 #[inline(always)]
126 pub const fn fit_content_percent(limit: f32) -> Self {
127 Self(((f32_to_bits(limit) as u64) << 32) | Self::FIT_CONTENT_PERCENT_TAG)
128 }
129
130 /// Get the primary tag
131 #[inline(always)]
132 pub const fn tag(self) -> u64 {
133 self.0 & Self::TAG_MASK
134 }
135
136 /// Get the numeric value associated with the `CompactLength`
137 /// (e.g. the pixel value for a LENGTH variant)
138 #[inline(always)]
139 pub const fn value(self) -> f32 {
140 f32_from_bits((self.0 >> 32) as u32)
141 }
142
143 /// Get the numeric value associated with the `CompactLength`
144 /// (e.g. the pixel value for a LENGTH variant)
145 #[inline(always)]
146 pub const fn calc_value(self) -> u64 {
147 self.0
148 }
149
150 /// Returns true if the value is 0 px
151 #[inline(always)]
152 pub const fn is_calc(self) -> bool {
153 self.0 & Self::CALC_TAG_MASK == 0
154 }
155
156 /// Returns true if the value is 0 px
157 #[inline(always)]
158 pub const fn is_zero(self) -> bool {
159 self.0 == Self::ZERO.0
160 }
161
162 /// Returns true if the value is a length or percentage value
163 #[inline(always)]
164 pub const fn is_length_or_percentage(self) -> bool {
165 matches!(self.tag(), Self::LENGTH_TAG | Self::PERCENT_TAG)
166 }
167
168 /// Returns true if the value is auto
169 #[inline(always)]
170 pub const fn is_auto(self) -> bool {
171 self.tag() == Self::AUTO_TAG
172 }
173
174 /// Returns true if the value is min-content
175 #[inline(always)]
176 pub const fn is_min_content(self) -> bool {
177 matches!(self.tag(), Self::MIN_CONTENT_TAG)
178 }
179
180 /// Returns true if the value is max-content
181 #[inline(always)]
182 pub const fn is_max_content(self) -> bool {
183 matches!(self.tag(), Self::MAX_CONTENT_TAG)
184 }
185
186 /// Returns true if the value is a fit-content(...) value
187 #[inline(always)]
188 pub const fn is_fit_content(self) -> bool {
189 matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
190 }
191
192 /// Returns true if the value is max-content or a fit-content(...) value
193 #[inline(always)]
194 pub const fn is_max_or_fit_content(self) -> bool {
195 matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
196 }
197
198 /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
199 /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
200 /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
201 #[inline(always)]
202 pub fn is_max_content_alike(&self) -> bool {
203 matches!(
204 self.tag(),
205 CompactLength::AUTO_TAG
206 | CompactLength::MAX_CONTENT_TAG
207 | CompactLength::FIT_CONTENT_PX_TAG
208 | CompactLength::FIT_CONTENT_PERCENT_TAG
209 )
210 }
211
212 /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
213 #[inline(always)]
214 pub fn is_min_or_max_content(&self) -> bool {
215 matches!(self.tag(), Self::MIN_CONTENT_TAG | Self::MAX_CONTENT_TAG)
216 }
217
218 /// Returns true if the value is auto, min-content, max-content, or fit-content(...)
219 #[inline(always)]
220 pub const fn is_intrinsic(self) -> bool {
221 matches!(
222 self.tag(),
223 Self::AUTO_TAG
224 | Self::MIN_CONTENT_TAG
225 | Self::MAX_CONTENT_TAG
226 | Self::FIT_CONTENT_PX_TAG
227 | Self::FIT_CONTENT_PERCENT_TAG
228 )
229 }
230
231 /// Returns true if the value is and fr value
232 #[inline(always)]
233 pub const fn is_fr(self) -> bool {
234 self.tag() == Self::FR_TAG
235 }
236
237 /// Whether the track sizing functions depends on the size of the parent node
238 #[inline(always)]
239 pub const fn uses_percentage(self) -> bool {
240 // TODO: handle calc() values
241 matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc()
242 }
243
244 /// Resolve percentage values against the passed parent_size, returning Some(value)
245 /// Non-percentage values always return None.
246 #[inline(always)]
247 pub fn resolved_percentage_size(self, parent_size: f32, calc_resolver: impl Fn(u64, f32) -> f32) -> Option<f32> {
248 match self.tag() {
249 CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
250 _ if self.is_calc() => Some(calc_resolver(self.0, parent_size)),
251 _ => None,
252 }
253 }
254}
255
256impl TaffyZero for CompactLength {
257 const ZERO: Self = Self::length(0.0);
258}
259impl TaffyAuto for CompactLength {
260 const AUTO: Self = Self::auto();
261}
262impl TaffyMinContent for CompactLength {
263 const MIN_CONTENT: Self = Self::min_content();
264}
265impl TaffyMaxContent for CompactLength {
266 const MAX_CONTENT: Self = Self::max_content();
267}
268impl FromLength for CompactLength {
269 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
270 Self::length(value.into())
271 }
272}
273impl FromPercent for CompactLength {
274 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
275 Self::percent(value.into())
276 }
277}
278impl FromFr for CompactLength {
279 fn from_fr<Input: Into<f32> + Copy>(value: Input) -> Self {
280 Self::fr(value.into())
281 }
282}
283impl TaffyFitContent for CompactLength {
284 fn fit_content(lp: LengthPercentage) -> Self {
285 let value = lp.0.value();
286 match lp.0.tag() {
287 Self::LENGTH_TAG => Self::fit_content_px(value),
288 Self::PERCENT_TAG => Self::fit_content_percent(value),
289 _ => unreachable!(),
290 }
291 }
292}
293
294/// Note: these two functions are copied directly from the std (core) library. But by duplicating them
295/// here we can reduce MSRV from 1.83 all the way down to 1.65 while retaining const constructors.
296mod compat {
297 #![allow(unsafe_code)]
298
299 /// Raw transmutation from `f32` to `u32`.
300 pub const fn f32_to_bits(val: f32) -> u32 {
301 // SAFETY: `u32` is a plain old datatype so we can always transmute to it.
302 unsafe { core::mem::transmute(val) }
303 }
304 /// Raw transmutation from `u32` to `f32`.
305 pub const fn f32_from_bits(v: u32) -> f32 {
306 // SAFETY: `u32` is a plain old datatype so we can always transmute from it.
307 unsafe { core::mem::transmute(v) }
308 }
309}