1#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
2pub enum CompositingLayerBackground {
3 Opaque,
4 #[default]
5 Transparent,
6}
7
8const ROTATION_UNITS_PER_DEGREE: f32 = 1_000.0;
9const TRANSFORM_UNITS: f32 = 1_000_000.0;
10const TRANSFORM_UNITS_I32: i32 = 1_000_000;
11const TRANSLATION_UNITS: f32 = 1_000.0;
12const MAX_SCALE: f32 = 64.0;
13const MAX_TRANSLATION: f32 = 1_000_000.0;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20pub struct LayerTransform {
21 rotation_millidegrees: i32,
22 scale_x_micros: i32,
23 scale_y_micros: i32,
24 origin_x_micros: i32,
25 origin_y_micros: i32,
26 translation_x_millis: i32,
27 translation_y_millis: i32,
28}
29
30impl LayerTransform {
31 pub const fn identity() -> Self {
32 Self {
33 rotation_millidegrees: 0,
34 scale_x_micros: TRANSFORM_UNITS_I32,
35 scale_y_micros: TRANSFORM_UNITS_I32,
36 origin_x_micros: TRANSFORM_UNITS_I32 / 2,
37 origin_y_micros: TRANSFORM_UNITS_I32 / 2,
38 translation_x_millis: 0,
39 translation_y_millis: 0,
40 }
41 }
42
43 pub fn rotation_degrees(mut self, degrees: f32) -> Self {
44 self.rotation_millidegrees = quantize_rotation(degrees);
45 self
46 }
47
48 pub fn rotation_radians(self, radians: f32) -> Self {
49 self.rotation_degrees(radians.to_degrees())
50 }
51
52 pub fn scale(mut self, scale: f32) -> Self {
53 let scale = quantize_scale(scale);
54 self.scale_x_micros = scale;
55 self.scale_y_micros = scale;
56 self
57 }
58
59 pub fn scale_xy(mut self, scale_x: f32, scale_y: f32) -> Self {
60 self.scale_x_micros = quantize_scale(scale_x);
61 self.scale_y_micros = quantize_scale(scale_y);
62 self
63 }
64
65 pub fn translation(mut self, x: f32, y: f32) -> Self {
67 self.translation_x_millis = quantize_translation(x);
68 self.translation_y_millis = quantize_translation(y);
69 self
70 }
71
72 pub fn origin(mut self, x: f32, y: f32) -> Self {
74 self.origin_x_micros = quantize_origin(x);
75 self.origin_y_micros = quantize_origin(y);
76 self
77 }
78
79 pub fn rotation_degrees_f32(self) -> f32 {
80 self.rotation_millidegrees as f32 / ROTATION_UNITS_PER_DEGREE
81 }
82
83 pub fn rotation_radians_f32(self) -> f32 {
84 self.rotation_degrees_f32().to_radians()
85 }
86
87 pub fn scale_x(self) -> f32 {
88 self.scale_x_micros as f32 / TRANSFORM_UNITS
89 }
90
91 pub fn scale_y(self) -> f32 {
92 self.scale_y_micros as f32 / TRANSFORM_UNITS
93 }
94
95 pub fn origin_x(self) -> f32 {
96 self.origin_x_micros as f32 / TRANSFORM_UNITS
97 }
98
99 pub fn origin_y(self) -> f32 {
100 self.origin_y_micros as f32 / TRANSFORM_UNITS
101 }
102
103 pub fn translation_x(self) -> f32 {
104 self.translation_x_millis as f32 / TRANSLATION_UNITS
105 }
106
107 pub fn translation_y(self) -> f32 {
108 self.translation_y_millis as f32 / TRANSLATION_UNITS
109 }
110
111 pub(crate) fn project_to_physical(self, scale: super::UiScale) -> Self {
112 self.translation(
113 self.translation_x() * scale.factor(),
114 self.translation_y() * scale.factor(),
115 )
116 }
117
118 pub const fn is_identity(self) -> bool {
119 self.rotation_millidegrees == 0
120 && self.scale_x_micros == TRANSFORM_UNITS_I32
121 && self.scale_y_micros == TRANSFORM_UNITS_I32
122 && self.translation_x_millis == 0
123 && self.translation_y_millis == 0
124 }
125
126 pub fn transformed_bounds(self, rect: super::UiRect) -> super::UiRect {
127 if self.is_identity() {
128 return rect;
129 }
130 let points = [
131 self.transform_point(rect, rect.left as f32, rect.top as f32),
132 self.transform_point(rect, rect.right as f32, rect.top as f32),
133 self.transform_point(rect, rect.left as f32, rect.bottom as f32),
134 self.transform_point(rect, rect.right as f32, rect.bottom as f32),
135 ];
136 let min_x = points
137 .iter()
138 .map(|point| point.0)
139 .fold(f32::INFINITY, f32::min);
140 let min_y = points
141 .iter()
142 .map(|point| point.1)
143 .fold(f32::INFINITY, f32::min);
144 let max_x = points
145 .iter()
146 .map(|point| point.0)
147 .fold(f32::NEG_INFINITY, f32::max);
148 let max_y = points
149 .iter()
150 .map(|point| point.1)
151 .fold(f32::NEG_INFINITY, f32::max);
152 super::UiRect::new(min_x, min_y, max_x, max_y)
153 }
154
155 #[doc(hidden)]
156 pub fn transform_point(self, rect: super::UiRect, x: f32, y: f32) -> (f32, f32) {
157 let origin_x = rect.left as f32 + rect.width() as f32 * self.origin_x();
158 let origin_y = rect.top as f32 + rect.height() as f32 * self.origin_y();
159 let dx = (x - origin_x) * self.scale_x();
160 let dy = (y - origin_y) * self.scale_y();
161 let angle = self.rotation_radians_f32();
162 let (sin, cos) = angle.sin_cos();
163 (
164 origin_x + dx * cos - dy * sin + self.translation_x(),
165 origin_y + dx * sin + dy * cos + self.translation_y(),
166 )
167 }
168}
169
170impl Default for LayerTransform {
171 fn default() -> Self {
172 Self::identity()
173 }
174}
175
176fn quantize_rotation(degrees: f32) -> i32 {
177 if !degrees.is_finite() {
178 return 0;
179 }
180 let units_per_turn = (360.0 * ROTATION_UNITS_PER_DEGREE) as i32;
181 ((degrees.rem_euclid(360.0) * ROTATION_UNITS_PER_DEGREE).round() as i32)
182 .rem_euclid(units_per_turn)
183}
184
185fn quantize_scale(scale: f32) -> i32 {
186 if !scale.is_finite() {
187 return TRANSFORM_UNITS_I32;
188 }
189 (scale.clamp(-MAX_SCALE, MAX_SCALE) * TRANSFORM_UNITS).round() as i32
190}
191
192fn quantize_origin(origin: f32) -> i32 {
193 if !origin.is_finite() {
194 return TRANSFORM_UNITS_I32 / 2;
195 }
196 (origin.clamp(0.0, 1.0) * TRANSFORM_UNITS).round() as i32
197}
198
199fn quantize_translation(translation: f32) -> i32 {
200 if !translation.is_finite() {
201 return 0;
202 }
203 (translation.clamp(-MAX_TRANSLATION, MAX_TRANSLATION) * TRANSLATION_UNITS).round() as i32
204}
205
206#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
208pub struct CompositingLayerSpec {
209 pub opacity: u8,
210 pub background: CompositingLayerBackground,
211 pub transform: LayerTransform,
212 pub(crate) shadow: Option<super::ShadowStyle>,
214}
215
216impl CompositingLayerSpec {
217 pub const fn new() -> Self {
218 Self {
219 opacity: 255,
220 background: CompositingLayerBackground::Transparent,
221 transform: LayerTransform::identity(),
222 shadow: None,
223 }
224 }
225
226 pub const fn opaque(mut self) -> Self {
227 self.background = CompositingLayerBackground::Opaque;
228 self
229 }
230
231 pub const fn transparent(mut self) -> Self {
232 self.background = CompositingLayerBackground::Transparent;
233 self
234 }
235
236 pub fn opacity(mut self, opacity: f32) -> Self {
237 self.opacity = (opacity.clamp(0.0, 1.0) * 255.0).round() as u8;
238 self
239 }
240
241 pub fn opacity_f32(self) -> f32 {
242 self.opacity as f32 / 255.0
243 }
244
245 pub fn transform(mut self, transform: LayerTransform) -> Self {
246 self.transform = transform;
247 self
248 }
249
250 pub fn rotation_degrees(mut self, degrees: f32) -> Self {
251 self.transform = self.transform.rotation_degrees(degrees);
252 self
253 }
254
255 pub fn rotation_radians(mut self, radians: f32) -> Self {
256 self.transform = self.transform.rotation_radians(radians);
257 self
258 }
259
260 pub fn scale(mut self, scale: f32) -> Self {
261 self.transform = self.transform.scale(scale);
262 self
263 }
264
265 pub fn scale_xy(mut self, scale_x: f32, scale_y: f32) -> Self {
266 self.transform = self.transform.scale_xy(scale_x, scale_y);
267 self
268 }
269
270 pub fn transform_origin(mut self, x: f32, y: f32) -> Self {
271 self.transform = self.transform.origin(x, y);
272 self
273 }
274
275 pub fn translation(mut self, x: f32, y: f32) -> Self {
276 self.transform = self.transform.translation(x, y);
277 self
278 }
279}
280
281impl Default for CompositingLayerSpec {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287#[cfg(test)]
288#[path = "compositing_layer_test.rs"]
289mod tests;