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