Skip to main content

ferroui_base/rendering/composition/
composition_clip.rs

1//! The WinUI clip objects: `CompositionClip` and its subclasses `InsetClip`,
2//! `RectangleClip` and `GeometricClip`.
3//!
4//! As for the brushes, every clip is one [`CompositionClip`] behind an `Rc`,
5//! which holds what the concrete class adds in a kind; the concrete classes
6//! are typed handles around the same `Rc` ([`InsetClip`],
7//! [`RectangleClip`]). A clip is set on a visual with
8//! [`CompositionVisual::set_composition_clip`](super::CompositionVisual::set_composition_clip).
9
10use super::composition_object_impls::composition_object_impls;
11use super::generated::{
12    CompositionClipHooks, CompositionClipProps, GeometricClipHooks, GeometricClipProps, InsetClipHooks,
13    InsetClipProps, RectangleClipHooks, RectangleClipProps, ServerGeometricClipProps, ServerInsetClipProps,
14    ServerRectangleClipProps,
15};
16use super::server::{
17    IServerObject, ServerCompositor, ServerGeometricClip, ServerInsetClip, ServerObjectId, ServerRectangleClip,
18};
19use super::{CompositionGeometry, CompositionObject, CompositionViewBox, Compositor, ICompositionObject};
20use crate::{Matrix, Vector};
21use std::ops::Deref;
22use std::rc::{Rc, Weak};
23
24/// What the concrete class of a clip adds to [`CompositionClip`].
25pub(crate) enum CompositionClipKind {
26    Inset(InsetClipProps),
27    Rectangle(RectangleClipProps),
28    Geometric(GeometricClipProps),
29}
30
31/// A clip of a visual (WinUI `Microsoft.UI.Composition.CompositionClip`):
32/// the area of the visual that stays visible, with its own animatable
33/// transform (`AnchorPoint`, `CenterPoint`, `Offset`, `RotationAngle`,
34/// `Scale`, `TransformMatrix`) in the space of the visual.
35///
36/// Deviation (winui-composition.md): `AnchorPoint` is relative to the size of
37/// the visual the clip is set on; `RotationAngleInDegrees` is a view of
38/// `RotationAngle` and cannot be the target of an animation.
39pub struct CompositionClip {
40    this: Weak<CompositionClip>,
41    object: CompositionObject,
42    kind: CompositionClipKind,
43}
44
45impl CompositionClip {
46    fn create(
47        compositor: &Rc<Compositor>,
48        kind: CompositionClipKind,
49        server: impl FnOnce(&Rc<ServerCompositor>) -> Rc<dyn IServerObject> + Send + 'static,
50    ) -> Rc<CompositionClip> {
51        let server = compositor.create_server_object(move |compositor, _| server(compositor));
52        let clip = Rc::new_cyclic(|this: &Weak<CompositionClip>| CompositionClip {
53            this: this.clone(),
54            object: CompositionObject::new(compositor, Some(server)),
55            kind,
56        });
57        match &clip.kind {
58            CompositionClipKind::Inset(props) => props.initialize_defaults(&*clip),
59            CompositionClipKind::Rectangle(props) => props.initialize_defaults(&*clip),
60            CompositionClipKind::Geometric(props) => props.initialize_defaults(&*clip),
61        }
62        clip
63    }
64
65    /// The generated properties of `CompositionClip`.
66    pub fn clip_props(&self) -> &CompositionClipProps {
67        match &self.kind {
68            CompositionClipKind::Inset(props) => props.base(),
69            CompositionClipKind::Rectangle(props) => props.base(),
70            CompositionClipKind::Geometric(props) => props.base(),
71        }
72    }
73
74    pub fn compositor(&self) -> &Rc<Compositor> {
75        self.object.compositor()
76    }
77
78    /// The embedded `CompositionObject`.
79    pub fn object(&self) -> &CompositionObject {
80        &self.object
81    }
82
83    /// The id of the server-side clip.
84    pub fn server(&self) -> ServerObjectId {
85        self.object.required_server()
86    }
87
88    pub fn is_disposed(&self) -> bool {
89        self.object.is_disposed()
90    }
91
92    pub fn dispose(&self) {
93        self.object.dispose();
94    }
95
96    /// The name of the concrete class, for messages.
97    pub fn type_name(&self) -> &'static str {
98        match &self.kind {
99            CompositionClipKind::Inset(_) => "InsetClip",
100            CompositionClipKind::Rectangle(_) => "RectangleClip",
101            CompositionClipKind::Geometric(_) => "GeometricClip",
102        }
103    }
104
105    /// The point of the clip, relative to the size of the visual, that is
106    /// placed at `offset`.
107    pub fn anchor_point(&self) -> Vector {
108        self.clip_props().anchor_point()
109    }
110
111    pub fn set_anchor_point(&self, value: Vector) {
112        self.clip_props().set_anchor_point(self, value)
113    }
114
115    /// The point the clip is scaled and rotated about.
116    pub fn center_point(&self) -> Vector {
117        self.clip_props().center_point()
118    }
119
120    pub fn set_center_point(&self, value: Vector) {
121        self.clip_props().set_center_point(self, value)
122    }
123
124    /// The offset of the clip.
125    pub fn offset(&self) -> Vector {
126        self.clip_props().offset()
127    }
128
129    pub fn set_offset(&self, value: Vector) {
130        self.clip_props().set_offset(self, value)
131    }
132
133    /// The rotation of the clip about its center point, in radians.
134    pub fn rotation_angle(&self) -> f32 {
135        self.clip_props().rotation_angle()
136    }
137
138    pub fn set_rotation_angle(&self, value: f32) {
139        self.clip_props().set_rotation_angle(self, value)
140    }
141
142    /// The rotation of the clip in degrees: a view of
143    /// [`rotation_angle`](Self::rotation_angle).
144    pub fn rotation_angle_in_degrees(&self) -> f32 {
145        self.rotation_angle().to_degrees()
146    }
147
148    pub fn set_rotation_angle_in_degrees(&self, value: f32) {
149        self.set_rotation_angle(value.to_radians())
150    }
151
152    /// The scale of the clip about its center point (default `(1, 1)`).
153    pub fn scale(&self) -> Vector {
154        self.clip_props().scale()
155    }
156
157    pub fn set_scale(&self, value: Vector) {
158        self.clip_props().set_scale(self, value)
159    }
160
161    /// The transform matrix of the clip (WinUI `Matrix3x2`).
162    pub fn transform_matrix(&self) -> Matrix {
163        self.clip_props().transform_matrix()
164    }
165
166    pub fn set_transform_matrix(&self, value: Matrix) {
167        self.clip_props().set_transform_matrix(self, value)
168    }
169}
170
171impl CompositionClipHooks for CompositionClip {}
172
173impl InsetClipHooks for CompositionClip {}
174
175impl RectangleClipHooks for CompositionClip {}
176
177impl GeometricClipHooks for CompositionClip {}
178
179composition_object_impls!(
180    CompositionClip, "CompositionClip",
181    serialize: |this, writer| match &this.kind {
182        CompositionClipKind::Inset(props) => props.serialize_changes_core(this, writer),
183        CompositionClipKind::Rectangle(props) => props.serialize_changes_core(this, writer),
184        CompositionClipKind::Geometric(props) => props.serialize_changes_core(this, writer),
185    },
186    start_animation: |this, name, animation, final_value| match &this.kind {
187        CompositionClipKind::Inset(props) => props.start_animation(this, name, animation, final_value),
188        CompositionClipKind::Rectangle(props) => props.start_animation(this, name, animation, final_value),
189        CompositionClipKind::Geometric(props) => props.start_animation(this, name, animation, final_value),
190    },
191    composition_property: |this, name| match &this.kind {
192        CompositionClipKind::Inset(_) => ServerInsetClipProps::get_composition_property(name),
193        CompositionClipKind::Rectangle(_) => ServerRectangleClipProps::get_composition_property(name),
194        CompositionClipKind::Geometric(_) => ServerGeometricClipProps::get_composition_property(name),
195    },
196);
197
198/// A clip that leaves visible the rectangle `(left_inset, top_inset,
199/// width - right_inset, height - bottom_inset)` of the visual it is set on
200/// (WinUI `Microsoft.UI.Composition.InsetClip`). The insets are animatable
201/// (`LeftInset`, `TopInset`, `RightInset`, `BottomInset`).
202#[derive(Clone)]
203pub struct InsetClip(Rc<CompositionClip>);
204
205impl Deref for InsetClip {
206    type Target = Rc<CompositionClip>;
207
208    fn deref(&self) -> &Rc<CompositionClip> {
209        &self.0
210    }
211}
212
213impl InsetClip {
214    pub(crate) fn new(compositor: &Rc<Compositor>) -> InsetClip {
215        InsetClip(CompositionClip::create(
216            compositor,
217            CompositionClipKind::Inset(InsetClipProps::new()),
218            |c| ServerInsetClip::new(c),
219        ))
220    }
221
222    pub(crate) fn with_insets(compositor: &Rc<Compositor>, left: f32, top: f32, right: f32, bottom: f32) -> InsetClip {
223        let clip = Self::new(compositor);
224        clip.set_left_inset(left);
225        clip.set_top_inset(top);
226        clip.set_right_inset(right);
227        clip.set_bottom_inset(bottom);
228        clip
229    }
230
231    /// The handle of `clip` as an inset clip, if it is one.
232    pub fn from_clip(clip: &Rc<CompositionClip>) -> Option<InsetClip> {
233        matches!(clip.kind, CompositionClipKind::Inset(_)).then(|| InsetClip(clip.clone()))
234    }
235
236    fn props(&self) -> &InsetClipProps {
237        match &self.0.kind {
238            CompositionClipKind::Inset(props) => props,
239            _ => unreachable!("an inset clip handle wraps an inset clip"),
240        }
241    }
242
243    pub fn left_inset(&self) -> f32 {
244        self.props().left_inset()
245    }
246
247    pub fn set_left_inset(&self, value: f32) {
248        self.props().set_left_inset(&*self.0, value)
249    }
250
251    pub fn top_inset(&self) -> f32 {
252        self.props().top_inset()
253    }
254
255    pub fn set_top_inset(&self, value: f32) {
256        self.props().set_top_inset(&*self.0, value)
257    }
258
259    pub fn right_inset(&self) -> f32 {
260        self.props().right_inset()
261    }
262
263    pub fn set_right_inset(&self, value: f32) {
264        self.props().set_right_inset(&*self.0, value)
265    }
266
267    pub fn bottom_inset(&self) -> f32 {
268        self.props().bottom_inset()
269    }
270
271    pub fn set_bottom_inset(&self, value: f32) {
272        self.props().set_bottom_inset(&*self.0, value)
273    }
274}
275
276/// A clip that leaves visible a rectangle, with optional rounded corners,
277/// in the coordinates of the visual it is set on (WinUI
278/// `Microsoft.UI.Composition.RectangleClip`). Every member is animatable
279/// (`Left`, `Top`, `Right`, `Bottom`, `TopLeftRadius`, `TopRightRadius`,
280/// `BottomLeftRadius`, `BottomRightRadius`); the radii are elliptical
281/// (WinUI `Vector2`).
282#[derive(Clone)]
283pub struct RectangleClip(Rc<CompositionClip>);
284
285impl Deref for RectangleClip {
286    type Target = Rc<CompositionClip>;
287
288    fn deref(&self) -> &Rc<CompositionClip> {
289        &self.0
290    }
291}
292
293impl RectangleClip {
294    pub(crate) fn new(compositor: &Rc<Compositor>) -> RectangleClip {
295        RectangleClip(CompositionClip::create(
296            compositor,
297            CompositionClipKind::Rectangle(RectangleClipProps::new()),
298            |c| ServerRectangleClip::new(c),
299        ))
300    }
301
302    pub(crate) fn with_edges(compositor: &Rc<Compositor>, left: f32, top: f32, right: f32, bottom: f32) -> RectangleClip {
303        let clip = Self::new(compositor);
304        clip.set_left(left);
305        clip.set_top(top);
306        clip.set_right(right);
307        clip.set_bottom(bottom);
308        clip
309    }
310
311    #[allow(clippy::too_many_arguments)]
312    pub(crate) fn with_edges_and_radii(
313        compositor: &Rc<Compositor>,
314        left: f32,
315        top: f32,
316        right: f32,
317        bottom: f32,
318        top_left_radius: Vector,
319        top_right_radius: Vector,
320        bottom_right_radius: Vector,
321        bottom_left_radius: Vector,
322    ) -> RectangleClip {
323        let clip = Self::with_edges(compositor, left, top, right, bottom);
324        clip.set_top_left_radius(top_left_radius);
325        clip.set_top_right_radius(top_right_radius);
326        clip.set_bottom_right_radius(bottom_right_radius);
327        clip.set_bottom_left_radius(bottom_left_radius);
328        clip
329    }
330
331    /// The handle of `clip` as a rectangle clip, if it is one.
332    pub fn from_clip(clip: &Rc<CompositionClip>) -> Option<RectangleClip> {
333        matches!(clip.kind, CompositionClipKind::Rectangle(_)).then(|| RectangleClip(clip.clone()))
334    }
335
336    fn props(&self) -> &RectangleClipProps {
337        match &self.0.kind {
338            CompositionClipKind::Rectangle(props) => props,
339            _ => unreachable!("a rectangle clip handle wraps a rectangle clip"),
340        }
341    }
342
343    pub fn left(&self) -> f32 {
344        self.props().left()
345    }
346
347    pub fn set_left(&self, value: f32) {
348        self.props().set_left(&*self.0, value)
349    }
350
351    pub fn top(&self) -> f32 {
352        self.props().top()
353    }
354
355    pub fn set_top(&self, value: f32) {
356        self.props().set_top(&*self.0, value)
357    }
358
359    pub fn right(&self) -> f32 {
360        self.props().right()
361    }
362
363    pub fn set_right(&self, value: f32) {
364        self.props().set_right(&*self.0, value)
365    }
366
367    pub fn bottom(&self) -> f32 {
368        self.props().bottom()
369    }
370
371    pub fn set_bottom(&self, value: f32) {
372        self.props().set_bottom(&*self.0, value)
373    }
374
375    pub fn top_left_radius(&self) -> Vector {
376        self.props().top_left_radius()
377    }
378
379    pub fn set_top_left_radius(&self, value: Vector) {
380        self.props().set_top_left_radius(&*self.0, value)
381    }
382
383    pub fn top_right_radius(&self) -> Vector {
384        self.props().top_right_radius()
385    }
386
387    pub fn set_top_right_radius(&self, value: Vector) {
388        self.props().set_top_right_radius(&*self.0, value)
389    }
390
391    pub fn bottom_left_radius(&self) -> Vector {
392        self.props().bottom_left_radius()
393    }
394
395    pub fn set_bottom_left_radius(&self, value: Vector) {
396        self.props().set_bottom_left_radius(&*self.0, value)
397    }
398
399    pub fn bottom_right_radius(&self) -> Vector {
400        self.props().bottom_right_radius()
401    }
402
403    pub fn set_bottom_right_radius(&self, value: Vector) {
404        self.props().set_bottom_right_radius(&*self.0, value)
405    }
406}
407
408/// A clip that leaves visible the inside of a geometry (WinUI
409/// `Microsoft.UI.Composition.GeometricClip`), in the coordinates of the
410/// visual it is set on, or of its [`view_box`](Self::view_box) when it has
411/// one: the view box maps the geometry onto the size of the visual as for a
412/// shape visual. The transform of the clip applies after the view box. A
413/// change of the geometry (its trimming and animations included) or of the
414/// view box changes the clip.
415///
416/// Deviation (winui-composition.md): a geometric clip without a geometry
417/// clips nothing; the trimming of the geometry applies to the clip.
418#[derive(Clone)]
419pub struct GeometricClip(Rc<CompositionClip>);
420
421impl Deref for GeometricClip {
422    type Target = Rc<CompositionClip>;
423
424    fn deref(&self) -> &Rc<CompositionClip> {
425        &self.0
426    }
427}
428
429impl GeometricClip {
430    pub(crate) fn new(compositor: &Rc<Compositor>) -> GeometricClip {
431        GeometricClip(CompositionClip::create(
432            compositor,
433            CompositionClipKind::Geometric(GeometricClipProps::new()),
434            |c| ServerGeometricClip::new(c),
435        ))
436    }
437
438    pub(crate) fn with_geometry(compositor: &Rc<Compositor>, geometry: Option<Rc<CompositionGeometry>>) -> GeometricClip {
439        let clip = Self::new(compositor);
440        clip.set_geometry(geometry);
441        clip
442    }
443
444    /// The handle of `clip` as a geometric clip, if it is one.
445    pub fn from_clip(clip: &Rc<CompositionClip>) -> Option<GeometricClip> {
446        matches!(clip.kind, CompositionClipKind::Geometric(_)).then(|| GeometricClip(clip.clone()))
447    }
448
449    fn props(&self) -> &GeometricClipProps {
450        match &self.0.kind {
451            CompositionClipKind::Geometric(props) => props,
452            _ => unreachable!("a geometric clip handle wraps a geometric clip"),
453        }
454    }
455
456    /// The geometry whose inside stays visible.
457    pub fn geometry(&self) -> Option<Rc<CompositionGeometry>> {
458        self.props().geometry().and_then(|geometry| geometry.into_any_rc().downcast::<CompositionGeometry>().ok())
459    }
460
461    pub fn set_geometry(&self, value: Option<Rc<CompositionGeometry>>) {
462        self.props().set_geometry(&*self.0, value.map(|geometry| geometry as Rc<dyn ICompositionObject>))
463    }
464
465    /// The area of the geometry coordinates mapped onto the visual.
466    pub fn view_box(&self) -> Option<Rc<CompositionViewBox>> {
467        self.props().view_box().and_then(|view_box| view_box.into_any_rc().downcast::<CompositionViewBox>().ok())
468    }
469
470    pub fn set_view_box(&self, value: Option<Rc<CompositionViewBox>>) {
471        self.props().set_view_box(&*self.0, value.map(|view_box| view_box as Rc<dyn ICompositionObject>))
472    }
473}