Skip to main content

lgui_core/core/scene/
compositing_layer.rs

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/// A backend-neutral transform applied while a retained layer is composited.
16///
17/// Values are quantized internally so animated transforms remain safe to compare and hash.
18/// The retained layer contents are not rerasterized when only this transform changes.
19#[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    /// Moves the composited layer without changing its retained contents.
66    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    /// Sets the transform origin in normalized layer coordinates.
73    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/// An independently retained paint surface. Animation is only one possible producer of changes.
207#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
208pub struct CompositingLayerSpec {
209    pub opacity: u8,
210    pub background: CompositingLayerBackground,
211    pub transform: LayerTransform,
212    // The scene compiler reserves transparent padding before assigning this effect.
213    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;