1use super::*;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Hash)]
8#[serde(crate = "crate::serde")]
9pub enum Size {
10 Pixels(Numeric),
12 Percent(Numeric),
14 Combined(Numeric, Numeric),
16}
17
18impl Display for Size {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 Size::Pixels(val) => write!(f, "{}px", val),
22 Size::Percent(val) => write!(f, "{}%", val),
23 Size::Combined(pix, per) => write!(f, "{}px + {}%", pix, per),
24 }
25 }
26}
27
28impl Neg for Size {
29 type Output = Size;
30 fn neg(self) -> Self::Output {
31 match self {
32 Size::Pixels(pix) => Size::Pixels(-pix),
33 Size::Percent(per) => Size::Percent(-per),
34 Size::Combined(pix, per) => Size::Combined(-pix, -per),
35 }
36 }
37}
38
39impl Add for Size {
40 type Output = Size;
41 fn add(self, rhs: Self) -> Self::Output {
42 let mut pixel_component: Numeric = Default::default();
43 let mut percent_component: Numeric = Default::default();
44
45 [self, rhs].iter().for_each(|size| match size {
46 Size::Pixels(s) => pixel_component = pixel_component + *s,
47 Size::Percent(s) => percent_component = percent_component + *s,
48 Size::Combined(s0, s1) => {
49 pixel_component = pixel_component + *s0;
50 percent_component = percent_component + *s1;
51 }
52 });
53
54 Size::Combined(pixel_component, percent_component)
55 }
56}
57
58impl Add<Percent> for Size {
59 type Output = Size;
60 fn add(self, rhs: Percent) -> Self::Output {
61 self + Size::Percent(rhs.0)
62 }
63}
64
65impl Sub<Percent> for Size {
66 type Output = Size;
67 fn sub(self, rhs: Percent) -> Self::Output {
68 self - Size::Percent(rhs.0)
69 }
70}
71
72impl Add<Size> for Percent {
73 type Output = Size;
74 fn add(self, rhs: Size) -> Self::Output {
75 Size::Percent(self.0) + rhs
76 }
77}
78
79impl Sub<Size> for Percent {
80 type Output = Size;
81 fn sub(self, rhs: Size) -> Self::Output {
82 Size::Percent(self.0) - rhs
83 }
84}
85
86impl Sub for Size {
87 type Output = Size;
88 fn sub(self, rhs: Self) -> Self::Output {
89 let mut pixel_component: Numeric = Default::default();
90 let mut percent_component: Numeric = Default::default();
91
92 let sizes = [(self, 1), (rhs, -1)];
93 for (size, multiplier) in sizes.iter() {
94 match size {
95 Size::Pixels(s) => {
96 pixel_component = pixel_component + *s * Numeric::from(*multiplier)
97 }
98 Size::Percent(s) => {
99 percent_component = percent_component + *s * Numeric::from(*multiplier)
100 }
101 Size::Combined(s0, s1) => {
102 pixel_component = pixel_component + *s0 * Numeric::from(*multiplier);
103 percent_component = percent_component + *s1 * Numeric::from(*multiplier);
104 }
105 }
106 }
107
108 Size::Combined(pixel_component, percent_component)
109 }
110}
111
112impl Size {
113 #[allow(non_snake_case)]
114 pub fn ZERO() -> Self {
116 Size::Pixels(Numeric::F64(0.0))
117 }
118
119 pub fn expect_percent(&self) -> f64 {
122 match &self {
123 Size::Percent(val) => val.to_float() / 100.0,
124 Size::Pixels(val) => {
125 log::warn!("Percentage value expected but stored value was pixel.");
126 val.to_float() / 100.0
127 }
128 Size::Combined(_, percent) => {
129 log::warn!("Percentage value expected but stored value was a combination.");
130 percent.to_float() / 100.0
131 }
132 }
133 }
134
135 pub fn expect_pixels(&self) -> Numeric {
138 match &self {
139 Size::Pixels(val) => val.clone(),
140 Size::Percent(val) => {
141 log::warn!("Pixel value expected but stored value was percentage.");
142 val.clone()
143 }
144 Size::Combined(pixels, _) => {
145 log::warn!("Pixel value expected but stored value was a combination.");
146 pixels.clone()
147 }
148 }
149 }
150}
151
152#[derive(Clone, Copy)]
154pub enum Axis {
155 X,
157 Y,
159}
160
161#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
163#[serde(crate = "crate::serde")]
164pub enum LayoutRole {
165 #[default]
167 Default,
168 Breakout,
173}
174
175impl Interpolatable for LayoutRole {}
176impl HelperFunctions for LayoutRole {}
177
178impl Size {
179 pub fn evaluate(&self, bounds: (f64, f64), axis: Axis) -> f64 {
182 let target_bound = match axis {
183 Axis::X => bounds.0,
184 Axis::Y => bounds.1,
185 };
186 match &self {
187 Size::Pixels(num) => num.to_float(),
188 Size::Percent(num) => target_bound * (num.to_float() / 100.0),
189 Size::Combined(pixel_component, percent_component) => {
190 (target_bound * (percent_component.to_float() / 100.0)) + pixel_component.to_float()
192 }
193 }
194 }
195}
196
197#[derive(Debug, Default, Serialize, Deserialize)]
199pub struct CommonProperty {
200 name: String,
202 property_type: String,
204 optional: bool,
206}
207
208#[derive(Debug, Default, Clone)]
213pub struct CommonProperties {
214 pub id: Property<Option<String>>,
216 pub x: Property<Option<Size>>,
218 pub y: Property<Option<Size>>,
220 pub padding_x: Property<Option<Size>>,
222 pub padding_y: Property<Option<Size>>,
224 pub width: Property<Option<Size>>,
226 pub height: Property<Option<Size>>,
228 pub anchor_x: Property<Option<Size>>,
230 pub anchor_y: Property<Option<Size>>,
232 pub scale_x: Property<Option<Size>>,
235 pub scale_y: Property<Option<Size>>,
237 pub skew_x: Property<Option<Rotation>>,
239 pub skew_y: Property<Option<Rotation>>,
241 pub rotate: Property<Option<Rotation>>,
243 pub transform: Property<Option<Transform2D>>,
245 pub opacity: Property<Option<Opacity>>,
247 pub layout_role: Property<Option<LayoutRole>>,
249 pub unclippable: Property<Option<bool>>,
251 pub _raycastable: Property<Option<bool>>,
253 pub _suspended: Property<Option<bool>>,
255}
256
257impl CommonProperties {
258 pub fn get_default_properties_literal() -> Vec<(String, String)> {
260 Self::get_property_identifiers()
261 .iter()
262 .map(|id| {
263 if id.0 == "transform" {
264 (
265 id.0.to_string(),
266 "Transform2D::default_wrapped()".to_string(),
267 )
268 } else {
269 (id.0.to_string(), "Default::default()".to_string())
270 }
271 })
272 .collect()
273 }
274
275 pub fn get_property_identifiers() -> Vec<(String, String)> {
277 COMMON_PROPERTIES_TYPE
278 .iter()
279 .map(|(c, t)| (c.to_string(), t.to_string()))
280 .collect()
281 }
282
283 pub fn get_as_common_property() -> Vec<CommonProperty> {
285 Self::get_property_identifiers()
286 .iter()
287 .map(|id| CommonProperty {
288 name: id.0.to_string(),
289 property_type: id.1.to_string(),
290 optional: (id.0 == "transform" || id.0 == "width" || id.0 == "height"),
291 })
292 .collect()
293 }
294
295 pub fn retrieve_property_scope(&self) -> HashMap<String, Variable> {
297 let CommonProperties {
298 id,
299 x,
300 y,
301 padding_x,
302 padding_y,
303 width,
304 height,
305 anchor_x,
306 anchor_y,
307 scale_x,
308 scale_y,
309 skew_x,
310 skew_y,
311 rotate,
312 transform,
313 opacity,
314 layout_role,
315 unclippable,
316 _raycastable,
317 _suspended,
318 } = self;
320
321 HashMap::from([
322 (
323 "id".to_string(),
324 Variable::new_from_typed_property(id.clone()),
325 ),
326 (
327 "x".to_string(),
328 Variable::new_from_typed_property(x.clone()),
329 ),
330 (
331 "y".to_string(),
332 Variable::new_from_typed_property(y.clone()),
333 ),
334 (
335 "padding_x".to_string(),
336 Variable::new_from_typed_property(padding_x.clone()),
337 ),
338 (
339 "padding_y".to_string(),
340 Variable::new_from_typed_property(padding_y.clone()),
341 ),
342 (
343 "scale_x".to_string(),
344 Variable::new_from_typed_property(scale_x.clone()),
345 ),
346 (
347 "scale_y".to_string(),
348 Variable::new_from_typed_property(scale_y.clone()),
349 ),
350 (
351 "skew_x".to_string(),
352 Variable::new_from_typed_property(skew_x.clone()),
353 ),
354 (
355 "skew_y".to_string(),
356 Variable::new_from_typed_property(skew_y.clone()),
357 ),
358 (
359 "rotate".to_string(),
360 Variable::new_from_typed_property(rotate.clone()),
361 ),
362 (
363 "anchor_x".to_string(),
364 Variable::new_from_typed_property(anchor_x.clone()),
365 ),
366 (
367 "anchor_y".to_string(),
368 Variable::new_from_typed_property(anchor_y.clone()),
369 ),
370 (
371 "transform".to_string(),
372 Variable::new_from_typed_property(transform.clone()),
373 ),
374 (
375 "opacity".to_string(),
376 Variable::new_from_typed_property(opacity.clone()),
377 ),
378 (
379 "layout_role".to_string(),
380 Variable::new_from_typed_property(layout_role.clone()),
381 ),
382 (
383 "width".to_string(),
384 Variable::new_from_typed_property(width.clone()),
385 ),
386 (
387 "height".to_string(),
388 Variable::new_from_typed_property(height.clone()),
389 ),
390 (
391 "unclippable".to_string(),
392 Variable::new_from_typed_property(unclippable.clone()),
393 ),
394 (
395 "_raycastable".to_string(),
396 Variable::new_from_typed_property(_raycastable.clone()),
397 ),
398 (
399 "_suspended".to_string(),
400 Variable::new_from_typed_property(_suspended.clone()),
401 ),
402 ])
403 }
404}
405
406impl Into<Rotation> for Size {
407 fn into(self) -> Rotation {
408 if let Size::Percent(pix) = self {
409 Rotation::Percent(pix)
410 } else {
411 panic!("Tried to coerce a pixel value into a rotation value; try `%` or `rad` instead of `px`.")
412 }
413 }
414}
415
416impl Size {
417 pub fn get_pixels(&self, parent: f64) -> f64 {
419 match &self {
420 Self::Pixels(p) => p.to_float(),
421 Self::Percent(p) => parent * (p.to_float() / 100.0),
422 Self::Combined(pix, per) => (parent * (per.to_float() / 100.0)) + pix.to_float(),
423 }
424 }
425}
426
427impl Interpolatable for Size {
428 fn interpolate(&self, other: &Self, t: f64) -> Self {
429 fn components(size: &Size) -> (Numeric, Numeric) {
430 match size {
431 Size::Pixels(pixels) => (*pixels, Numeric::F64(0.0)),
432 Size::Percent(percent) => (Numeric::F64(0.0), *percent),
433 Size::Combined(pixels, percent) => (*pixels, *percent),
434 }
435 }
436
437 fn from_components(pixels: Numeric, percent: Numeric) -> Size {
438 let has_pixels = pixels.to_float().abs() > f64::EPSILON;
439 let has_percent = percent.to_float().abs() > f64::EPSILON;
440 match (has_pixels, has_percent) {
441 (true, true) => Size::Combined(pixels, percent),
442 (true, false) => Size::Pixels(pixels),
443 (false, true) => Size::Percent(percent),
444 (false, false) => Size::Pixels(Numeric::F64(0.0)),
445 }
446 }
447
448 let (self_pixels, self_percent) = components(self);
449 let (other_pixels, other_percent) = components(other);
450 let t = Numeric::F64(t);
451
452 from_components(
453 self_pixels + ((other_pixels - self_pixels) * t),
454 self_percent + ((other_percent - self_percent) * t),
455 )
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 #[test]
464 fn size_interpolation_blends_pixel_and_percent_components() {
465 let start = Size::Pixels(0.into());
466 let end = Size::Percent(100.into());
467 let midpoint = start.interpolate(&end, 0.5);
468
469 assert!((midpoint.get_pixels(200.0) - 100.0).abs() < 0.0001);
470 }
471
472 #[test]
473 fn size_interpolation_can_settle_from_percent_offset_to_pixel_base() {
474 let start = Size::Combined(0.into(), 100.into());
475 let end = Size::Pixels(0.into());
476
477 assert!((start.interpolate(&end, 0.5).get_pixels(200.0) - 100.0).abs() < 0.0001);
478 assert!(start.interpolate(&end, 1.0).get_pixels(200.0).abs() < 0.0001);
479 }
480}
481
482impl Default for Size {
483 fn default() -> Self {
484 Self::Percent(Numeric::F64(100.0))
485 }
486}
487
488impl Mul for Size {
489 type Output = Size;
490
491 fn mul(self, rhs: Self) -> Self::Output {
492 match self {
493 Size::Pixels(pix0) => {
494 match rhs {
495 Size::Pixels(pix1) => Size::Pixels(pix0 + pix1),
500 Size::Percent(per1) => Size::Pixels(pix0 * per1),
501 Size::Combined(pix1, per1) => Size::Pixels((pix0 * per1) + pix0 + pix1),
502 }
503 }
504 Size::Percent(per0) => match rhs {
505 Size::Pixels(pix1) => Size::Pixels(per0 * pix1),
506 Size::Percent(per1) => Size::Percent(per0 * per1),
507 Size::Combined(pix1, per1) => Size::Pixels((per0 * pix1) + (per0 * per1)),
508 },
509 Size::Combined(pix0, per0) => match rhs {
510 Size::Pixels(pix1) => Size::Pixels((pix0 * per0) + pix1),
511 Size::Percent(per1) => Size::Percent(pix0 * per0 * per1),
512 Size::Combined(pix1, per1) => Size::Pixels((pix0 * per0) + (pix1 * per1)),
513 },
514 }
515 }
516}