ferroui_base/rendering/composition/server/
server_composition_shape.rs1use super::{
7 composition_transform, impl_server_render_resource, paint_composition_brush, platform_geometry_of,
8 CompositionProperty, IAnimatedServerObject, IServerObject, IServerRenderResource, IServerRenderResourceObserver,
9 ServerCompositor, ServerRenderResource,
10};
11use crate::media::immutable::{ImmutableDashStyle, ImmutablePen, ImmutableSolidColorBrush};
12use crate::media::{IBrush, IDashStyle, IImmutableBrush, IPen, PenLineCap, PenLineJoin};
13use crate::platform::{IDrawingContextImpl, IGeometryImpl};
14use crate::rendering::composition::generated::{
15 ServerCompositionContainerShapeHooks, ServerCompositionContainerShapeProps, ServerCompositionShapeCollectionHooks,
16 ServerCompositionShapeCollectionProps, ServerCompositionShapeHooks, ServerCompositionShapeProps,
17 ServerCompositionSpriteShapeHooks, ServerCompositionSpriteShapeProps,
18};
19use crate::rendering::composition::transport::BatchStreamReader;
20use crate::{Matrix, Vector};
21use std::any::{Any, TypeId};
22use std::rc::{Rc, Weak};
23use std::sync::Arc;
24use std::time::Duration;
25
26const MAX_SHAPE_DEPTH: usize = 256;
29
30fn no_composition_property(_name: &str) -> Option<&'static CompositionProperty> {
33 None
34}
35
36pub struct ServerCompositionShapeCollection {
43 this: Weak<ServerCompositionShapeCollection>,
44 base: ServerRenderResource,
45 props: ServerCompositionShapeCollectionProps,
46}
47
48impl ServerCompositionShapeCollection {
49 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionShapeCollection> {
50 let collection = Rc::new_cyclic(|this: &Weak<ServerCompositionShapeCollection>| {
51 ServerCompositionShapeCollection {
52 this: this.clone(),
53 base: ServerRenderResource::new(
54 compositor,
55 this.clone() as Weak<dyn IAnimatedServerObject>,
56 this.clone() as Weak<dyn IServerRenderResource>,
57 ),
58 props: ServerCompositionShapeCollectionProps::new(),
59 }
60 });
61 collection.base.object().activate();
62 collection
63 }
64
65 pub fn shapes(&self) -> Vec<Rc<dyn IServerObject>> {
68 self.props.list().list()
69 }
70
71 pub fn count(&self) -> usize {
72 self.props.list().count()
73 }
74
75 pub fn is_disposed(&self) -> bool {
76 self.base.is_disposed()
77 }
78
79 pub fn of(object: &Rc<dyn IServerObject>) -> Option<&ServerCompositionShapeCollection> {
81 object.as_any().downcast_ref::<ServerCompositionShapeCollection>()
82 }
83
84 fn observe(&self, shapes: &[Rc<dyn IServerObject>], observe: bool) {
86 let Some(this) = self.this.upgrade() else { return };
87 let observer: Rc<dyn IServerRenderResourceObserver> = this;
88 for shape in shapes {
89 if let Some(resource) = shape.clone().as_render_resource() {
90 if observe {
91 resource.add_observer(&observer);
92 } else {
93 resource.remove_observer(&observer);
94 }
95 }
96 }
97 }
98}
99
100impl_server_render_resource!(ServerCompositionShapeCollection, base, composition_property: no_composition_property);
101
102impl ServerCompositionShapeCollectionHooks for ServerCompositionShapeCollection {}
103
104impl IServerObject for ServerCompositionShapeCollection {
105 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
106 let old = self.shapes();
107 self.props.deserialize_changes_core(self, reader, committed_at);
108 let new = self.shapes();
109 self.observe(&old, false);
110 self.observe(&new, true);
111 }
112
113 fn values_invalidated(&self) {
114 self.base.core().invalidated(self);
115 }
116
117 fn dispose(&self) {
118 self.observe(&self.shapes(), false);
119 self.props.list().clear();
120 self.base.core().dispose();
121 }
122
123 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
124 Some(self)
125 }
126
127 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
128 Some(self)
129 }
130
131 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
132 self.props.find_props(type_id)
133 }
134
135 fn as_any(&self) -> &dyn Any {
136 self
137 }
138
139 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
140 self
141 }
142}
143
144#[derive(Clone)]
149pub struct CompositionShapePen {
150 brush: Option<Rc<dyn IBrush>>,
151 thickness: f64,
152 dash_style: Option<Rc<ImmutableDashStyle>>,
153 line_cap: PenLineCap,
154 line_join: PenLineJoin,
155 miter_limit: f64,
156}
157
158impl IPen for CompositionShapePen {
159 fn brush(&self) -> Option<Rc<dyn IBrush>> {
160 self.brush.clone()
161 }
162
163 fn dash_style(&self) -> Option<Rc<dyn IDashStyle>> {
164 self.dash_style.clone().map(|style| style as Rc<dyn IDashStyle>)
165 }
166
167 fn line_cap(&self) -> PenLineCap {
168 self.line_cap
169 }
170
171 fn line_join(&self) -> PenLineJoin {
172 self.line_join
173 }
174
175 fn miter_limit(&self) -> f64 {
176 self.miter_limit
177 }
178
179 fn thickness(&self) -> f64 {
180 self.thickness
181 }
182
183 fn as_any(&self) -> &dyn Any {
184 self
185 }
186
187 fn into_immutable_pen(self: Rc<Self>) -> Rc<ImmutablePen> {
188 let brush: Option<Rc<dyn IImmutableBrush>> = self.brush.clone().and_then(|brush| {
191 if let Some(solid) = brush.as_solid_color_brush() {
192 if brush.as_any().is::<ImmutableSolidColorBrush>() {
193 return brush.clone().into_immutable_brush();
194 }
195 let snapshot: Rc<dyn IImmutableBrush> = Rc::new(ImmutableSolidColorBrush::from_brush(solid));
196 return Some(snapshot);
197 }
198 brush.into_immutable_brush()
199 });
200 Rc::new(ImmutablePen::new(
201 brush,
202 self.thickness,
203 self.dash_style.clone(),
204 self.line_cap,
205 self.line_join,
206 self.miter_limit,
207 ))
208 }
209}
210
211pub fn shape_transform(props: &ServerCompositionShapeProps) -> Option<Matrix> {
216 composition_transform(
217 Vector::new(0.0, 0.0),
218 Vector::new(0.0, 0.0),
219 props.center_point(),
220 props.transform_matrix(),
221 props.scale(),
222 props.rotation_angle(),
223 props.offset(),
224 )
225}
226
227macro_rules! server_shape {
230 ($(#[$meta:meta])* $name:ident, $props:ident, $hooks:ident) => {
231 $(#[$meta])*
232 pub struct $name {
233 base: ServerRenderResource,
234 props: $props,
235 }
236
237 impl $name {
238 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<$name> {
241 let shape = Rc::new_cyclic(|this: &Weak<$name>| $name {
242 base: ServerRenderResource::new(
243 compositor,
244 this.clone() as Weak<dyn IAnimatedServerObject>,
245 this.clone() as Weak<dyn IServerRenderResource>,
246 ),
247 props: $props::new(),
248 });
249 shape.base.object().activate();
250 shape
251 }
252
253 pub fn props(&self) -> &$props {
255 &self.props
256 }
257
258 pub fn shape_props(&self) -> &ServerCompositionShapeProps {
260 self.props.base()
261 }
262
263 pub fn transform(&self) -> Option<Matrix> {
266 shape_transform(self.props.base())
267 }
268
269 pub fn is_disposed(&self) -> bool {
270 self.base.is_disposed()
271 }
272 }
273
274 impl_server_render_resource!($name, base, composition_property: $props::get_composition_property);
275
276 impl ServerCompositionShapeHooks for $name {}
277
278 impl $hooks for $name {}
279
280 impl IServerObject for $name {
281 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
282 self.props.deserialize_changes_core(self, reader, committed_at);
283 }
284
285 fn values_invalidated(&self) {
286 self.base.core().invalidated(self);
287 }
288
289 fn dispose(&self) {
290 self.base.core().dispose();
291 }
292
293 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
294 Some(self)
295 }
296
297 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
298 Some(self)
299 }
300
301 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
302 self.props.find_props(type_id)
303 }
304
305 fn as_any(&self) -> &dyn Any {
306 self
307 }
308
309 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
310 self
311 }
312 }
313 };
314}
315
316server_shape!(
317 ServerCompositionSpriteShape,
320 ServerCompositionSpriteShapeProps,
321 ServerCompositionSpriteShapeHooks
322);
323
324fn is_surface_brush(brush: &Rc<dyn IServerObject>) -> bool {
330 crate::rendering::composition::drawing::is_painted_composition_brush(&**brush)
331}
332
333impl ServerCompositionSpriteShape {
334 pub fn platform_geometry(&self) -> Option<Arc<dyn IGeometryImpl>> {
336 platform_geometry_of(&self.props.geometry()?)
337 }
338
339 pub fn pen(&self, transform: Matrix, brush: Option<Rc<dyn IBrush>>) -> Option<CompositionShapePen> {
350 let props = &self.props;
351 let mut thickness = props.stroke_thickness() as f64;
352 if props.is_stroke_non_scaling() {
353 let scale = transform.get_determinant().abs().sqrt();
354 if scale > f64::EPSILON {
355 thickness /= scale;
356 }
357 }
358 if thickness.is_nan() || thickness <= 0.0 {
359 return None;
360 }
361 let dash_style = props.stroke_dash_array().filter(|dashes| !dashes.is_empty()).map(|dashes| {
362 let dashes: Vec<f64> = dashes.iter().map(|dash| *dash as f64).collect();
363 Rc::new(ImmutableDashStyle::new(Some(dashes.as_slice()), props.stroke_dash_offset() as f64))
364 });
365 Some(CompositionShapePen {
366 brush,
367 thickness,
368 dash_style,
369 line_cap: props.stroke_start_cap().to_pen_line_cap(),
370 line_join: props.stroke_line_join().to_pen_line_join(),
371 miter_limit: props.stroke_miter_limit() as f64,
372 })
373 }
374
375 fn render(&self, canvas: &mut dyn IDrawingContextImpl, transform: Matrix, base: Matrix) {
381 let Some(geometry) = self.platform_geometry() else { return };
382 let fill = self.props.fill_brush();
383 let stroke = self.props.stroke_brush();
384 let fill_platform = fill.as_ref().and_then(|brush| brush.clone().as_brush());
385 let stroke_platform = stroke.as_ref().and_then(|brush| brush.clone().as_brush());
386 let pen = stroke_platform.and_then(|brush| self.pen(transform, Some(brush)));
387
388 canvas.set_transform(transform * base);
389 if let Some(brush) = fill.as_ref().filter(|brush| is_surface_brush(brush)) {
390 canvas.push_geometry_clip(&*geometry);
391 paint_composition_brush(canvas, brush, geometry.bounds());
392 canvas.pop_geometry_clip();
393 }
394 if fill_platform.is_some() || pen.is_some() {
395 canvas.draw_geometry(fill_platform.as_deref(), pen.as_ref().map(|pen| pen as &dyn IPen), &*geometry);
396 }
397 if let Some(brush) = stroke.as_ref().filter(|brush| is_surface_brush(brush)) {
398 if let Some(outline) = self.pen(transform, None) {
399 let widened = geometry.get_widened_geometry(&outline);
400 canvas.push_geometry_clip(&*widened);
401 paint_composition_brush(canvas, brush, widened.bounds());
402 canvas.pop_geometry_clip();
403 }
404 }
405 canvas.set_transform(base);
406 }
407}
408
409server_shape!(
410 ServerCompositionContainerShape,
413 ServerCompositionContainerShapeProps,
414 ServerCompositionContainerShapeHooks
415);
416
417impl ServerCompositionContainerShape {
418 pub fn shapes(&self) -> Vec<Rc<dyn IServerObject>> {
420 self.props
421 .shapes()
422 .as_ref()
423 .and_then(ServerCompositionShapeCollection::of)
424 .map(ServerCompositionShapeCollection::shapes)
425 .unwrap_or_default()
426 }
427}
428
429pub fn render_shapes(canvas: &mut dyn IDrawingContextImpl, shapes: &[Rc<dyn IServerObject>], parent: Matrix, base: Matrix) {
435 render_shapes_at(canvas, shapes, parent, base, 0);
436}
437
438fn render_shapes_at(
439 canvas: &mut dyn IDrawingContextImpl,
440 shapes: &[Rc<dyn IServerObject>],
441 parent: Matrix,
442 base: Matrix,
443 depth: usize,
444) {
445 if depth > MAX_SHAPE_DEPTH {
446 return;
447 }
448 for shape in shapes {
449 let any = shape.as_any();
450 if let Some(sprite) = any.downcast_ref::<ServerCompositionSpriteShape>() {
451 let transform = sprite.transform().map_or(parent, |local| local * parent);
452 sprite.render(canvas, transform, base);
453 } else if let Some(container) = any.downcast_ref::<ServerCompositionContainerShape>() {
454 let transform = container.transform().map_or(parent, |local| local * parent);
455 render_shapes_at(canvas, &container.shapes(), transform, base, depth + 1);
456 }
457 }
458}