Skip to main content

ferroui_xaml_interactivity/ferro_object/
behavior.rs

1//! Port of `FerroObject/Behavior.cs`.
2
3use crate::{IBehavior, IBehaviorEventsHandler};
4use ferroui_base::{
5    ferro_class, ferro_properties, FerroObject, FerroObjectImpl, FerroProperty, Nullable, Ref, StyledProperty, WeakRef,
6};
7use std::cell::{Cell, RefCell};
8
9/// A base class for behaviors, implementing the basic plumbing of [`IBehavior`].
10///
11/// The class is abstract: deriving classes override the members of [`BehaviorImpl`].
12///
13/// Deviation (DEVIATIONS.md, Behaviors): the reference names the hooks of the attachment of the
14/// host to the logical tree `OnAttachedToLogicalTree` / `OnDetachedFromLogicalTree`; the styled
15/// element behaviors would hide the members of the same name of the styled element, so both
16/// families name them [`on_attached_to_logical_tree_event`](Behavior::on_attached_to_logical_tree_event)
17/// and [`on_detached_from_logical_tree_event`](Behavior::on_detached_from_logical_tree_event), as
18/// the reference names the other host hooks (`OnInitializedEvent`, `OnDataContextChangedEvent`).
19#[repr(C)]
20pub struct Behavior {
21    base: FerroObject,
22    // Deviation (DEVIATIONS.md, Behaviors): the host is held weakly (the host holds its
23    // behaviors through its behavior collection).
24    associated_object: RefCell<Option<WeakRef<FerroObject>>>,
25    is_attached_to_logical_tree: Cell<bool>,
26    is_attached_to_visual_tree: Cell<bool>,
27    is_initialized_notified: Cell<bool>,
28    is_loaded: Cell<bool>,
29}
30
31ferro_class! {
32    Behavior: FerroObject, virtuals BehaviorImpl: FerroObjectImpl {
33        /// Called after the behavior is attached to the associated object.
34        ///
35        /// Override this to hook up functionality to the associated object.
36        fn on_attached(this);
37        /// Called when the behavior is being detached from its associated object.
38        ///
39        /// Override this to unhook functionality from the associated object.
40        fn on_detaching(this);
41        /// Called after the associated object is attached to the visual tree.
42        ///
43        /// Invoked only when the associated object is a visual.
44        fn on_attached_to_visual_tree(this);
45        /// Called when the associated object is being detached from the visual tree.
46        ///
47        /// Invoked only when the associated object is a visual.
48        fn on_detached_from_visual_tree(this);
49        /// Called after the associated object is attached to the logical tree
50        /// (`OnAttachedToLogicalTree`).
51        ///
52        /// Invoked only when the associated object is a styled element.
53        fn on_attached_to_logical_tree_event(this);
54        /// Called when the associated object is being detached from the logical tree
55        /// (`OnDetachedFromLogicalTree`).
56        ///
57        /// Invoked only when the associated object is a styled element.
58        fn on_detached_from_logical_tree_event(this);
59        /// Called after the associated object is loaded.
60        ///
61        /// Invoked only when the associated object is a control.
62        fn on_loaded(this);
63        /// Called when the associated object is unloaded.
64        ///
65        /// Invoked only when the associated object is a control.
66        fn on_unloaded(this);
67        /// Called when the associated object is initialized.
68        ///
69        /// Invoked only when the associated object is a styled element.
70        fn on_initialized_event(this);
71        /// Called when the data context of the associated object changed.
72        ///
73        /// Invoked only when the associated object is a styled element.
74        fn on_data_context_changed_event(this);
75        /// Called when the resources of the associated object changed.
76        ///
77        /// Invoked only when the associated object is a styled element.
78        fn on_resources_changed_event(this);
79        /// Called when the actual theme variant of the associated object changed.
80        ///
81        /// Invoked only when the associated object is a styled element.
82        fn on_actual_theme_variant_changed_event(this);
83    }
84}
85
86ferroui_base::ferro_impl_classes!(Behavior: FerroObjectImpl);
87
88impl BehaviorImpl for Behavior {
89    fn on_attached(_this: &Self) {}
90
91    fn on_detaching(_this: &Self) {}
92
93    fn on_attached_to_visual_tree(_this: &Self) {}
94
95    fn on_detached_from_visual_tree(_this: &Self) {}
96
97    fn on_attached_to_logical_tree_event(_this: &Self) {}
98
99    fn on_detached_from_logical_tree_event(_this: &Self) {}
100
101    fn on_loaded(_this: &Self) {}
102
103    fn on_unloaded(_this: &Self) {}
104
105    fn on_initialized_event(_this: &Self) {}
106
107    fn on_data_context_changed_event(_this: &Self) {}
108
109    fn on_resources_changed_event(_this: &Self) {}
110
111    fn on_actual_theme_variant_changed_event(_this: &Self) {}
112}
113
114ferro_properties! {
115    impl Behavior {
116        /// Identifies the `IsEnabled` property.
117        pub fn is_enabled_property() -> StyledProperty<bool> {
118            FerroProperty::register::<Behavior, _>("IsEnabled", true)
119        }
120    }
121}
122
123impl Behavior {
124    /// The static constructor: the class implements the behavior contracts.
125    fn static_constructor() {
126        <dyn IBehavior>::register::<Behavior>();
127        <dyn IBehaviorEventsHandler>::register::<Behavior>();
128    }
129
130    /// Creates the class data; see [`FerroObject::construct`].
131    pub fn construct() -> Self {
132        Self {
133            base: FerroObject::construct(),
134            associated_object: RefCell::new(None),
135            is_attached_to_logical_tree: Cell::new(false),
136            is_attached_to_visual_tree: Cell::new(false),
137            is_initialized_notified: Cell::new(false),
138            is_loaded: Cell::new(false),
139        }
140    }
141
142    /// Gets the object to which the behavior is attached.
143    pub fn associated_object(&self) -> Option<Ref<FerroObject>> {
144        self.associated_object.borrow().as_ref().and_then(WeakRef::upgrade)
145    }
146
147    /// Gets or sets a value indicating whether this instance is enabled.
148    pub fn is_enabled(&self) -> bool {
149        self.get_value(Self::is_enabled_property())
150    }
151
152    pub fn set_is_enabled(&self, value: bool) {
153        self.set_value(Self::is_enabled_property(), value)
154    }
155
156    /// Attaches the behavior to the specified object.
157    ///
158    /// # Panics
159    /// Panics if the behavior is attached to another object.
160    pub fn attach(&self, associated_object: impl Into<Nullable<FerroObject>>) {
161        let associated_object = associated_object.into().0;
162        let current = self.associated_object();
163        if associated_object == current {
164            return;
165        }
166
167        if current.is_some() {
168            panic!("An instance of a behavior cannot be attached to more than one object at a time.");
169        }
170
171        debug_assert!(associated_object.is_some(), "Cannot attach the behavior to a null object.");
172        let Some(associated_object) = associated_object else {
173            panic!("Value cannot be null. (Parameter 'associatedObject')");
174        };
175        *self.associated_object.borrow_mut() = Some(associated_object.downgrade());
176
177        self.on_attached();
178    }
179
180    /// Detaches the behaviors from the [`associated_object`](Self::associated_object).
181    pub fn detach(&self) {
182        self.on_detaching();
183        self.is_attached_to_logical_tree.set(false);
184        self.is_attached_to_visual_tree.set(false);
185        self.is_initialized_notified.set(false);
186        self.is_loaded.set(false);
187        *self.associated_object.borrow_mut() = None;
188    }
189}
190
191impl IBehavior for Behavior {
192    fn associated_object(&self) -> Option<Ref<FerroObject>> {
193        Behavior::associated_object(self)
194    }
195
196    fn attach(&self, associated_object: Option<Ref<FerroObject>>) {
197        Behavior::attach(self, associated_object)
198    }
199
200    fn detach(&self) {
201        Behavior::detach(self)
202    }
203}
204
205impl IBehaviorEventsHandler for Behavior {
206    fn attached_to_visual_tree_event_handler(&self) {
207        if self.is_attached_to_visual_tree.get() {
208            return;
209        }
210
211        self.is_attached_to_visual_tree.set(true);
212        self.on_attached_to_visual_tree();
213    }
214
215    fn detached_from_visual_tree_event_handler(&self) {
216        if !self.is_attached_to_visual_tree.get() {
217            return;
218        }
219
220        self.is_attached_to_visual_tree.set(false);
221        self.on_detached_from_visual_tree();
222    }
223
224    fn attached_to_logical_tree_event_handler(&self) {
225        if self.is_attached_to_logical_tree.get() {
226            return;
227        }
228
229        self.is_attached_to_logical_tree.set(true);
230        self.on_attached_to_logical_tree_event();
231    }
232
233    fn detached_from_logical_tree_event_handler(&self) {
234        if !self.is_attached_to_logical_tree.get() {
235            return;
236        }
237
238        self.is_attached_to_logical_tree.set(false);
239        self.on_detached_from_logical_tree_event();
240    }
241
242    fn loaded_event_handler(&self) {
243        if self.is_loaded.get() {
244            return;
245        }
246
247        self.is_loaded.set(true);
248        self.on_loaded();
249    }
250
251    fn unloaded_event_handler(&self) {
252        if !self.is_loaded.get() {
253            return;
254        }
255
256        self.is_loaded.set(false);
257        self.on_unloaded();
258    }
259
260    fn initialized_event_handler(&self) {
261        if self.is_initialized_notified.get() {
262            return;
263        }
264
265        self.is_initialized_notified.set(true);
266        self.on_initialized_event();
267    }
268
269    fn data_context_changed_event_handler(&self) {
270        self.on_data_context_changed_event();
271    }
272
273    fn resources_changed_event_handler(&self) {
274        self.on_resources_changed_event();
275    }
276
277    fn actual_theme_variant_changed_event_handler(&self) {
278        self.on_actual_theme_variant_changed_event();
279    }
280}