Skip to main content

cranpose_ui_graphics/
draw_group.rs

1//! Groups of draws a [`DrawScope`](crate::DrawScope) moves, scales, turns or
2//! cuts as one block, as Compose's `withTransform` does.
3
4use crate::{
5    DrawPrimitive, GraphicsLayer, Path, Point, Rect, Size, TransformOrigin,
6    layer_transform::layer_transform_to_parent,
7};
8
9/// How a group of draws lands in the scope that holds it: scaled, then
10/// turned about the group's origin, then moved, as a [`GraphicsLayer`] with
11/// its transform origin at the top left places its content.
12#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct DrawGroup {
14    /// Where the group's origin lands in the holding scope.
15    pub translation: Point,
16    /// Horizontal scale.
17    pub scale_x: f32,
18    /// Vertical scale.
19    pub scale_y: f32,
20    /// Clockwise turn on screen, in degrees.
21    pub rotation: f32,
22    /// The group's scope rect in its own coordinates: what its draws see as
23    /// the scope, and what `clip` cuts to.
24    pub bounds: Rect,
25    /// Whether nothing the group draws outside `bounds` shows.
26    pub clip: bool,
27    /// Whether the group draws into a surface of its own before it is
28    /// composited, so a mask drawn last in the group cuts only its draws.
29    pub isolated: bool,
30}
31
32impl DrawGroup {
33    /// A group that draws in the holding scope's coordinates.
34    pub fn plain(bounds: Rect) -> Self {
35        Self {
36            translation: Point::default(),
37            scale_x: 1.0,
38            scale_y: 1.0,
39            rotation: 0.0,
40            bounds,
41            clip: false,
42            isolated: false,
43        }
44    }
45
46    /// A [`DrawGroup::plain`] group cut to `bounds`.
47    pub fn clipped(bounds: Rect) -> Self {
48        Self {
49            clip: true,
50            ..Self::plain(bounds)
51        }
52    }
53
54    /// A group of `size` moved by `dx`, `dy`.
55    pub(crate) fn moved(dx: f32, dy: f32, size: Size) -> Self {
56        Self {
57            translation: Point::new(dx, dy),
58            ..Self::plain(Rect::from_size(size))
59        }
60    }
61
62    /// A group that cuts its draws to the mask drawn last in it, over `rect`.
63    pub(crate) fn masked(rect: Rect) -> Self {
64        Self {
65            isolated: true,
66            ..Self::clipped(rect)
67        }
68    }
69
70    /// A group of `size` scaled and turned about `pivot`, a point of the
71    /// holding scope that stays where it is.
72    pub(crate) fn about(
73        pivot: Point,
74        scale_x: f32,
75        scale_y: f32,
76        rotation: f32,
77        size: Size,
78    ) -> Self {
79        let turned = Self {
80            scale_x,
81            scale_y,
82            rotation,
83            ..Self::plain(Rect::from_size(size))
84        };
85        let moved = turned.transform_to_parent().map_point(pivot);
86        Self {
87            translation: Point::new(pivot.x - moved.x, pivot.y - moved.y),
88            ..turned
89        }
90    }
91
92    /// Whether the group scales or turns its draws, beyond moving them.
93    pub fn scales_or_turns(&self) -> bool {
94        self.scale_x != 1.0 || self.scale_y != 1.0 || self.rotation != 0.0
95    }
96
97    /// The graphics layer that places the group's draws as the group does.
98    pub fn layer(&self) -> GraphicsLayer {
99        GraphicsLayer {
100            scale_x: self.scale_x,
101            scale_y: self.scale_y,
102            rotation_z: self.rotation,
103            translation_x: self.translation.x,
104            translation_y: self.translation.y,
105            transform_origin: TransformOrigin::new(0.0, 0.0),
106            ..GraphicsLayer::DEFAULT
107        }
108    }
109
110    /// Maps the group's coordinates to the holding scope's.
111    pub fn transform_to_parent(&self) -> crate::ProjectiveTransform {
112        let origin = Rect {
113            x: 0.0,
114            y: 0.0,
115            ..self.bounds
116        };
117        layer_transform_to_parent(origin, Point::default(), &self.layer())
118    }
119
120    /// The group moved by `dx`, `dy` in the holding scope, its draws with it.
121    pub fn translate(self, dx: f32, dy: f32) -> Self {
122        Self {
123            translation: Point::new(self.translation.x + dx, self.translation.y + dy),
124            ..self
125        }
126    }
127}
128
129/// A group's start or end among recorded primitives. The primitives between
130/// a start and its end are in the group's coordinates.
131#[derive(Clone, Copy, Debug, PartialEq)]
132pub enum GroupMarker {
133    /// The group's draws start here.
134    Start(DrawGroup),
135    /// The group opened last ends here.
136    End,
137}
138
139/// One step of [`DrawScope::with_transform`](crate::DrawScope::with_transform),
140/// as Compose's `DrawTransform` calls. Each step applies inside the steps
141/// before it.
142#[derive(Clone, Copy, Debug, PartialEq)]
143pub enum DrawTransform<'a> {
144    /// Moves the draws by `dx`, `dy`.
145    Translate {
146        /// Horizontal move, in logical pixels.
147        dx: f32,
148        /// Vertical move, in logical pixels.
149        dy: f32,
150    },
151    /// Scales the draws about `pivot`, which stays where it is.
152    Scale {
153        /// Horizontal scale.
154        scale_x: f32,
155        /// Vertical scale.
156        scale_y: f32,
157        /// The point of the scope the scale keeps in place.
158        pivot: Point,
159    },
160    /// Turns the draws clockwise about `pivot`.
161    Rotate {
162        /// The turn, in degrees.
163        degrees: f32,
164        /// The point of the scope the turn keeps in place.
165        pivot: Point,
166    },
167    /// Shrinks the scope by the four insets: the draws move by `left` and
168    /// `top` and see the smaller size. Nothing is cut.
169    Inset {
170        /// Inset from the left edge.
171        left: f32,
172        /// Inset from the top edge.
173        top: f32,
174        /// Inset from the right edge.
175        right: f32,
176        /// Inset from the bottom edge.
177        bottom: f32,
178    },
179    /// Cuts the draws to `rect`, in scope coordinates.
180    ClipRect(Rect),
181    /// Cuts the draws to the fill of a path, in scope coordinates.
182    ClipPath(&'a Path),
183}
184
185/// Moves recorded primitives by `dx`, `dy` as one block: the primitives
186/// outside every group and the outermost groups move, and what a group
187/// holds moves with its group.
188pub fn translate_recorded(
189    primitives: impl IntoIterator<Item = DrawPrimitive>,
190    dx: f32,
191    dy: f32,
192) -> impl Iterator<Item = DrawPrimitive> {
193    let mut depth = 0u32;
194    primitives
195        .into_iter()
196        .map(move |primitive| match primitive {
197            DrawPrimitive::Group(GroupMarker::Start(group)) => {
198                depth += 1;
199                if depth == 1 {
200                    DrawPrimitive::Group(GroupMarker::Start(group.translate(dx, dy)))
201                } else {
202                    primitive
203                }
204            }
205            DrawPrimitive::Group(GroupMarker::End) => {
206                depth = depth.saturating_sub(1);
207                primitive
208            }
209            primitive if depth == 0 => primitive.translate(dx, dy),
210            primitive => primitive,
211        })
212}
213
214#[cfg(test)]
215#[path = "tests/draw_group_tests.rs"]
216mod tests;