ferroui_base/rendering/composition/server/
server_composition_clip.rs1use 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
23macro_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 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 pub fn props(&self) -> &$props {
51 &self.props
52 }
53
54 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 ServerInsetClip,
110 ServerInsetClipProps,
111 ServerInsetClipHooks
112);
113
114server_clip!(
115 ServerRectangleClip,
118 ServerRectangleClipProps,
119 ServerRectangleClipHooks
120);
121
122server_clip!(
123 ServerGeometricClip,
126 ServerGeometricClipProps,
127 ServerGeometricClipHooks
128);
129
130#[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#[derive(Clone)]
161pub struct CompositionClipShape {
162 pub rect: RoundedRect,
165 pub transform: Option<Matrix>,
169 pub geometry: Option<Arc<dyn IGeometryImpl>>,
171}
172
173impl CompositionClipShape {
174 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 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 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 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 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
291pub 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}