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