Skip to main content

pax_runtime_api/
transform.rs

1//! Rotation and 2D transform types used by layout and rendering.
2
3use super::*;
4
5/// Encodes a rotation in various units
6#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Copy)]
7pub enum Rotation {
8    /// Radian units (2π rad for one full rotation)
9    Radians(Numeric),
10    /// Degree units (360 deg for one full rotation)
11    Degrees(Numeric),
12    /// Percentage units (100% for one full rotation)
13    Percent(Numeric),
14}
15
16impl Display for Rotation {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Rotation::Radians(rad) => write!(f, "{}rad", rad),
20            Rotation::Degrees(deg) => write!(f, "{}deg", deg),
21            Rotation::Percent(per) => write!(f, "{}%", per),
22        }
23    }
24}
25
26impl Default for Rotation {
27    fn default() -> Self {
28        Self::Degrees(Numeric::F64(0.0))
29    }
30}
31
32impl Interpolatable for Rotation {
33    fn interpolate(&self, other: &Self, t: f64) -> Self {
34        Self::Degrees(Numeric::F64(
35            self.get_as_degrees() + (other.get_as_degrees() - self.get_as_degrees()) * t,
36        ))
37    }
38}
39
40impl Rotation {
41    #[allow(non_snake_case)]
42    /// Returns zero degrees.
43    pub fn ZERO() -> Self {
44        Self::Degrees(Numeric::F64(0.0))
45    }
46
47    /// Returns a normalized float proportional to `0deg : 0.0 :: 360deg : 1.0`.
48    ///
49    /// For example, `0rad` maps to `0.0`, `100%` maps to `1.0`, and `720deg`
50    /// maps to `2.0`.
51    pub fn to_float_0_1(&self) -> f64 {
52        match self {
53            Self::Radians(rad) => rad.to_float() / (std::f64::consts::PI * 2.0),
54            Self::Degrees(deg) => deg.to_float() / 360.0_f64,
55            Self::Percent(per) => per.to_float() / 100.0,
56        }
57    }
58
59    /// Returns the rotation as radians, regardless of the original unit
60    pub fn get_as_radians(&self) -> f64 {
61        if let Self::Radians(num) = self {
62            num.to_float()
63        } else if let Self::Degrees(num) = self {
64            num.to_float() * std::f64::consts::PI * 2.0 / 360.0
65        } else if let Self::Percent(num) = self {
66            num.to_float() * std::f64::consts::PI * 2.0 / 100.0
67        } else {
68            unreachable!()
69        }
70    }
71
72    /// Returns the rotation as degrees, regardless of the original unit
73    pub fn get_as_degrees(&self) -> f64 {
74        if let Self::Radians(num) = self {
75            num.to_float() * 180.0 / std::f64::consts::PI
76        } else if let Self::Degrees(num) = self {
77            num.to_float()
78        } else if let Self::Percent(num) = self {
79            num.to_float() * 360.0 / 100.0
80        } else {
81            unreachable!()
82        }
83    }
84}
85impl Neg for Rotation {
86    type Output = Rotation;
87    fn neg(self) -> Self::Output {
88        match self {
89            Rotation::Degrees(deg) => Rotation::Degrees(-deg),
90            Rotation::Radians(rad) => Rotation::Radians(-rad),
91            Rotation::Percent(per) => Rotation::Percent(-per),
92        }
93    }
94}
95
96impl Add for Rotation {
97    type Output = Rotation;
98
99    fn add(self, rhs: Self) -> Self::Output {
100        let self_rad = self.get_as_radians();
101        let other_rad = rhs.get_as_radians();
102        Rotation::Radians(Numeric::F64(self_rad + other_rad))
103    }
104}
105
106/// A sugared representation of an Affine transform combined with an `anchor` layout property.
107#[derive(Debug, Default, Clone, Deserialize, Serialize)]
108pub struct Transform2D {
109    /// Linked list of ancestral Transform2Ds
110    pub previous: Option<Box<Transform2D>>,
111    /// Represents affine rotation over z axis (single-dimensional for 2D rendering)
112    pub rotate: Option<Rotation>,
113    /// Represents affine translation across the x-y plane
114    pub translate: Option<[Size; 2]>,
115    /// Represents the alignment of the (0,0) position of this element as it relates to its own bounding box. (origin offset)
116    pub anchor: Option<[Size; 2]>,
117    /// Represents affine scale coefficients across the x-y plane
118    pub scale: Option<[Size; 2]>,
119    /// Represents affine skew over x and y axes
120    pub skew: Option<[Rotation; 2]>,
121}
122
123impl Interpolatable for Transform2D {}
124
125impl Mul for Transform2D {
126    type Output = Transform2D;
127
128    fn mul(self, rhs: Self) -> Self::Output {
129        let mut ret = rhs.clone();
130        ret.previous = Some(Box::new(self));
131        ret
132    }
133}
134
135impl Transform2D {
136    /// Scale coefficients over the x-y plane.
137    pub fn scale(x: Size, y: Size) -> Self {
138        let mut ret = Transform2D::default();
139        ret.scale = Some([x, y]);
140        ret
141    }
142    /// Rotation over the z axis.
143    pub fn rotate(z: Rotation) -> Self {
144        let mut ret = Transform2D::default();
145        ret.rotate = Some(z);
146        ret
147    }
148    /// Translation over the x-y plane.
149    pub fn translate(x: Size, y: Size) -> Self {
150        let mut ret = Transform2D::default();
151        ret.translate = Some([x, y]);
152        ret
153    }
154    /// Transform origin point for this element, relative to its own bounding box.
155    pub fn anchor(x: Size, y: Size) -> Self {
156        let mut ret = Transform2D::default();
157        ret.anchor = Some([x, y]);
158        ret
159    }
160}