Skip to main content

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

1//! The WinUI members of `Visual` on the server (phase 2, not from upstream):
2//! the rotation axis, the relative offset and size adjustments, pixel
3//! snapping and `ParentForTransform`, with the bookkeeping that keeps them
4//! up to date.
5
6use super::act::ActSubscriber;
7use super::ServerCompositionVisual;
8use crate::media::EffectExtensions;
9use crate::numerics::{Quaternion, Vector3};
10use crate::platform::LtrbRect;
11use crate::rendering::composition::generated::ServerCompositionVisualProps;
12use crate::rendering::composition::{
13    CompositionBackfaceVisibility, CompositionBorderMode, CompositionCompositeMode, MatrixUtils,
14};
15use crate::{Matrix, Vector, Vector3D};
16use std::rc::Rc;
17
18impl ServerCompositionVisual {
19    // --- the properties, typed ------------------------------------------------
20
21    /// The axis `RotationAngle` rotates about.
22    pub fn rotation_axis(&self) -> Vector3D {
23        self.props.base().rotation_axis()
24    }
25
26    /// The part of the size of the parent added to the offset.
27    pub fn relative_offset_adjustment(&self) -> Vector3D {
28        self.props.base().relative_offset_adjustment()
29    }
30
31    /// The part of the size of the parent added to the size.
32    pub fn relative_size_adjustment(&self) -> Vector {
33        self.props.base().relative_size_adjustment()
34    }
35
36    pub fn is_hit_test_visible(&self) -> bool {
37        self.props.base().is_hit_test_visible()
38    }
39
40    pub fn is_pixel_snapping_enabled(&self) -> bool {
41        self.props.base().is_pixel_snapping_enabled()
42    }
43
44    pub fn border_mode(&self) -> CompositionBorderMode {
45        self.props.base().border_mode()
46    }
47
48    pub fn backface_visibility(&self) -> CompositionBackfaceVisibility {
49        self.props.base().backface_visibility()
50    }
51
52    pub fn composite_mode(&self) -> CompositionCompositeMode {
53        self.props.base().composite_mode()
54    }
55
56    /// The visual whose coordinate space the transform of this visual is
57    /// relative to.
58    pub fn parent_for_transform(&self) -> Option<Rc<ServerCompositionVisual>> {
59        self.props
60            .base()
61            .parent_for_transform()
62            .and_then(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
63    }
64
65    // --- effective size and offset ---------------------------------------------
66
67    /// The size of the visual: `Size` plus `RelativeSizeAdjustment` times the
68    /// size of the parent.
69    pub(super) fn effective_size(&self) -> Vector {
70        let size = self.props.base().size();
71        let relative = self.relative_size_adjustment();
72        if relative == Vector::default() {
73            return size;
74        }
75        let parent = self.parent().map(|parent| parent.size()).unwrap_or_default();
76        size + Vector::multiply(relative, parent)
77    }
78
79    /// The offset of the visual: `Offset` plus `RelativeOffsetAdjustment`
80    /// times the size of the parent (X and Y; the parent has no depth).
81    pub(super) fn effective_offset(&self) -> Vector3D {
82        let offset = self.props.base().offset();
83        let relative = self.relative_offset_adjustment();
84        if relative.x == 0.0 && relative.y == 0.0 {
85            return offset;
86        }
87        let parent = self.parent().map(|parent| parent.size()).unwrap_or_default();
88        offset + Vector3D::new(relative.x * parent.x, relative.y * parent.y, 0.0)
89    }
90
91    /// Whether the offset or the size of the visual follows the size of its
92    /// parent.
93    fn follows_parent_size(&self) -> (bool, bool) {
94        let offset = self.relative_offset_adjustment();
95        (offset.x != 0.0 || offset.y != 0.0, self.relative_size_adjustment() != Vector::default())
96    }
97
98    /// The size of the visual changed (its `Size`, its relative size
99    /// adjustment or the size of the parent it follows): the children whose
100    /// offset or size follow it are updated.
101    pub(super) fn notify_children_of_size_change(&self) {
102        let Some(children) = self.children() else { return };
103        for child in children.list().iter() {
104            child.parent_size_changed();
105        }
106    }
107
108    /// The size of the parent changed, or the visual has a new parent.
109    pub(super) fn parent_size_changed(&self) {
110        let (offset_follows, size_follows) = self.follows_parent_size();
111        if size_follows {
112            self.effective_size_changed();
113        } else if offset_follows {
114            self.trigger_combined_transform_dirty();
115        }
116    }
117
118    /// What a change of `Size` does, for a change of the effective size that
119    /// does not come from the property.
120    fn effective_size_changed(&self) {
121        self.trigger_composition_fields_dirty();
122        self.trigger_combined_transform_dirty();
123        self.trigger_clip_size_dirty();
124        self.own_bounds_dirty.set(true);
125        self.content.size_changed(self);
126        self.notify_children_of_size_change();
127    }
128
129    // --- the transform ------------------------------------------------------------
130
131    /// The rotation of the visual as the 2D transform takes it: the Z rotation
132    /// and the orientation. A `RotationAxis` along Z (or zero) rotates in the
133    /// plane; any other axis is folded into the orientation, applied before
134    /// `Orientation`.
135    fn effective_rotation(&self) -> (f32, Quaternion) {
136        let angle = self.rotation_angle();
137        let orientation = self.orientation();
138        let axis = self.rotation_axis();
139        if angle == 0.0 || (axis.x == 0.0 && axis.y == 0.0) {
140            let angle = if axis.z < 0.0 { -angle } else { angle };
141            return (angle, orientation);
142        }
143        let axis = Vector3::normalize(axis.into());
144        let rotation = Quaternion::create_from_axis_angle(axis, angle);
145        (0.0, Quaternion::concatenate(rotation, orientation))
146    }
147
148    /// Where something at the depth `z` of the space of the visual is drawn,
149    /// relative to the same thing at the depth 0, in that space (phase 7, not
150    /// from upstream: the Z of the offset of a drop shadow).
151    ///
152    /// The renderers project the 3D part of the transform of a visual (its
153    /// `Orientation`, a `RotationAxis` out of the plane, the Z of its
154    /// `Scale`) orthographically onto the plane of the parent. A point at the
155    /// depth `z` is moved by that projection of `(0, 0, z)`: nothing for a
156    /// visual in its plane, along the tilt for a visual turned out of it.
157    /// The result is that move brought back into the space of the visual.
158    ///
159    /// Deviation (winui-composition.md): only the 3D part of the visual's own
160    /// transform is taken; the 3D transforms of its ancestors are already
161    /// flattened, and there is no perspective.
162    pub fn depth_offset(&self, z: f32) -> Vector {
163        if z == 0.0 || !z.is_finite() {
164            return Vector::default();
165        }
166        let (_, orientation) = self.effective_rotation();
167        if orientation == Quaternion::IDENTITY {
168            return Vector::default();
169        }
170        let rotation = crate::numerics::Matrix4x4::create_from_quaternion(orientation);
171        let depth = (z * self.scale().z as f32) as f64;
172        let moved = Vector::new(depth * rotation.m31 as f64, depth * rotation.m32 as f64);
173        if moved == Vector::default() {
174            return moved;
175        }
176        // Back into the space of the visual through the linear part of its
177        // own transform.
178        let own = self.own_transform.get().unwrap_or(Matrix::IDENTITY);
179        let linear = Matrix::new(own.m11, own.m12, own.m21, own.m22, 0.0, 0.0);
180        match linear.try_invert() {
181            Some(inverse) => {
182                let local = crate::Point::new(moved.x, moved.y).transform(inverse);
183                Vector::new(local.x, local.y)
184            }
185            // A visual seen edge-on draws nothing.
186            None => Vector::default(),
187        }
188    }
189
190    /// The transform of the visual from its properties, relative to its
191    /// parent: what the own-properties pass computes, with the WinUI members
192    /// applied (the relative adjustments, the rotation axis, pixel snapping).
193    pub(super) fn compute_own_transform_from_properties(&self, transform_matrix: Matrix) -> Option<Matrix> {
194        let (rotation_angle, orientation) = self.effective_rotation();
195        let transform = MatrixUtils::compute_transform(
196            self.size(),
197            self.anchor_point(),
198            self.center_point(),
199            transform_matrix,
200            self.scale(),
201            rotation_angle,
202            orientation,
203            self.offset() + self.translation(),
204        );
205        if !self.is_pixel_snapping_enabled() {
206            return transform;
207        }
208        // Deviation (winui-composition.md): the translation relative to the
209        // parent is rounded to the device pixels of the target.
210        let scaling = self.root().map(|root| root.scaling()).filter(|scaling| *scaling > 0.0).unwrap_or(1.0);
211        let mut snapped = transform.unwrap_or(Matrix::IDENTITY);
212        snapped.m31 = (snapped.m31 * scaling).round() / scaling;
213        snapped.m32 = (snapped.m32 * scaling).round() / scaling;
214        (!snapped.is_identity()).then_some(snapped)
215    }
216
217    /// The transform from the space of `ParentForTransform` to the space of
218    /// the parent; `None` when there is no such visual or the two are not in
219    /// one tree.
220    fn parent_for_transform_matrix(&self) -> Option<Matrix> {
221        let target = self.parent_for_transform()?;
222        let parent = self.parent()?;
223        if Rc::ptr_eq(&target, &parent) {
224            return Some(Matrix::IDENTITY);
225        }
226        let mut target_chain = Vec::new();
227        let mut visual = Some(target.clone());
228        while let Some(current) = visual {
229            visual = current.parent();
230            target_chain.push(current);
231        }
232        let mut candidate = Some(parent.clone());
233        while let Some(ancestor) = candidate {
234            if target_chain.iter().any(|visual| Rc::ptr_eq(visual, &ancestor)) {
235                let target_to_ancestor = if Rc::ptr_eq(&target, &ancestor) {
236                    Matrix::IDENTITY
237                } else {
238                    Self::compute_transform_from_ancestor(&target, &ancestor)?
239                };
240                let parent_to_ancestor = if Rc::ptr_eq(&parent, &ancestor) {
241                    Matrix::IDENTITY
242                } else {
243                    Self::compute_transform_from_ancestor(&parent, &ancestor)?
244                };
245                return Some(target_to_ancestor * parent_to_ancestor.try_invert()?);
246            }
247            candidate = ancestor.parent();
248        }
249        None
250    }
251
252    /// The own transform of a visual with a `ParentForTransform`, computed by
253    /// the adorner pass (after the own-properties pass, when the transforms of
254    /// the other visuals are known).
255    pub(super) fn update_parent_for_transform(&self) {
256        let own = self.compute_own_transform_from_properties(self.transform_matrix());
257        let transform = match self.parent_for_transform_matrix() {
258            Some(relative) => {
259                let transform = own.unwrap_or(Matrix::IDENTITY) * relative;
260                (!transform.is_identity()).then_some(transform)
261            }
262            None => own,
263        };
264        self.own_transform.set(transform);
265    }
266
267    fn parent_for_transform_subscription(&self) -> ActSubscriber {
268        self.adorned_visual_act_subscription_action()
269    }
270
271    /// Follows the world transforms of `ParentForTransform` and of the parent:
272    /// a change of either enqueues the visual for the adorner pass.
273    fn subscribe_parent_for_transform(&self, subscribe: bool) {
274        let Some(target) = self.parent_for_transform() else { return };
275        let subscription = self.parent_for_transform_subscription();
276        let mut followed = vec![target];
277        if let Some(parent) = self.parent() {
278            followed.push(parent);
279        }
280        for visual in followed {
281            if visual.disposed.get() {
282                continue;
283            }
284            if subscribe {
285                visual.att_helper_subscribe_to_act_notification(subscription.clone());
286            } else {
287                visual.att_helper_unsubscribe_from_act_notification(&subscription);
288            }
289        }
290    }
291
292    pub(super) fn on_parent_for_transform_changing_core(&self) {
293        self.subscribe_parent_for_transform(false);
294    }
295
296    pub(super) fn on_parent_for_transform_changed_core(&self) {
297        self.subscribe_parent_for_transform(true);
298        self.trigger_combined_transform_dirty();
299        self.adorner_helper_enqueue_for_adorner_update();
300    }
301
302    /// The parent is about to change: the subscription to the old parent goes.
303    pub(super) fn winui_members_parent_changing(&self) {
304        if self.parent_for_transform().is_some() {
305            if let Some(parent) = self.parent() {
306                parent.att_helper_unsubscribe_from_act_notification(&self.parent_for_transform_subscription());
307            }
308        }
309    }
310
311    /// The parent changed: the offset and size follow the new parent, and the
312    /// transform relative to `ParentForTransform` is computed again.
313    pub(super) fn winui_members_parent_changed(&self) {
314        if self.parent_for_transform().is_some() {
315            if let Some(parent) = self.parent() {
316                parent.att_helper_subscribe_to_act_notification(self.parent_for_transform_subscription());
317            }
318            self.adorner_helper_enqueue_for_adorner_update();
319        }
320        if self.parent().is_some() {
321            self.parent_size_changed();
322        }
323    }
324
325    /// The visual is disposed: the subscriptions go and the reference to
326    /// `ParentForTransform` is dropped without notifications.
327    pub(super) fn winui_members_dispose(&self) {
328        self.subscribe_parent_for_transform(false);
329        ServerCompositionVisualProps::id_of_parent_for_transform_property().set_field(self, None);
330    }
331
332    // --- bounds -------------------------------------------------------------------
333
334    /// The bounds of the content and the children of the visual in its own
335    /// coordinates, with the padding of its effect, ignoring its own clip and
336    /// visibility: what a visual surface or a redirect visual that shows the
337    /// visual covers (not from upstream). As of the last update of the target
338    /// of the visual.
339    pub fn unclipped_sub_tree_bounds(&self) -> Option<LtrbRect> {
340        let mut bounds = self.own_content_bounds.get();
341        if let Some(children) = self.children() {
342            for child in children.list().iter() {
343                bounds = LtrbRect::full_union(bounds, child.transformed_sub_tree_bounds.get());
344            }
345        }
346        match (bounds, self.effect()) {
347            (Some(bounds), Some(effect)) => {
348                Some(bounds.inflate(EffectExtensions::get_effect_output_padding(Some(&*effect))))
349            }
350            (bounds, _) => bounds,
351        }
352    }
353
354    /// Whether `ancestor` is this visual or one of its ancestors.
355    pub fn is_self_or_descendant_of(&self, ancestor: &ServerCompositionVisual) -> bool {
356        if std::ptr::eq(self, ancestor) {
357            return true;
358        }
359        let mut visual = self.parent();
360        while let Some(current) = visual {
361            if std::ptr::eq(&*current, ancestor) {
362                return true;
363            }
364            visual = current.parent();
365        }
366        false
367    }
368}