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, 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;