Skip to main content

ferroui_xaml_interactions_custom/carousel/
carousel_key_navigation_behavior.rs

1//! Port of `Carousel/CarouselKeyNavigationBehavior.cs`.
2
3use ferroui_base::input::{InputElement, Key, KeyEventArgs};
4use ferroui_base::interactivity::{Interactive, RoutedEventHandlerToken, RoutingStrategies};
5use ferroui_base::layout::Orientation;
6use ferroui_base::{
7    ferro_class, ferro_class_info, ferro_properties, instantiate, FerroObjectImpl, FerroProperty, Ref, StaticType,
8    StyledElementImpl, StyledProperty,
9};
10use ferroui_controls::Carousel;
11use ferroui_xaml_interactivity::{StyledElementBehaviorImpl, StyledElementBehaviorImplExt, StyledElementBehaviorOfT};
12use std::cell::Cell;
13
14/// Enables keyboard navigation for a [`Carousel`] using arrow keys.
15#[repr(C)]
16pub struct CarouselKeyNavigationBehavior {
17    base: StyledElementBehaviorOfT,
18    // The handler added to the associated object (the reference removes its delegate).
19    key_down_handler: Cell<Option<RoutedEventHandlerToken>>,
20}
21
22ferro_class!(CarouselKeyNavigationBehavior: StyledElementBehaviorOfT);
23ferro_class_info!(CarouselKeyNavigationBehavior { new: CarouselKeyNavigationBehavior::new });
24ferroui_base::ferro_impl_classes!(CarouselKeyNavigationBehavior: FerroObjectImpl, StyledElementImpl);
25
26ferro_properties! {
27    impl CarouselKeyNavigationBehavior {
28        /// Identifies the `Orientation` property.
29        pub fn orientation_property() -> StyledProperty<Orientation> {
30            FerroProperty::register::<CarouselKeyNavigationBehavior, _>("Orientation", Orientation::Horizontal)
31        }
32    }
33}
34
35ferroui_base::ferro_overrides! {
36    impl StyledElementBehaviorImpl for CarouselKeyNavigationBehavior {
37        fn on_attached(this: &Self) {
38            Self::parent_on_attached(this);
39
40            if let Some(associated_object) = this.associated_object_as::<Carousel>() {
41                let behavior = this.to_ref().downgrade();
42                let token = associated_object.add_handler_with(
43                    InputElement::key_down_event(),
44                    move |_sender: &Interactive, e: &KeyEventArgs| {
45                        if let Some(behavior) = behavior.upgrade() {
46                            behavior.on_key_down(e);
47                        }
48                    },
49                    RoutingStrategies::TUNNEL,
50                    false,
51                );
52                this.key_down_handler.set(Some(token));
53            }
54        }
55
56        fn on_detaching(this: &Self) {
57            if let Some(token) = this.key_down_handler.take() {
58                if let Some(associated_object) = this.associated_object_as::<Carousel>() {
59                    associated_object.remove_handler(InputElement::key_down_event(), token);
60                }
61            }
62
63            Self::parent_on_detaching(this);
64        }
65    }
66}
67
68impl CarouselKeyNavigationBehavior {
69    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
70    pub fn construct() -> Self {
71        Self { base: StyledElementBehaviorOfT::construct(<Carousel as StaticType>::TYPE), key_down_handler: Cell::new(None) }
72    }
73
74    /// Creates a behavior.
75    pub fn new() -> Ref<Self> {
76        instantiate(Self::construct())
77    }
78
79    /// Gets or sets the orientation used for navigation. This is a property of the object model.
80    pub fn orientation(&self) -> Orientation {
81        self.get_value(Self::orientation_property())
82    }
83
84    pub fn set_orientation(&self, value: Orientation) {
85        self.set_value(Self::orientation_property(), value)
86    }
87
88    fn on_key_down(&self, e: &KeyEventArgs) {
89        if !self.is_enabled() {
90            return;
91        }
92        let Some(associated_object) = self.associated_object_as::<Carousel>() else {
93            return;
94        };
95
96        let (next_key, previous_key) = match self.orientation() {
97            Orientation::Horizontal => (Key::Right, Key::Left),
98            Orientation::Vertical => (Key::Down, Key::Up),
99        };
100
101        if e.key == next_key {
102            associated_object.next();
103            e.set_handled(true);
104        } else if e.key == previous_key {
105            associated_object.previous();
106            e.set_handled(true);
107        }
108    }
109}