Skip to main content

ferroui_xaml_interactions_custom/button/
button_click_event_trigger_behavior.rs

1//! Port of `Button/ButtonClickEventTriggerBehavior.cs`.
2
3use ferroui_base::input::{InputElement, KeyEventArgs, KeyModifiers};
4use ferroui_base::interactivity::{
5    IRoutedEventArgs, Interactive, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
6};
7use ferroui_base::{
8    ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObjectImpl, FerroProperty, Ref,
9    StaticType, StyledElementImpl, StyledProperty,
10};
11use ferroui_controls::Button;
12use ferroui_xaml_interactivity::{Interaction, StyledElementBehaviorImpl, StyledElementTriggerOfT};
13use std::cell::Cell;
14use std::rc::Rc;
15
16/// The handlers the trigger adds to its button, removed by their tokens.
17#[derive(Clone, Copy)]
18struct Handlers {
19    click: RoutedEventHandlerToken,
20    key_down: RoutedEventHandlerToken,
21    key_up: RoutedEventHandlerToken,
22}
23
24/// A behavior that listens for a [`Button::click_event`] event on its source and executes its
25/// actions when that event is fired.
26///
27/// The args of the click are the parameter of the actions.
28#[repr(C)]
29pub struct ButtonClickEventTriggerBehavior {
30    base: StyledElementTriggerOfT,
31    saved_key_modifiers: Cell<KeyModifiers>,
32    // The handlers are removed by their tokens (a closure has no identity to remove it by, as the
33    // reference removes its methods).
34    handlers: Cell<Option<Handlers>>,
35}
36
37ferro_class!(ButtonClickEventTriggerBehavior: StyledElementTriggerOfT);
38ferro_class_info!(ButtonClickEventTriggerBehavior { new: ButtonClickEventTriggerBehavior::new });
39ferroui_base::ferro_impl_classes!(ButtonClickEventTriggerBehavior: FerroObjectImpl, StyledElementImpl);
40
41ferro_properties! {
42    impl ButtonClickEventTriggerBehavior {
43        /// Identifies the `KeyModifiers` property.
44        pub fn key_modifiers_property() -> StyledProperty<KeyModifiers> {
45            FerroProperty::register::<ButtonClickEventTriggerBehavior, _>("KeyModifiers", KeyModifiers::NONE)
46        }
47    }
48}
49
50ferroui_base::ferro_overrides! {
51    impl StyledElementBehaviorImpl for ButtonClickEventTriggerBehavior {
52        fn on_attached_to_visual_tree(this: &Self) {
53            let Some(associated_object) = this.associated_object_as::<Button>() else {
54                return;
55            };
56
57            this.remove_handlers(&associated_object);
58
59            let trigger = this.to_ref().downgrade();
60            let click = associated_object.click(move |_sender: &Interactive, e: &RoutedEventArgs| {
61                if let Some(trigger) = trigger.upgrade() {
62                    trigger.associated_object_on_click(e);
63                }
64            });
65
66            let trigger = this.to_ref().downgrade();
67            let key_down = associated_object.add_handler_with(
68                InputElement::key_down_event(),
69                move |_sender: &Interactive, e: &KeyEventArgs| {
70                    if let Some(trigger) = trigger.upgrade() {
71                        trigger.button_on_key_down(e);
72                    }
73                },
74                RoutingStrategies::TUNNEL,
75                false,
76            );
77
78            let trigger = this.to_ref().downgrade();
79            let key_up = associated_object.add_handler_with(
80                InputElement::key_up_event(),
81                move |_sender: &Interactive, e: &KeyEventArgs| {
82                    if let Some(trigger) = trigger.upgrade() {
83                        trigger.button_on_key_up(e);
84                    }
85                },
86                RoutingStrategies::TUNNEL,
87                false,
88            );
89
90            this.handlers.set(Some(Handlers { click, key_down, key_up }));
91        }
92
93        fn on_detached_from_visual_tree(this: &Self) {
94            if let Some(associated_object) = this.associated_object_as::<Button>() {
95                this.remove_handlers(&associated_object);
96            }
97        }
98    }
99}
100
101impl ButtonClickEventTriggerBehavior {
102    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
103    pub fn construct() -> Self {
104        Self {
105            base: StyledElementTriggerOfT::construct(<Button as StaticType>::TYPE),
106            saved_key_modifiers: Cell::new(KeyModifiers::NONE),
107            handlers: Cell::new(None),
108        }
109    }
110
111    /// Creates a trigger.
112    pub fn new() -> Ref<Self> {
113        instantiate(Self::construct())
114    }
115
116    /// Gets or sets the required key modifiers to execute [`Button::click_event`] event handler.
117    /// This is a property of the object model.
118    pub fn key_modifiers(&self) -> KeyModifiers {
119        self.get_value(Self::key_modifiers_property())
120    }
121
122    pub fn set_key_modifiers(&self, value: KeyModifiers) {
123        self.set_value(Self::key_modifiers_property(), value)
124    }
125
126    fn remove_handlers(&self, button: &Button) {
127        if let Some(handlers) = self.handlers.take() {
128            button.remove_handler(Button::click_event(), handlers.click);
129            button.remove_handler(InputElement::key_down_event(), handlers.key_down);
130            button.remove_handler(InputElement::key_up_event(), handlers.key_up);
131        }
132    }
133
134    fn associated_object_on_click(&self, e: &RoutedEventArgs) {
135        // The args are the parameter of the actions (a shared handle to the live args).
136        let parameter: BoxedValue = Rc::new(IRoutedEventArgs::share(e));
137        self.execute(Some(&parameter));
138    }
139
140    fn execute(&self, parameter: Option<&BoxedValue>) {
141        if !self.is_enabled() {
142            return;
143        }
144
145        if let Some(associated_object) = self.associated_object() {
146            if self.key_modifiers() == self.saved_key_modifiers.get() {
147                let sender: BoxedValue = Rc::new(associated_object);
148                Interaction::execute_actions(Some(&sender), self.actions().as_ref(), parameter);
149            }
150        }
151    }
152
153    fn button_on_key_down(&self, e: &KeyEventArgs) {
154        self.saved_key_modifiers.set(e.key_modifiers);
155    }
156
157    fn button_on_key_up(&self, _e: &KeyEventArgs) {
158        self.saved_key_modifiers.set(KeyModifiers::NONE);
159    }
160}