Skip to main content

ferroui_xaml_interactivity/styled_element/
styled_element_action.rs

1//! Port of `StyledElement/StyledElementAction.cs`.
2
3use crate::contract::InterfaceTable;
4use crate::helpers::TemplatedParentHelper;
5use crate::IAction;
6use ferroui_base::{
7    ferro_class, ferro_properties, BoxedValue, FerroObject, FerroObjectImpl, FerroProperty, ObjectType, StyledElement,
8    StyledElementImpl, StyledProperty,
9};
10
11/// A base class for action that calls a method on a specified object when invoked.
12///
13/// The action is a styled element: under a [`StyledElementTrigger`](crate::StyledElementTrigger)
14/// that is in the logical tree it is a logical child of the trigger (one way), so it binds with
15/// `$parent` and `TemplateBinding`.
16///
17/// The class is abstract: a deriving class implements [`StyledElementActionImpl::execute`].
18#[repr(C)]
19pub struct StyledElementAction {
20    base: StyledElement,
21}
22
23ferro_class! {
24    StyledElementAction: StyledElement, virtuals StyledElementActionImpl: StyledElementImpl {
25        /// Executes the action.
26        ///
27        /// `sender` is the object that is passed to the action by the behavior. Generally this is
28        /// the associated object of the behavior or a target object. The value of `parameter` is
29        /// determined by the caller. An example of parameter usage is EventTriggerBehavior,
30        /// which passes the event args as a parameter to its actions.
31        ///
32        /// Returns the result of the action.
33        fn execute(this, sender: Option<&BoxedValue>, parameter: Option<&BoxedValue>) -> Option<BoxedValue>;
34    }
35}
36
37ferroui_base::ferro_impl_classes!(StyledElementAction: FerroObjectImpl, StyledElementImpl);
38
39impl StyledElementActionImpl for StyledElementAction {
40    fn execute(_this: &Self, _sender: Option<&BoxedValue>, _parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
41        panic!("StyledElementAction is abstract: 'execute' must be implemented by the deriving class")
42    }
43}
44
45ferro_properties! {
46    impl StyledElementAction {
47        /// Identifies the `IsEnabled` property.
48        pub fn is_enabled_property() -> StyledProperty<bool> {
49            FerroProperty::register::<StyledElementAction, _>("IsEnabled", true)
50        }
51    }
52}
53
54impl StyledElementAction {
55    /// The static constructor: the class implements the action contract.
56    fn static_constructor() {
57        <dyn IAction>::register::<StyledElementAction>();
58    }
59
60    /// Creates the class data; see [`FerroObject::construct`].
61    pub fn construct() -> Self {
62        Self { base: StyledElement::construct() }
63    }
64
65    /// Gets or sets a value indicating whether this instance is enabled.
66    pub fn is_enabled(&self) -> bool {
67        self.get_value(Self::is_enabled_property())
68    }
69
70    pub fn set_is_enabled(&self, value: bool) {
71        self.set_value(Self::is_enabled_property(), value)
72    }
73
74    /// Initializes the action (an internal member of the reference).
75    #[doc(hidden)]
76    pub fn initialize(&self) {
77        self.initialize_if_needed();
78    }
79
80    /// Makes the action a logical child of `parent` (an internal member of the reference).
81    #[doc(hidden)]
82    pub fn attach_action_to_logical_tree(&self, parent: &StyledElement) {
83        // Required for $parent binding in markup.
84        self.set_parent(parent.to_ref());
85
86        // Required for TemplateBinding in markup.
87        if let Some(templated_parent) = parent.templated_parent() {
88            TemplatedParentHelper::set_templated_parent(self, Some(templated_parent));
89        }
90
91        if let Some(lifecycle) = <dyn IActionLogicalTreeLifecycle>::of(self) {
92            lifecycle.attached_to_action_logical_tree();
93        }
94    }
95
96    /// Removes the action from the logical tree (an internal member of the reference).
97    #[doc(hidden)]
98    pub fn detach_action_from_logical_tree(&self, parent: &StyledElement) {
99        if let Some(lifecycle) = <dyn IActionLogicalTreeLifecycle>::of(self) {
100            lifecycle.detached_from_action_logical_tree();
101        }
102
103        self.set_parent(None);
104
105        if self.templated_parent().is_some() || parent.templated_parent().is_some() {
106            TemplatedParentHelper::set_templated_parent(self, None);
107        }
108    }
109}
110
111impl IAction for StyledElementAction {
112    fn execute(&self, sender: Option<&BoxedValue>, parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
113        StyledElementAction::execute(self, sender, parameter)
114    }
115}
116
117/// The lifecycle of an action in the logical tree of its trigger (an internal contract of the
118/// reference, implemented by [`InvokeCommandActionBase`](crate::InvokeCommandActionBase)).
119#[doc(hidden)]
120pub trait IActionLogicalTreeLifecycle {
121    fn attached_to_action_logical_tree(&self);
122
123    fn detached_from_action_logical_tree(&self);
124}
125
126static TABLE: InterfaceTable<dyn IActionLogicalTreeLifecycle> = InterfaceTable::new();
127
128fn cast<'a, T: ObjectType + IActionLogicalTreeLifecycle>(
129    object: &'a FerroObject,
130) -> Option<&'a (dyn IActionLogicalTreeLifecycle + 'static)> {
131    object.downcast_ref::<T>().map(|object| object as &(dyn IActionLogicalTreeLifecycle + 'static))
132}
133
134impl dyn IActionLogicalTreeLifecycle {
135    /// States that the objects of class `T` (and of the classes deriving from it) implement the
136    /// contract. Called from the static constructor of the class.
137    pub fn register<T: ObjectType + IActionLogicalTreeLifecycle>() {
138        TABLE.register(T::TYPE, cast::<T>);
139    }
140
141    /// The object as an implementation of the contract, if its class is one.
142    pub fn of(object: &FerroObject) -> Option<&dyn IActionLogicalTreeLifecycle> {
143        TABLE.find(object)
144    }
145}