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: LocalProperty<Option<String>>,
216 pub x: LocalProperty<Option<Size>>,
218 pub y: LocalProperty<Option<Size>>,
220 pub padding_x: LocalProperty<Option<Size>>,
222 pub padding_y: LocalProperty<Option<Size>>,
224 pub width: LocalProperty<Option<Size>>,
226 pub height: LocalProperty<Option<Size>>,
228 pub anchor_x: LocalProperty<Option<Size>>,
230 pub anchor_y: LocalProperty<Option<Size>>,
232 pub scale_x: LocalProperty<Option<Size>>,
235 pub scale_y: LocalProperty<Option<Size>>,
237 pub skew_x: LocalProperty<Option<Rotation>>,
239 pub skew_y: LocalProperty<Option<Rotation>>,
241 pub rotate: LocalProperty<Option<Rotation>>,
243 pub transform: LocalProperty<Option<Transform2D>>,
245 pub opacity: LocalProperty<Option<Opacity>>,
248 pub layout_role: LocalProperty<Option<LayoutRole>>,
250 pub unclippable: LocalProperty<Option<bool>>,
252 pub _raycastable: LocalProperty<Option<bool>>,
254 pub _suspended: LocalProperty<Option<bool>>,
256}
257
258impl CommonProperties {
259 pub fn get_default_properties_literal() -> Vec<(String, String)> {
261 Self::get_property_identifiers()
262 .iter()
263 .map(|id| {
264 if id.0 == "transform" {
265 (
266 id.0.to_string(),
267 "Transform2D::default_wrapped()".to_string(),
268 )
269 } else {
270 (id.0.to_string(), "Default::default()".to_string())
271 }
272 })
273 .collect()
274 }
275
276 pub fn get_property_identifiers() -> Vec<(String, String)> {
278 COMMON_PROPERTIES_TYPE
279 .iter()
280 .map(|(c, t)| (c.to_string(), t.to_string()))
281 .collect()
282 }
283
284 pub fn get_as_common_property() -> Vec<CommonProperty> {
286 Self::get_property_identifiers()
287 .iter()
288 .map(|id| CommonProperty {
289 name: id.0.to_string(),
290 property_type: id.1.to_string(),
291 optional: (id.0 == "transform" || id.0 == "width" || id.0 == "height"),
292 })
293 .collect()
294 }
295
296 pub fn retrieve_property_scope(&self) -> HashMap<String, Variable> {
298 let CommonProperties {
299 id,
300 x,
301 y,
302 padding_x,
303 padding_y,
304 width,
305 height,
306 anchor_x,
307 anchor_y,
308 scale_x,
309 scale_y,
310 skew_x,
311 skew_y,
312 rotate,
313 transform,
314 opacity,
315 layout_role,
316 unclippable,
317 _raycastable,
318 _suspended,
319 } = self;
321
322 HashMap::from([
323 (
324 "id".to_string(),
325 Variable::new_from_typed_property(id.clone()),
326 ),
327 (
328 "x".to_string(),
329 Variable::new_from_typed_property(x.clone()),
330 ),
331 (
332 "y".to_string(),
333 Variable::new_from_typed_property(y.clone()),
334 ),
335 (
336 "padding_x".to_string(),
337 Variable::new_from_typed_property(padding_x.clone()),
338 ),
339 (
340 "padding_y".to_string(),
341 Variable::new_from_typed_property(padding_y.clone()),
342 ),
343 (
344 "scale_x".to_string(),
345 Variable::new_from_typed_property(scale_x.clone()),
346 ),
347 (
348 "scale_y".to_string(),
349 Variable::new_from_typed_property(scale_y.clone()),
350 ),
351 (
352 "skew_x".to_string(),
353 Variable::new_from_typed_property(skew_x.clone()),
354 ),
355 (
356 "skew_y".to_string(),
357 Variable::new_from_typed_property(skew_y.clone()),
358 ),
359 (
360 "rotate".to_string(),
361 Variable::new_from_typed_property(rotate.clone()),
362 ),
363 (
364 "anchor_x".to_string(),
365 Variable::new_from_typed_property(anchor_x.clone()),
366 ),
367 (
368 "anchor_y".to_string(),
369 Variable::new_from_typed_property(anchor_y.clone()),
370 ),
371 (
372 "transform".to_string(),
373 Variable::new_from_typed_property(transform.clone()),
374 ),
375 (
376 "opacity".to_string(),
377 Variable::new_from_typed_property(opacity.clone()),
378 ),
379 (
380 "layout_role".to_string(),
381 Variable::new_from_typed_property(layout_role.clone()),
382 ),
383 (
384 "width".to_string(),
385 Variable::new_from_typed_property(width.clone()),
386 ),
387 (
388 "height".to_string(),
389 Variable::new_from_typed_property(height.clone()),
390 ),
391 (
392 "unclippable".to_string(),
393 Variable::new_from_typed_property(unclippable.clone()),
394 ),
395 (
396 "_raycastable".to_string(),
397 Variable::new_from_typed_property(_raycastable.clone()),
398 ),
399 (
400 "_suspended".to_string(),
401 Variable::new_from_typed_property(_suspended.clone()),
402 ),
403 ])
404 }
405}
406
407impl Into<Rotation> for Size {
408 fn into(self) -> Rotation {
409 if let Size::Percent(pix) = self {
410 Rotation::Percent(pix)
411 } else {
412 panic!("Tried to coerce a pixel value into a rotation value; try `%` or `rad` instead of `px`.")
413 }
414 }
415}
416
417impl Size {
418 pub fn get_pixels(&self, parent: f64) -> f64 {
420 match &self {
421 Self::Pixels(p) => p.to_float(),
422 Self::Percent(p) => parent * (p.to_float() / 100.0),
423 Self::Combined(pix, per) => (parent * (per.to_float() / 100.0)) + pix.to_float(),
424 }
425 }
426}
427
428impl Interpolatable for Size {
429 fn interpolate(&self, other: &Self, t: f64) -> Self {
430 fn components(size: &Size) -> (Numeric, Numeric) {
431 match size {
432 Size::Pixels(pixels) => (*pixels, Numeric::F64(0.0)),
433 Size::Percent(percent) => (Numeric::F64(0.0), *percent),
434 Size::Combined(pixels, percent) => (*pixels, *percent),
435 }
436 }
437
438 fn from_components(pixels: Numeric, percent: Numeric) -> Size {
439 let has_pixels = pixels.to_float().abs() > f64::EPSILON;
440 let has_percent = percent.to_float().abs() > f64::EPSILON;
441 match (has_pixels, has_percent) {
442 (true, true) => Size::Combined(pixels, percent),
443 (true, false) => Size::Pixels(pixels),
444 (false, true) => Size::Percent(percent),
445 (false, false) => Size::Pixels(Numeric::F64(0.0)),
446 }
447 }
448
449 let (self_pixels, self_percent) = components(self);
450 let (other_pixels, other_percent) = components(other);
451 let t = Numeric::F64(t);
452
453 from_components(
454 self_pixels + ((other_pixels - self_pixels) * t),
455 self_percent + ((other_percent - self_percent) * t),
456 )
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 fn size_interpolation_blends_pixel_and_percent_components() {
466 let start = Size::Pixels(0.into());
467 let end = Size::Percent(100.into());
468 let midpoint = start.interpolate(&end, 0.5);
469
470 assert!((midpoint.get_pixels(200.0) - 100.0).abs() < 0.0001);
471 }
472
473 #[test]
474 fn size_interpolation_can_settle_from_percent_offset_to_pixel_base() {
475 let start = Size::Combined(0.into(), 100.into());
476 let end = Size::Pixels(0.into());
477
478 assert!((start.interpolate(&end, 0.5).get_pixels(200.0) - 100.0).abs() < 0.0001);
479 assert!(start.interpolate(&end, 1.0).get_pixels(200.0).abs() < 0.0001);
480 }
481}
482
483impl Default for Size {
484 fn default() -> Self {
485 Self::Percent(Numeric::F64(100.0))
486 }
487}
488
489impl Mul for Size {
490 type Output = Size;
491
492 fn mul(self, rhs: Self) -> Self::Output {
493 match self {
494 Size::Pixels(pix0) => {
495 match rhs {
496 Size::Pixels(pix1) => Size::Pixels(pix0 + pix1),
501 Size::Percent(per1) => Size::Pixels(pix0 * per1),
502 Size::Combined(pix1, per1) => Size::Pixels((pix0 * per1) + pix0 + pix1),
503 }
504 }
505 Size::Percent(per0) => match rhs {
506 Size::Pixels(pix1) => Size::Pixels(per0 * pix1),
507 Size::Percent(per1) => Size::Percent(per0 * per1),
508 Size::Combined(pix1, per1) => Size::Pixels((per0 * pix1) + (per0 * per1)),
509 },
510 Size::Combined(pix0, per0) => match rhs {
511 Size::Pixels(pix1) => Size::Pixels((pix0 * per0) + pix1),
512 Size::Percent(per1) => Size::Percent(pix0 * per0 * per1),
513 Size::Combined(pix1, per1) => Size::Pixels((pix0 * per0) + (pix1 * per1)),
514 },
515 }
516 }
517}