Skip to main content

ferroui_xaml_interactivity/styled_element/
styled_element_trigger.rs

1//! Port of `StyledElement/StyledElementTrigger.cs`.
2
3use crate::{
4    ActionCollection, IBehavior, ITrigger, StyledElementAction, StyledElementBehavior, StyledElementBehaviorImpl,
5    StyledElementBehaviorImplExt,
6};
7use ferroui_base::{
8    ferro_class, ferro_class_info, ferro_properties, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
9    FerroPropertyChangedEventArgs, Ref, StyledElement, StyledElementImpl, StyledProperty,
10};
11use ferroui_controls::TopLevel;
12
13/// A base class for behaviors, implementing the basic plumbing of [`ITrigger`].
14///
15/// The actions are a styled property, so a style can replace them (an
16/// [`ActionCollectionTemplate`](crate::ActionCollectionTemplate) in a setter). While the trigger
17/// is in the logical tree its styled element actions are its logical children (one way), so they
18/// bind against the trigger and the host.
19#[repr(C)]
20pub struct StyledElementTrigger {
21    base: StyledElementBehavior,
22}
23
24ferro_class!(StyledElementTrigger: StyledElementBehavior);
25ferro_class_info!(StyledElementTrigger {
26    markup: {
27        content: Actions,
28    },
29});
30ferroui_base::ferro_impl_classes!(StyledElementTrigger: StyledElementImpl);
31
32ferro_properties! {
33    impl StyledElementTrigger {
34        /// Identifies the `Actions` property.
35        pub fn actions_property() -> StyledProperty<Option<ActionCollection>> {
36            FerroProperty::register::<StyledElementTrigger, _>("Actions", None)
37        }
38    }
39}
40
41ferroui_base::ferro_overrides! {
42    impl FerroObjectImpl for StyledElementTrigger {
43        /// The constructor body: the trigger starts with an empty collection of actions, set as
44        /// the current value so that styles can replace it.
45        fn constructed(this: &Self) {
46            Self::parent_constructed(this);
47            this.set_current_value(Self::actions_property(), Some(ActionCollection::new()));
48        }
49
50        fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
51            Self::parent_on_property_changed(this, change);
52
53            if change.property() == Self::actions_property().as_property() {
54                let old_actions = change.get_old_value::<Option<ActionCollection>>().flatten();
55                let new_actions = change.get_new_value::<Option<ActionCollection>>();
56
57                if let Some(old_actions) = &old_actions {
58                    if this.is_initialized() {
59                        this.detach_actions_from_logical_tree(Some(old_actions));
60                    }
61                }
62
63                if let Some(new_actions) = &new_actions {
64                    if this.is_initialized() {
65                        this.attach_actions_to_logical_tree(Some(new_actions));
66                    }
67                }
68            }
69        }
70    }
71}
72
73ferroui_base::ferro_overrides! {
74    impl StyledElementBehaviorImpl for StyledElementTrigger {
75        fn initialize(this: &Self) {
76            Self::parent_initialize(this);
77
78            this.initialize_actions(this.actions().as_ref());
79        }
80
81        fn attach_behavior_to_logical_tree(this: &Self) {
82            Self::parent_attach_behavior_to_logical_tree(this);
83
84            this.attach_actions_to_logical_tree(this.actions().as_ref());
85        }
86
87        fn detach_behavior_from_logical_tree(this: &Self) {
88            this.detach_actions_from_logical_tree(this.actions().as_ref());
89
90            Self::parent_detach_behavior_from_logical_tree(this);
91        }
92    }
93}
94
95impl StyledElementTrigger {
96    /// The static constructor: the class implements the trigger contract.
97    fn static_constructor() {
98        <dyn ITrigger>::register::<StyledElementTrigger>();
99    }
100
101    /// Creates the class data; see [`FerroObject::construct`].
102    pub fn construct() -> Self {
103        Self { base: StyledElementBehavior::construct() }
104    }
105
106    /// Gets or sets the collection of actions associated with the behavior. This is a property
107    /// of the object model.
108    pub fn actions(&self) -> Option<ActionCollection> {
109        self.get_value(Self::actions_property())
110    }
111
112    pub fn set_actions(&self, value: Option<ActionCollection>) {
113        self.set_value(Self::actions_property(), value)
114    }
115
116    fn attach_actions_to_logical_tree(&self, actions: Option<&ActionCollection>) {
117        let Some(actions) = actions else {
118            return;
119        };
120
121        let parent: Ref<StyledElement> = match self.associated_object().and_then(|o| o.cast::<TopLevel>()) {
122            Some(top_level) => top_level.upcast(),
123            None => {
124                if self.parent().is_none() {
125                    return;
126                }
127
128                self.to_ref().upcast()
129            }
130        };
131
132        for action in actions.snapshot().iter() {
133            if let Some(styled_element_action) = action.downcast_ref::<StyledElementAction>() {
134                styled_element_action.attach_action_to_logical_tree(&parent);
135            }
136        }
137    }
138
139    fn detach_actions_from_logical_tree(&self, actions: Option<&ActionCollection>) {
140        let Some(actions) = actions else {
141            return;
142        };
143
144        let parent: &StyledElement = self;
145
146        for action in actions.snapshot().iter() {
147            if let Some(styled_element_action) = action.downcast_ref::<StyledElementAction>() {
148                styled_element_action.detach_action_from_logical_tree(parent);
149            }
150        }
151    }
152
153    fn initialize_actions(&self, actions: Option<&ActionCollection>) {
154        let Some(actions) = actions else {
155            return;
156        };
157
158        for action in actions.snapshot().iter() {
159            if let Some(styled_element_action) = action.downcast_ref::<StyledElementAction>() {
160                styled_element_action.initialize();
161            }
162        }
163    }
164}
165
166impl IBehavior for StyledElementTrigger {
167    fn associated_object(&self) -> Option<Ref<FerroObject>> {
168        StyledElementBehavior::associated_object(self)
169    }
170
171    fn attach(&self, associated_object: Option<Ref<FerroObject>>) {
172        StyledElementBehavior::attach(self, associated_object)
173    }
174
175    fn detach(&self) {
176        StyledElementBehavior::detach(self)
177    }
178}
179
180impl ITrigger for StyledElementTrigger {
181    fn actions(&self) -> Option<ActionCollection> {
182        StyledElementTrigger::actions(self)
183    }
184}