Skip to main content

ferroui_base/rendering/composition/server/server_composition_visual/
mod.rs

1//! Server-side `CompositionVisual` counterpart.
2//!
3//! Upstream this is a class hierarchy rooted at `ServerCompositionVisual`.
4//! Here every server visual is one [`ServerCompositionVisual`]: the state
5//! and behaviour of the base class (and of the container class, which every
6//! concrete class derives from), plus a [`IServerVisualContent`] that holds
7//! what a concrete class adds and overrides. The parts of the upstream
8//! partial class live in the submodules of this module.
9
10mod act;
11mod adorners;
12mod computed_properties;
13mod dirty_inputs;
14mod readback;
15mod render;
16mod server_composition_visual_cache;
17mod update;
18mod walker;
19
20pub use readback::{ReadbackData, VisualReadback};
21pub use server_composition_visual_cache::ServerCompositionVisualCache;
22pub use walker::TreeWalkerFrame;
23
24use super::{
25    CompositionProperty, IAnimatedServerObject, IServerAnimatedPropertyHost, IServerObject, IServerPropertyHost,
26    IServerRenderResource, IServerRenderResourceObserver, ServerCompositionBitmapCache, ServerCompositionTarget,
27    ServerCompositionVisualCollection, ServerCompositor, ServerObject, ServerValueChange, ServerVisualRenderContext,
28};
29use crate::media::{IBrush, IImmutableEffect, RenderOptions, TextOptions};
30use crate::numerics::Quaternion;
31use crate::platform::{IDrawingContextImpl, IGeometryImpl, LtrbRect};
32use crate::rendering::composition::animations::IAnimationInstance;
33use crate::rendering::composition::expressions::{ExpressionVariant, IExpressionObject};
34use crate::rendering::composition::generated::{
35    ServerCompositionContainerVisualHooks, ServerCompositionContainerVisualProps, ServerCompositionVisualHooks,
36    ServerCompositionVisualProps,
37};
38use crate::rendering::composition::transport::BatchStreamReader;
39use crate::{Matrix, Rect, Vector, Vector3D};
40use std::any::{Any, TypeId};
41use std::cell::{Cell, RefCell};
42use std::rc::{Rc, Weak};
43use std::sync::Arc;
44use std::time::Duration;
45
46/// What a concrete server visual class adds to [`ServerCompositionVisual`]:
47/// its own properties and its overrides of the virtual members.
48pub trait IServerVisualContent: 'static {
49    /// `DeserializeChangesCore` of the class; the default is the one of the
50    /// container visual.
51    fn deserialize_changes_core(
52        &self,
53        visual: &ServerCompositionVisual,
54        reader: &mut BatchStreamReader<'_>,
55        committed_at: Duration,
56    ) {
57        visual.base_deserialize_changes_core(reader, committed_at);
58    }
59
60    fn compute_own_content_bounds(&self, _visual: &ServerCompositionVisual) -> Option<LtrbRect> {
61        None
62    }
63
64    fn render_core(
65        &self,
66        _visual: &ServerCompositionVisual,
67        _context: &mut ServerVisualRenderContext<'_>,
68        _current_transformed_clip: LtrbRect,
69    ) {
70    }
71
72    fn size_changed(&self, _visual: &ServerCompositionVisual) {}
73
74    fn on_attached_to_root(&self, _visual: &ServerCompositionVisual, _target: &Rc<ServerCompositionTarget>) {}
75
76    fn on_detached_from_root(&self, _visual: &ServerCompositionVisual, _target: &Rc<ServerCompositionTarget>) {}
77
78    fn push_clip_to_bounds(&self, visual: &ServerCompositionVisual, canvas: &mut dyn IDrawingContextImpl) {
79        let size = visual.size();
80        canvas.push_clip(Rect::new(0.0, 0.0, size.x, size.y));
81    }
82
83    fn has_effect(&self, visual: &ServerCompositionVisual) -> bool {
84        visual.effect().is_some()
85    }
86
87    /// A render resource the content depends on changed.
88    fn dependency_queued_invalidate(&self, _visual: &ServerCompositionVisual) {}
89
90    /// The generated property block of the given type, if the class has
91    /// one of its own.
92    fn find_props(&self, _type_id: TypeId) -> Option<&dyn Any> {
93        None
94    }
95
96    fn get_composition_property(&self, name: &str) -> Option<&'static CompositionProperty> {
97        ServerCompositionContainerVisualProps::get_composition_property(name)
98    }
99
100    /// The visual has been disposed on the UI thread.
101    fn dispose(&self, _visual: &ServerCompositionVisual) {}
102
103    fn as_any(&self) -> &dyn Any;
104}
105
106/// Server-side `CompositionVisual` counterpart. Is responsible for
107/// computing the transformation matrix, for applying various visual
108/// properties before calling visual-specific drawing code and for notifying
109/// the [`ServerCompositionTarget`] for new dirty rects.
110pub struct ServerCompositionVisual {
111    this: Weak<ServerCompositionVisual>,
112    object: ServerObject,
113    compositor: Weak<ServerCompositor>,
114    props: ServerCompositionContainerVisualProps,
115    content: Box<dyn IServerVisualContent>,
116    children: Option<Rc<ServerCompositionVisualCollection>>,
117    cache: RefCell<Option<Rc<ServerCompositionVisualCache>>>,
118    disposed: Cell<bool>,
119
120    // Act
121    att_helper: act::AttHelper,
122
123    // Dirty flags, handled by RecomputeOwnProperties
124    combined_transform_dirty: Cell<bool>,
125    clip_size_dirty: Cell<bool>,
126    own_bounds_dirty: Cell<bool>,
127    composition_fields_dirty: Cell<bool>,
128    content_changed: Cell<bool>,
129    delay_propagate_needs_bounds_update: Cell<bool>,
130    delay_propagate_is_dirty_for_render: Cell<bool>,
131    delay_propagate_has_extra_dirty_rects: Cell<bool>,
132
133    // Dirty rect, re-render flags, set by PropagateFlags
134    needs_bounding_box_update: Cell<bool>,
135    is_dirty_for_render: Cell<bool>,
136    is_dirty_for_render_in_subgraph: Cell<bool>,
137
138    // Transform that accounts for offset, RenderTransform and other
139    // properties of _this_ visual that is used to transform to parent's
140    // coordinate space. Updated by RecomputeOwnProperties pass
141    own_transform: Cell<Option<Matrix>>,
142    // The bounds of this visual's own content, excluding children.
143    // Coordinate space: local. Updated by RecomputeOwnProperties pass
144    own_content_bounds: Cell<Option<LtrbRect>>,
145    // The bounds of this visual and its subtree. Coordinate space: local.
146    // Updated by: PreSubgraph, PostSubraph (recursive)
147    sub_tree_bounds: Cell<Option<LtrbRect>>,
148    // The bounds of this visual and its subtree. Coordinate space: parent.
149    // Updated by: PostSubgraph
150    transformed_sub_tree_bounds: Cell<Option<LtrbRect>>,
151    // Visual's own clip area. Coordinate space: local
152    own_clip_rect: Cell<Option<LtrbRect>>,
153    needs_to_add_extra_dirty_rect_to_dirty_region: Cell<bool>,
154    extra_dirty_rect: Cell<LtrbRect>,
155    combined_transform_matrix: Cell<Matrix>,
156
157    // Dirty inputs
158    enqueued_for_own_properties_recompute: Cell<bool>,
159
160    // Readback
161    readback: Arc<VisualReadback>,
162    enqueued_for_readback_update: Cell<bool>,
163}
164
165impl ServerCompositionVisual {
166    /// Creates a server visual. `children` is the server side of the
167    /// children collection (created before the visual by the UI thread),
168    /// `readback` the readback slots shared with the UI-thread visual, and
169    /// `content` the concrete class.
170    pub fn new(
171        compositor: &Rc<ServerCompositor>,
172        children: Option<Rc<ServerCompositionVisualCollection>>,
173        readback: Arc<VisualReadback>,
174        content: Box<dyn IServerVisualContent>,
175    ) -> Rc<ServerCompositionVisual> {
176        Rc::new_cyclic(|this: &Weak<ServerCompositionVisual>| {
177            let owner: Weak<dyn IAnimatedServerObject> = this.clone();
178            ServerCompositionVisual {
179                this: this.clone(),
180                object: ServerObject::new(compositor, owner),
181                compositor: Rc::downgrade(compositor),
182                props: ServerCompositionContainerVisualProps::new(),
183                content,
184                children,
185                cache: RefCell::new(None),
186                disposed: Cell::new(false),
187                att_helper: act::AttHelper::default(),
188                combined_transform_dirty: Cell::new(false),
189                clip_size_dirty: Cell::new(false),
190                own_bounds_dirty: Cell::new(false),
191                composition_fields_dirty: Cell::new(false),
192                content_changed: Cell::new(false),
193                delay_propagate_needs_bounds_update: Cell::new(false),
194                delay_propagate_is_dirty_for_render: Cell::new(false),
195                delay_propagate_has_extra_dirty_rects: Cell::new(false),
196                needs_bounding_box_update: Cell::new(false),
197                is_dirty_for_render: Cell::new(false),
198                is_dirty_for_render_in_subgraph: Cell::new(false),
199                own_transform: Cell::new(None),
200                own_content_bounds: Cell::new(None),
201                sub_tree_bounds: Cell::new(None),
202                transformed_sub_tree_bounds: Cell::new(None),
203                own_clip_rect: Cell::new(None),
204                needs_to_add_extra_dirty_rect_to_dirty_region: Cell::new(false),
205                extra_dirty_rect: Cell::new(LtrbRect::default()),
206                combined_transform_matrix: Cell::new(Matrix::IDENTITY),
207                enqueued_for_own_properties_recompute: Cell::new(false),
208                readback,
209                enqueued_for_readback_update: Cell::new(false),
210            }
211        })
212    }
213
214    /// A weak handle of the visual.
215    pub fn weak(&self) -> Weak<ServerCompositionVisual> {
216        self.this.clone()
217    }
218
219    fn rc(&self) -> Option<Rc<ServerCompositionVisual>> {
220        self.this.upgrade()
221    }
222
223    /// The compositor of the visual, while it is alive.
224    pub fn compositor(&self) -> Option<Rc<ServerCompositor>> {
225        self.compositor.upgrade()
226    }
227
228    /// The `ServerObject` part: activation and animations.
229    pub fn object(&self) -> &ServerObject {
230        &self.object
231    }
232
233    pub fn activate(&self) {
234        self.object.activate();
235    }
236
237    pub fn deactivate(&self) {
238        self.object.deactivate();
239    }
240
241    /// The concrete class of the visual.
242    pub fn content(&self) -> &dyn IServerVisualContent {
243        &*self.content
244    }
245
246    /// The concrete class of the visual as `T`.
247    pub fn content_as<T: IServerVisualContent>(&self) -> Option<&T> {
248        self.content.as_any().downcast_ref::<T>()
249    }
250
251    pub fn children(&self) -> Option<&Rc<ServerCompositionVisualCollection>> {
252        self.children.as_ref()
253    }
254
255    pub fn cache(&self) -> Option<Rc<ServerCompositionVisualCache>> {
256        self.cache.borrow().clone()
257    }
258
259    /// The generated properties of the visual class.
260    pub fn visual_props(&self) -> &ServerCompositionVisualProps {
261        self.props.base()
262    }
263
264    // --- generated properties, typed ---------------------------------------
265
266    pub fn root(&self) -> Option<Rc<ServerCompositionTarget>> {
267        self.props.base().root().and_then(|o| o.into_any_rc().downcast::<ServerCompositionTarget>().ok())
268    }
269
270    pub fn parent(&self) -> Option<Rc<ServerCompositionVisual>> {
271        self.props.base().parent().and_then(|o| o.into_any_rc().downcast::<ServerCompositionVisual>().ok())
272    }
273
274    pub fn adorned_visual(&self) -> Option<Rc<ServerCompositionVisual>> {
275        self.props.base().adorned_visual().and_then(|o| o.into_any_rc().downcast::<ServerCompositionVisual>().ok())
276    }
277
278    pub fn cache_mode(&self) -> Option<Rc<ServerCompositionBitmapCache>> {
279        self.props.base().cache_mode().and_then(|o| o.into_any_rc().downcast::<ServerCompositionBitmapCache>().ok())
280    }
281
282    pub fn visible(&self) -> bool {
283        self.props.base().visible()
284    }
285
286    pub fn opacity(&self) -> f32 {
287        self.props.base().opacity()
288    }
289
290    pub fn clip(&self) -> Option<Arc<dyn IGeometryImpl>> {
291        self.props.base().clip()
292    }
293
294    pub fn clip_to_bounds(&self) -> bool {
295        self.props.base().clip_to_bounds()
296    }
297
298    pub fn offset(&self) -> Vector3D {
299        self.props.base().offset()
300    }
301
302    pub fn translation(&self) -> Vector3D {
303        self.props.base().translation()
304    }
305
306    pub fn size(&self) -> Vector {
307        self.props.base().size()
308    }
309
310    pub fn anchor_point(&self) -> Vector {
311        self.props.base().anchor_point()
312    }
313
314    pub fn center_point(&self) -> Vector3D {
315        self.props.base().center_point()
316    }
317
318    pub fn rotation_angle(&self) -> f32 {
319        self.props.base().rotation_angle()
320    }
321
322    pub fn orientation(&self) -> Quaternion {
323        self.props.base().orientation()
324    }
325
326    pub fn scale(&self) -> Vector3D {
327        self.props.base().scale()
328    }
329
330    pub fn transform_matrix(&self) -> Matrix {
331        self.props.base().transform_matrix()
332    }
333
334    pub fn adorner_is_clipped(&self) -> bool {
335        self.props.base().adorner_is_clipped()
336    }
337
338    pub fn opacity_mask_brush(&self) -> Option<Rc<dyn IBrush>> {
339        self.props.base().opacity_mask_brush().map(|brush| brush.value)
340    }
341
342    pub fn effect(&self) -> Option<std::sync::Arc<dyn IImmutableEffect>> {
343        self.props.base().effect()
344    }
345
346    pub fn render_options(&self) -> RenderOptions {
347        self.props.base().render_options()
348    }
349
350    pub fn text_options(&self) -> TextOptions {
351        self.props.base().text_options()
352    }
353
354    // --- ServerCompositionVisual.cs ------------------------------------------
355
356    pub fn on_cache_mode_state_changed(&self) {
357        if let Some(cache) = self.cache() {
358            cache.invalidate_properties();
359        }
360        self.invalidate_content();
361    }
362
363    fn render_core(&self, context: &mut ServerVisualRenderContext<'_>, current_transformed_clip: LtrbRect) {
364        self.content.render_core(self, context, current_transformed_clip);
365    }
366
367    /// `DeserializeChangesCore` of the container visual class: what the
368    /// concrete classes call as their base.
369    pub fn base_deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
370        self.props.deserialize_changes_core(self, reader, committed_at);
371    }
372}
373
374impl ServerCompositionVisualHooks for ServerCompositionVisual {
375    fn on_fields_deserialized(
376        &self,
377        changed: crate::rendering::composition::generated::CompositionVisualChangedFields,
378    ) {
379        self.on_fields_deserialized_core(changed);
380    }
381
382    fn on_root_changing(&self) {
383        if let (Some(root), Some(this)) = (self.root(), self.rc()) {
384            root.remove_visual(&this);
385            self.content.on_detached_from_root(self, &root);
386        }
387    }
388
389    fn on_root_changed(&self) {
390        if let (Some(root), Some(this)) = (self.root(), self.rc()) {
391            root.add_visual(&this);
392            self.content.on_attached_to_root(self, &root);
393            self.adorner_helper_attached_to_root();
394        }
395        if let Some(cache) = self.cache() {
396            cache.free_resources();
397        }
398    }
399
400    fn on_parent_changing(&self) {
401        self.on_parent_changing_core();
402    }
403
404    fn on_parent_changed(&self) {
405        self.on_parent_changed_core();
406    }
407
408    fn on_adorned_visual_changing(&self) {
409        self.on_adorned_visual_changing_core();
410    }
411
412    fn on_adorned_visual_changed(&self) {
413        self.on_adorned_visual_changed_core();
414    }
415
416    fn on_cache_mode_changing(&self) {
417        if let (Some(cache_mode), Some(this)) = (self.cache_mode(), self.rc()) {
418            cache_mode.unsubscribe(&this);
419        }
420        if let Some(cache) = self.cache.borrow_mut().take() {
421            cache.free_resources();
422        }
423    }
424
425    fn on_cache_mode_changed(&self) {
426        let cache_mode = self.cache_mode();
427        *self.cache.borrow_mut() =
428            cache_mode.as_ref().map(|mode| ServerCompositionVisualCache::new(self.this.clone(), mode.clone()));
429        if let (Some(cache_mode), Some(this)) = (cache_mode, self.rc()) {
430            cache_mode.subscribe(&this);
431        }
432        self.on_cache_mode_state_changed();
433    }
434}
435
436impl ServerCompositionContainerVisualHooks for ServerCompositionVisual {}
437
438impl IServerPropertyHost for ServerCompositionVisual {
439    fn server_compositor(&self) -> Option<Rc<ServerCompositor>> {
440        self.compositor.upgrade()
441    }
442
443    fn set_value(&self, property: &'static CompositionProperty, change: ServerValueChange<'_>) {
444        self.object.set_value(property, change);
445    }
446}
447
448impl IServerAnimatedPropertyHost for ServerCompositionVisual {
449    fn set_animated_value(
450        &self,
451        property: &'static CompositionProperty,
452        current_value: ExpressionVariant,
453        committed_at: Duration,
454        animation: Rc<dyn IAnimationInstance>,
455    ) {
456        self.object.set_animated_value(property, current_value, committed_at, animation);
457    }
458
459    fn remove_animation_for_property(&self, property: &'static CompositionProperty) {
460        self.object.remove_animation_for_property(property);
461    }
462
463    fn notify_animated_value_changed(&self, property: &'static CompositionProperty) {
464        self.notify_animated_value_changed_core(property);
465    }
466}
467
468impl IAnimatedServerObject for ServerCompositionVisual {
469    fn server_object(&self) -> &ServerObject {
470        &self.object
471    }
472
473    fn get_composition_property(&self, field_name: &str) -> Option<&'static CompositionProperty> {
474        self.content.get_composition_property(field_name)
475    }
476
477    fn as_server_object_dyn(&self) -> &dyn IServerObject {
478        self
479    }
480}
481
482impl IExpressionObject for ServerCompositionVisual {
483    fn get_property(&self, name: &str) -> ExpressionVariant {
484        self.object.get_property(name)
485    }
486}
487
488impl IServerRenderResourceObserver for ServerCompositionVisual {
489    fn dependency_queued_invalidate(&self, _sender: &dyn IServerRenderResource) {
490        self.content.dependency_queued_invalidate(self);
491    }
492}
493
494impl IServerObject for ServerCompositionVisual {
495    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
496        self.content.deserialize_changes_core(self, reader, committed_at);
497    }
498
499    /// Releases what the visual holds. Upstream the garbage collector
500    /// reclaims a server visual once nothing refers to it; here the UI
501    /// thread disposes it by id, and the references that would keep a
502    /// cycle alive are dropped without notifications.
503    fn dispose(&self) {
504        if self.disposed.replace(true) {
505            return;
506        }
507        self.content.dispose(self);
508        if let (Some(root), Some(this)) = (self.root(), self.rc()) {
509            root.remove_visual(&this);
510        }
511        if let (Some(cache_mode), Some(this)) = (self.cache_mode(), self.rc()) {
512            cache_mode.unsubscribe(&this);
513        }
514        // Deviation (DEVIATIONS.md, Server composition visuals): an adorner
515        // is subscribed to the transforms of the visual it adorns; upstream's
516        // delegate keeps the adorner with the adorned visual until the
517        // adorner is given another one. The entry here is a weak reference,
518        // which keeps the memory of the adorner, and the visual it adorns
519        // outlives it (a control every focus adorner it ever had): the entry
520        // goes with the adorner.
521        if let Some(adorned) = self.adorned_visual() {
522            if !adorned.disposed.get() {
523                adorned.att_helper_unsubscribe_from_act_notification(&self.adorned_visual_act_subscription_action());
524            }
525        }
526        if let Some(cache) = self.cache.borrow_mut().take() {
527            cache.free_resources();
528        }
529        if let (Some(parent), Some(bounds)) = (self.parent(), self.transformed_sub_tree_bounds.get()) {
530            if !parent.disposed.get() {
531                parent.add_extra_dirty_rect(bounds);
532            }
533        }
534        ServerCompositionVisualProps::id_of_root_property().set_field(self, None);
535        ServerCompositionVisualProps::id_of_parent_property().set_field(self, None);
536        ServerCompositionVisualProps::id_of_adorned_visual_property().set_field(self, None);
537        ServerCompositionVisualProps::id_of_cache_mode_property().set_field(self, None);
538    }
539
540    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
541        self.props.find_props(type_id).or_else(|| self.content.find_props(type_id))
542    }
543
544    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
545        Some(self)
546    }
547
548    fn as_any(&self) -> &dyn Any {
549        self
550    }
551
552    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
553        self
554    }
555}
556
557/// The content of a plain container visual: nothing of its own.
558pub struct ServerCompositionContainerVisual;
559
560impl IServerVisualContent for ServerCompositionContainerVisual {
561    fn as_any(&self) -> &dyn Any {
562        self
563    }
564}