Implement head-to-head benchmarks against Yoga (#337)
1//! Style types for representing lengths / sizes
2
3use crate::geometry::{Rect, Size};
4use crate::style_helpers::{FromPercent, FromPoints, TaffyAuto, TaffyMaxContent, TaffyMinContent, TaffyZero};
5use crate::sys::abs;
6
7/// A unit of linear measurement
8///
9/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
10#[derive(Copy, Clone, PartialEq, Debug)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub enum LengthPercentage {
13 /// Points are abstract absolute units. Users of Taffy may define what they correspond
14 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
15 Points(f32),
16 /// The dimension is stored in percentage relative to the parent item.
17 Percent(f32),
18}
19impl TaffyZero for LengthPercentage {
20 const ZERO: Self = Self::Points(0.0);
21}
22impl FromPoints for LengthPercentage {
23 fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
24 Self::Points(points.into())
25 }
26}
27impl FromPercent for LengthPercentage {
28 fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
29 Self::Percent(percent.into())
30 }
31}
32
33/// A unit of linear measurement
34///
35/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
36#[derive(Copy, Clone, PartialEq, Debug)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38pub enum LengthPercentageAuto {
39 /// Points are abstract absolute units. Users of Taffy may define what they correspond
40 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
41 Points(f32),
42 /// The dimension is stored in percentage relative to the parent item.
43 Percent(f32),
44 /// The dimension should be automatically computed
45 Auto,
46}
47impl TaffyZero for LengthPercentageAuto {
48 const ZERO: Self = Self::Points(0.0);
49}
50impl TaffyAuto for LengthPercentageAuto {
51 const AUTO: Self = Self::Auto;
52}
53impl FromPoints for LengthPercentageAuto {
54 fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
55 Self::Points(points.into())
56 }
57}
58impl FromPercent for LengthPercentageAuto {
59 fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
60 Self::Percent(percent.into())
61 }
62}
63
64impl From<LengthPercentage> for LengthPercentageAuto {
65 fn from(input: LengthPercentage) -> Self {
66 match input {
67 LengthPercentage::Points(value) => Self::Points(value),
68 LengthPercentage::Percent(value) => Self::Percent(value),
69 }
70 }
71}
72
73impl LengthPercentageAuto {
74 /// Returns:
75 /// - Some(points) for Points variants
76 /// - Some(resolved) using the provided context for Percent variants
77 /// - None for Auto variants
78 pub fn resolve_to_option(self, context: f32) -> Option<f32> {
79 match self {
80 Self::Points(points) => Some(points),
81 Self::Percent(percent) => Some(context * percent),
82 Self::Auto => None,
83 }
84 }
85}
86
87/// A unit of linear measurement
88///
89/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
90#[derive(Copy, Clone, PartialEq, Debug)]
91#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
92pub enum Dimension {
93 /// Points are abstract absolute units. Users of Taffy may define what they correspond
94 /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
95 Points(f32),
96 /// The dimension is stored in percentage relative to the parent item.
97 Percent(f32),
98 /// The dimension should be automatically computed
99 Auto,
100}
101impl TaffyZero for Dimension {
102 const ZERO: Self = Self::Points(0.0);
103}
104impl TaffyAuto for Dimension {
105 const AUTO: Self = Self::Auto;
106}
107impl FromPoints for Dimension {
108 fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
109 Self::Points(points.into())
110 }
111}
112impl FromPercent for Dimension {
113 fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
114 Self::Percent(percent.into())
115 }
116}
117
118impl From<LengthPercentage> for Dimension {
119 fn from(input: LengthPercentage) -> Self {
120 match input {
121 LengthPercentage::Points(value) => Self::Points(value),
122 LengthPercentage::Percent(value) => Self::Percent(value),
123 }
124 }
125}
126
127impl From<LengthPercentageAuto> for Dimension {
128 fn from(input: LengthPercentageAuto) -> Self {
129 match input {
130 LengthPercentageAuto::Points(value) => Self::Points(value),
131 LengthPercentageAuto::Percent(value) => Self::Percent(value),
132 LengthPercentageAuto::Auto => Self::Auto,
133 }
134 }
135}
136
137impl Dimension {
138 /// Is this value defined?
139 pub(crate) fn is_defined(self) -> bool {
140 matches!(self, Dimension::Points(_) | Dimension::Percent(_))
141 }
142
143 /// Get Points value if value is Points variant
144 #[cfg(feature = "grid")]
145 pub fn into_option(self) -> Option<f32> {
146 match self {
147 Dimension::Points(value) => Some(value),
148 _ => None,
149 }
150 }
151}
152
153impl Rect<Dimension> {
154 /// Create a new Rect with [`Dimension::Points`]
155 #[must_use]
156 pub const fn from_points(start: f32, end: f32, top: f32, bottom: f32) -> Self {
157 Rect {
158 left: Dimension::Points(start),
159 right: Dimension::Points(end),
160 top: Dimension::Points(top),
161 bottom: Dimension::Points(bottom),
162 }
163 }
164
165 /// Create a new Rect with [`Dimension::Percent`]
166 #[must_use]
167 pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
168 Rect {
169 left: Dimension::Percent(start),
170 right: Dimension::Percent(end),
171 top: Dimension::Percent(top),
172 bottom: Dimension::Percent(bottom),
173 }
174 }
175}
176
177/// The amount of space available to a node in a given axis
178/// https://www.w3.org/TR/css-sizing-3/#available
179#[derive(Copy, Clone, Debug, PartialEq)]
180pub enum AvailableSpace {
181 /// The amount of space available is the specified number of pixels
182 Definite(f32),
183 /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
184 MinContent,
185 /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
186 MaxContent,
187}
188impl TaffyZero for AvailableSpace {
189 const ZERO: Self = Self::Definite(0.0);
190}
191impl TaffyMaxContent for AvailableSpace {
192 const MAX_CONTENT: Self = Self::MaxContent;
193}
194impl TaffyMinContent for AvailableSpace {
195 const MIN_CONTENT: Self = Self::MinContent;
196}
197impl FromPoints for AvailableSpace {
198 fn from_points<Input: Into<f32> + Copy>(points: Input) -> Self {
199 Self::Definite(points.into())
200 }
201}
202
203impl AvailableSpace {
204 /// Returns true for definite values, else false
205 pub fn is_definite(self) -> bool {
206 matches!(self, AvailableSpace::Definite(_))
207 }
208
209 /// Convert to Option
210 /// Definite values become Some(value). Contraints become None.
211 pub fn into_option(self) -> Option<f32> {
212 match self {
213 AvailableSpace::Definite(value) => Some(value),
214 _ => None,
215 }
216 }
217
218 /// Return the definite value or a default value
219 pub fn unwrap_or(self, default: f32) -> f32 {
220 self.into_option().unwrap_or(default)
221 }
222
223 /// Return the definite value. Panic is the value is not definite.
224 #[track_caller]
225 pub fn unwrap(self) -> f32 {
226 self.into_option().unwrap()
227 }
228
229 /// Return self if definite or a default value
230 pub fn or(self, default: AvailableSpace) -> AvailableSpace {
231 match self {
232 AvailableSpace::Definite(_) => self,
233 _ => default,
234 }
235 }
236
237 /// Return self if definite or a the result of the default value callback
238 pub fn or_else(self, default_cb: impl FnOnce() -> AvailableSpace) -> AvailableSpace {
239 match self {
240 AvailableSpace::Definite(_) => self,
241 _ => default_cb(),
242 }
243 }
244
245 /// Return the definite value or the result of the default value callback
246 pub fn unwrap_or_else(self, default_cb: impl FnOnce() -> f32) -> f32 {
247 self.into_option().unwrap_or_else(default_cb)
248 }
249
250 /// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
251 pub fn maybe_set(self, value: Option<f32>) -> AvailableSpace {
252 match value {
253 Some(value) => AvailableSpace::Definite(value),
254 None => self,
255 }
256 }
257
258 /// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
259 pub fn map_definite_value(self, map_function: impl FnOnce(f32) -> f32) -> AvailableSpace {
260 match self {
261 AvailableSpace::Definite(value) => AvailableSpace::Definite(map_function(value)),
262 _ => self,
263 }
264 }
265
266 /// Compute free_space given the passed used_space
267 pub fn compute_free_space(&self, used_space: f32) -> f32 {
268 match self {
269 AvailableSpace::MaxContent => f32::INFINITY,
270 AvailableSpace::MinContent => 0.0,
271 AvailableSpace::Definite(available_space) => available_space - used_space,
272 }
273 }
274
275 /// Compare equality with another AvailableSpace, treating definite values
276 /// that are within f32::EPSILON of each other as equal
277 pub fn is_roughly_equal(self, other: AvailableSpace) -> bool {
278 use AvailableSpace::*;
279 match (self, other) {
280 (Definite(a), Definite(b)) => abs(a - b) < f32::EPSILON,
281 (MinContent, MinContent) => true,
282 (MaxContent, MaxContent) => true,
283 _ => false,
284 }
285 }
286}
287
288impl From<f32> for AvailableSpace {
289 fn from(value: f32) -> Self {
290 Self::Definite(value)
291 }
292}
293
294impl From<Option<f32>> for AvailableSpace {
295 fn from(option: Option<f32>) -> Self {
296 match option {
297 Some(value) => Self::Definite(value),
298 None => Self::MaxContent,
299 }
300 }
301}
302
303impl Size<AvailableSpace> {
304 /// Convert Size<AvailableSpace> into Size<Option<f32>>
305 pub fn into_options(self) -> Size<Option<f32>> {
306 Size { width: self.width.into_option(), height: self.height.into_option() }
307 }
308
309 /// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
310 pub fn maybe_set(self, value: Size<Option<f32>>) -> Size<AvailableSpace> {
311 Size { width: self.width.maybe_set(value.width), height: self.height.maybe_set(value.height) }
312 }
313}