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