Skip to main content

ferroui_base/media/
transform_group.rs

1use crate::media::media_collection::media_collection_type;
2use crate::media::{MediaCollection, ResetBehavior, Transform, TransformImpl};
3use crate::reactive::IDisposable;
4use crate::{
5    ferro_class, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
6    FerroPropertyChangedEventArgs, Matrix,
7    Ref, StyledProperty,
8};
9use std::cell::{Cell, RefCell};
10use std::rc::Rc;
11
12media_collection_type!(
13    /// A collection of [`Transform`]s.
14    Transforms, Ref<Transform>
15);
16
17impl Transforms {
18    /// Creates an empty collection.
19    pub fn new() -> Self {
20        Self(MediaCollection::new())
21    }
22
23    /// Creates a collection holding `items`.
24    pub fn from_items(items: impl IntoIterator<Item = Ref<Transform>>) -> Self {
25        Self(MediaCollection::from_items(items))
26    }
27}
28
29type ChildSubscriptions = Rc<RefCell<Vec<(Ref<Transform>, Rc<dyn IDisposable>)>>>;
30
31/// A transform that applies its child transforms in order.
32#[repr(C)]
33pub struct TransformGroup {
34    base: Transform,
35    children_notification_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
36    child_subscriptions: ChildSubscriptions,
37    last_matrix: Cell<Option<Matrix>>,
38}
39
40ferro_class!(TransformGroup: Transform);
41crate::ferro_class_info!(TransformGroup { new: TransformGroup::new });
42
43impl FerroObjectImpl for TransformGroup {
44    fn constructed(this: &Self) {
45        Self::parent_constructed(this);
46        this.set_children(Transforms::new());
47    }
48
49    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
50        Self::parent_on_property_changed(this, change);
51
52        if change.property() == Self::children_property().as_property() {
53            if let Some(subscription) = this.children_notification_subscription.take() {
54                subscription.dispose();
55            }
56
57            // Detach from the transforms of the old collection.
58            let old = std::mem::take(&mut *this.child_subscriptions.borrow_mut());
59            for (_, subscription) in old {
60                subscription.dispose();
61            }
62
63            if let Some(new_transforms) = change.get_new_value::<Option<Transforms>>() {
64                // Ensure reset behavior is Remove
65                new_transforms.set_reset_behavior(ResetBehavior::Remove);
66
67                let weak = this.to_ref().downgrade();
68                let subscriptions = this.child_subscriptions.clone();
69                let weak_added = weak.clone();
70                let subscriptions_added = subscriptions.clone();
71                let subscription = new_transforms.for_each_item(
72                    move |_, tr: &Ref<Transform>| {
73                        let weak_child = weak_added.clone();
74                        let handle = tr.changed(move || {
75                            if let Some(this) = weak_child.upgrade() {
76                                this.on_transform_invalidated();
77                            }
78                        });
79                        subscriptions_added.borrow_mut().push((tr.clone(), handle));
80                        if let Some(this) = weak_added.upgrade() {
81                            this.on_transform_invalidated();
82                        }
83                    },
84                    move |_, tr: &Ref<Transform>| {
85                        let position = subscriptions.borrow().iter().position(|(t, _)| t.ptr_eq(tr));
86                        if let Some(position) = position {
87                            let (_, handle) = subscriptions.borrow_mut().remove(position);
88                            handle.dispose();
89                        }
90                        if let Some(this) = weak.upgrade() {
91                            this.on_transform_invalidated();
92                        }
93                    },
94                    || {},
95                );
96                *this.children_notification_subscription.borrow_mut() = Some(subscription);
97            }
98
99            this.on_transform_invalidated();
100        }
101    }
102}
103
104impl TransformImpl for TransformGroup {
105    fn value(this: &Self) -> Matrix {
106        if let Some(matrix) = this.last_matrix.get() {
107            return matrix;
108        }
109
110        let mut matrix = Matrix::IDENTITY;
111        if let Some(children) = this.get_value(Self::children_property()) {
112            for t in children.iter() {
113                matrix *= t.value();
114            }
115        }
116        this.last_matrix.set(Some(matrix));
117        matrix
118    }
119}
120
121crate::ferro_properties! { impl TransformGroup {
122    ferro_property!(pub fn children_property() -> StyledProperty<Option<Transforms>> {
123        FerroProperty::register::<TransformGroup, _>("Children", None)
124    });
125} }
126
127impl TransformGroup {
128    /// Creates the class data.
129    pub fn construct() -> Self {
130        Self {
131            base: Transform::construct(),
132            children_notification_subscription: RefCell::new(None),
133            child_subscriptions: Rc::new(RefCell::new(Vec::new())),
134            last_matrix: Cell::new(None),
135        }
136    }
137
138    pub fn new() -> Ref<Self> {
139        instantiate(Self::construct())
140    }
141
142    fn on_transform_invalidated(&self) {
143        self.last_matrix.set(None);
144        self.raise_changed();
145    }
146
147    /// The children. Panics if the property has been cleared.
148    pub fn children(&self) -> Transforms {
149        self.get_value(Self::children_property()).expect("Children is not set")
150    }
151
152    pub fn set_children(&self, value: Transforms) {
153        self.set_value(Self::children_property(), Some(value))
154    }
155}