Skip to main content

ferroui_base/rendering/composition/server/
server_composition_clip.rs

1//! The server-side counterparts of the WinUI clip objects (`InsetClip`,
2//! `RectangleClip`, `GeometricClip`) and the shape a clip cuts out of the
3//! visual it is set on.
4
5use super::{
6    impl_server_render_resource, platform_geometry_of, IAnimatedServerObject, IServerObject, IServerRenderResource,
7    ServerCompositionViewBox, ServerCompositor, ServerRenderResource,
8};
9use crate::numerics::Quaternion;
10use crate::platform::{IDrawingContextImpl, IGeometryImpl, LtrbRect};
11use crate::rendering::composition::generated::{
12    ServerCompositionClipHooks, ServerCompositionClipProps, ServerGeometricClipHooks, ServerGeometricClipProps,
13    ServerInsetClipHooks, ServerInsetClipProps, ServerRectangleClipHooks, ServerRectangleClipProps,
14};
15use crate::rendering::composition::transport::BatchStreamReader;
16use crate::rendering::composition::MatrixUtils;
17use crate::{Matrix, Rect, RoundedRect, Vector, Vector3D};
18use std::any::{Any, TypeId};
19use std::rc::{Rc, Weak};
20use std::sync::Arc;
21use std::time::Duration;
22
23/// Declares a server-side clip class: the struct, its constructor, the
24/// render resource contracts and `IServerObject`.
25macro_rules! server_clip {
26    ($(#[$meta:meta])* $name:ident, $props:ident, $hooks:ident) => {
27        $(#[$meta])*
28        pub struct $name {
29            base: ServerRenderResource,
30            props: $props,
31        }
32
33        impl $name {
34            /// Creates the clip, active: the UI-thread clip activates its
35            /// server object when it is created.
36            pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<$name> {
37                let clip = Rc::new_cyclic(|this: &Weak<$name>| $name {
38                    base: ServerRenderResource::new(
39                        compositor,
40                        this.clone() as Weak<dyn IAnimatedServerObject>,
41                        this.clone() as Weak<dyn IServerRenderResource>,
42                    ),
43                    props: $props::new(),
44                });
45                clip.base.object().activate();
46                clip
47            }
48
49            /// The properties of the clip.
50            pub fn props(&self) -> &$props {
51                &self.props
52            }
53
54            /// The properties of `CompositionClip`.
55            pub fn clip_props(&self) -> &ServerCompositionClipProps {
56                self.props.base()
57            }
58
59            pub fn is_disposed(&self) -> bool {
60                self.base.is_disposed()
61            }
62        }
63
64        impl_server_render_resource!($name, base, composition_property: $props::get_composition_property);
65
66        impl ServerCompositionClipHooks for $name {}
67
68        impl $hooks for $name {}
69
70        impl IServerObject for $name {
71            fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
72                self.props.deserialize_changes_core(self, reader, committed_at);
73            }
74
75            fn values_invalidated(&self) {
76                self.base.core().invalidated(self);
77            }
78
79            fn dispose(&self) {
80                self.base.core().dispose();
81            }
82
83            fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
84                Some(self)
85            }
86
87            fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
88                Some(self)
89            }
90
91            fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
92                self.props.find_props(type_id)
93            }
94
95            fn as_any(&self) -> &dyn Any {
96                self
97            }
98
99            fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
100                self
101            }
102        }
103    };
104}
105
106server_clip!(
107    /// The server-side counterpart of an
108    /// [`InsetClip`](crate::rendering::composition::InsetClip).
109    ServerInsetClip,
110    ServerInsetClipProps,
111    ServerInsetClipHooks
112);
113
114server_clip!(
115    /// The server-side counterpart of a
116    /// [`RectangleClip`](crate::rendering::composition::RectangleClip).
117    ServerRectangleClip,
118    ServerRectangleClipProps,
119    ServerRectangleClipHooks
120);
121
122server_clip!(
123    /// The server-side counterpart of a
124    /// [`GeometricClip`](crate::rendering::composition::GeometricClip).
125    ServerGeometricClip,
126    ServerGeometricClipProps,
127    ServerGeometricClipHooks
128);
129
130/// The transform of a WinUI clip or brush from its own properties, in the
131/// space of an area of `size`: the anchor point is relative to the size,
132/// then the transform matrix, the scale and the rotation about the center
133/// point and the offset apply, as for a visual. `None` for the identity.
134#[allow(clippy::too_many_arguments)]
135pub fn composition_transform(
136    size: Vector,
137    anchor_point: Vector,
138    center_point: Vector,
139    transform_matrix: Matrix,
140    scale: Vector,
141    rotation_angle: f32,
142    offset: Vector,
143) -> Option<Matrix> {
144    MatrixUtils::compute_transform(
145        size,
146        anchor_point,
147        Vector3D::new(center_point.x, center_point.y, 0.0),
148        transform_matrix,
149        Vector3D::new(scale.x, scale.y, 1.0),
150        rotation_angle,
151        Quaternion::IDENTITY,
152        Vector3D::new(offset.x, offset.y, 0.0),
153    )
154}
155
156/// The area a composition clip leaves visible, in the coordinates of the
157/// visual it is set on: a rectangle (rounded for a rectangle clip) or, for
158/// a geometric clip, a geometry in the space of the clip, and the transform
159/// of the clip.
160#[derive(Clone)]
161pub struct CompositionClipShape {
162    /// The visible area in the space of the clip; for a geometric clip the
163    /// bounds of its geometry.
164    pub rect: RoundedRect,
165    /// The transform from the space of the clip to the space of the visual,
166    /// `None` for the identity. For a geometric clip it includes the
167    /// mapping of its view box.
168    pub transform: Option<Matrix>,
169    /// The geometry of a geometric clip: the visible area is its inside.
170    pub geometry: Option<Arc<dyn IGeometryImpl>>,
171}
172
173impl CompositionClipShape {
174    /// The shape of `clip`, a server-side clip object, on a visual of the
175    /// given size. `None` when `clip` is not a clip object, or is a
176    /// geometric clip without a geometry (which clips nothing).
177    ///
178    /// Deviation (winui-composition.md): the anchor point of the clip is
179    /// relative to the size of the visual, which is the reference box of
180    /// both clip classes here.
181    pub fn of(clip: &dyn IServerObject, size: Vector) -> Option<CompositionClipShape> {
182        let any = clip.as_any();
183        if let Some(geometric) = any.downcast_ref::<ServerGeometricClip>() {
184            return Self::of_geometric(geometric, size);
185        }
186        let (rect, base) = if let Some(inset) = any.downcast_ref::<ServerInsetClip>() {
187            let props = inset.props();
188            let rect = inset_rect(
189                size,
190                props.left_inset() as f64,
191                props.top_inset() as f64,
192                props.right_inset() as f64,
193                props.bottom_inset() as f64,
194            );
195            (RoundedRect::from_rect(rect), props.base())
196        } else if let Some(rectangle) = any.downcast_ref::<ServerRectangleClip>() {
197            let props = rectangle.props();
198            let (left, top) = (props.left() as f64, props.top() as f64);
199            let width = (props.right() as f64 - left).max(0.0);
200            let height = (props.bottom() as f64 - top).max(0.0);
201            let rect = RoundedRect::new(
202                Rect::new(left, top, width, height),
203                props.top_left_radius(),
204                props.top_right_radius(),
205                props.bottom_right_radius(),
206                props.bottom_left_radius(),
207            );
208            (rect, props.base())
209        } else {
210            return None;
211        };
212        let transform = composition_transform(
213            size,
214            base.anchor_point(),
215            base.center_point(),
216            base.transform_matrix(),
217            base.scale(),
218            base.rotation_angle(),
219            base.offset(),
220        );
221        Some(CompositionClipShape { rect, transform, geometry: None })
222    }
223
224    /// The shape of a geometric clip: its geometry, mapped onto the visual
225    /// by its view box, then transformed by the clip.
226    fn of_geometric(clip: &ServerGeometricClip, size: Vector) -> Option<CompositionClipShape> {
227        let geometry = platform_geometry_of(&clip.props().geometry()?)?;
228        let base = clip.clip_props();
229        let own = composition_transform(
230            size,
231            base.anchor_point(),
232            base.center_point(),
233            base.transform_matrix(),
234            base.scale(),
235            base.rotation_angle(),
236            base.offset(),
237        );
238        let view_box = clip
239            .props()
240            .view_box()
241            .and_then(|view_box| ServerCompositionViewBox::mapping_of(&view_box, size))
242            .map(|mapping| mapping.transform);
243        let transform = match (view_box, own) {
244            (Some(view_box), Some(own)) => Some(view_box * own),
245            (view_box, own) => view_box.or(own),
246        };
247        Some(CompositionClipShape { rect: RoundedRect::from_rect(geometry.bounds()), transform, geometry: Some(geometry) })
248    }
249
250    /// The bounds of the visible area in the space of the visual.
251    pub fn bounds(&self) -> LtrbRect {
252        let rect = LtrbRect::from_rect(self.rect.rect);
253        match self.transform {
254            Some(transform) => rect.transform_to_aabb(transform),
255            None => rect,
256        }
257    }
258
259    /// Pushes the clip onto `canvas`, whose transform is the one of the
260    /// visual; popped with [`pop`](Self::pop). The clip is taken in the space of
261    /// the clip: the transform of the canvas is changed for the push and
262    /// restored after it.
263    pub fn push(&self, canvas: &mut dyn IDrawingContextImpl) {
264        let saved = self.transform.map(|transform| {
265            let saved = canvas.transform();
266            canvas.set_transform(transform * saved);
267            saved
268        });
269        if let Some(geometry) = &self.geometry {
270            canvas.push_geometry_clip(&**geometry);
271        } else if self.rect.is_rounded() {
272            canvas.push_clip_rounded(self.rect);
273        } else {
274            canvas.push_clip(self.rect.rect);
275        }
276        if let Some(saved) = saved {
277            canvas.set_transform(saved);
278        }
279    }
280
281    /// Pops what [`push`](Self::push) pushed.
282    pub fn pop(&self, canvas: &mut dyn IDrawingContextImpl) {
283        if self.geometry.is_some() {
284            canvas.pop_geometry_clip();
285        } else {
286            canvas.pop_clip();
287        }
288    }
289}
290
291/// The rectangle an inset clip leaves visible on an area of `size`: `(left,
292/// top, size.x - right, size.y - bottom)`, empty when the insets overlap.
293pub fn inset_rect(size: Vector, left: f64, top: f64, right: f64, bottom: f64) -> Rect {
294    let width = (size.x - right - left).max(0.0);
295    let height = (size.y - bottom - top).max(0.0);
296    Rect::new(left, top, width, height)
297}