Skip to main content

ferroui_controls/
spinner.rs

1use crate::primitives::TemplatedControlImpl;
2use crate::{ContentControl, ContentControlImpl, ControlImpl};
3use bitflags::bitflags;
4use ferroui_base::input::InputElementImpl;
5use ferroui_base::interactivity::{
6    Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
7};
8use ferroui_base::layout::LayoutableImpl;
9use ferroui_base::{
10    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, ferro_routed_event_args, FerroObjectImpl, FerroProperty, StyledElementImpl, StyledProperty, VisualImpl,
11};
12
13bitflags! {
14    /// Represents spin directions that are valid.
15    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16    pub struct ValidSpinDirections: i32 {
17        /// Can not increase nor decrease.
18        const NONE = 0;
19
20        /// Can increase.
21        const INCREASE = 1;
22
23        /// Can decrease.
24        const DECREASE = 2;
25    }
26}
27
28/// Represents spin directions that could be initiated by the end-user.
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
30#[repr(i32)]
31pub enum SpinDirection {
32    /// Represents a spin initiated by the end-user in order to increase a
33    /// value.
34    #[default]
35    Increase = 0,
36
37    /// Represents a spin initiated by the end-user in order to decrease a
38    /// value.
39    Decrease = 1,
40}
41
42/// Provides data for the spinner's `Spin` event.
43#[derive(Clone)]
44pub struct SpinEventArgs {
45    base: RoutedEventArgs,
46    direction: SpinDirection,
47    using_mouse_wheel: bool,
48}
49
50ferro_routed_event_args!(SpinEventArgs: RoutedEventArgs);
51
52impl SpinEventArgs {
53    /// Creates args for a spin in the given direction, with no routed event.
54    pub fn new(direction: SpinDirection) -> Self {
55        Self { base: RoutedEventArgs::new(), direction, using_mouse_wheel: false }
56    }
57
58    /// Creates args for a routed event and a spin in the given direction.
59    pub fn with_event<T: ?Sized>(routed_event: Option<&RoutedEvent<T>>, direction: SpinDirection) -> Self {
60        Self::with_event_and_mouse_wheel(routed_event, direction, false)
61    }
62
63    /// Creates args for a spin in the given direction, with no routed
64    /// event, stating whether the spin was initiated by a mouse wheel.
65    pub fn with_mouse_wheel(direction: SpinDirection, using_mouse_wheel: bool) -> Self {
66        Self { base: RoutedEventArgs::new(), direction, using_mouse_wheel }
67    }
68
69    /// Creates args for a routed event and a spin in the given direction,
70    /// stating whether the spin was initiated by a mouse wheel.
71    pub fn with_event_and_mouse_wheel<T: ?Sized>(
72        routed_event: Option<&RoutedEvent<T>>,
73        direction: SpinDirection,
74        using_mouse_wheel: bool,
75    ) -> Self {
76        let base = RoutedEventArgs::new();
77        base.set_routed_event(routed_event);
78        Self { base, direction, using_mouse_wheel }
79    }
80
81    /// The direction for the spin.
82    #[inline]
83    pub fn direction(&self) -> SpinDirection {
84        self.direction
85    }
86
87    /// Whether the spin event originated from a mouse wheel event.
88    #[inline]
89    pub fn using_mouse_wheel(&self) -> bool {
90        self.using_mouse_wheel
91    }
92}
93
94/// Base class for controls that represents controls that can spin.
95///
96/// The class is abstract: it has no `new`.
97#[repr(C)]
98pub struct Spinner {
99    base: ContentControl,
100}
101
102ferro_class! {
103    Spinner: ContentControl, virtuals SpinnerImpl: ContentControlImpl {
104        /// Called when the valid spin direction changed.
105        fn on_valid_spin_direction_changed(this, old_value: ValidSpinDirections, new_value: ValidSpinDirections);
106        /// Raises the `Spin` event when spinning is initiated by the
107        /// end-user.
108        fn on_spin(this, e: &SpinEventArgs);
109    }
110}
111
112ferro_impl_classes!(
113    Spinner: StyledElementImpl,
114    VisualImpl,
115    LayoutableImpl,
116    InteractiveImpl,
117    InputElementImpl,
118    ControlImpl,
119    TemplatedControlImpl,
120    ContentControlImpl
121);
122
123ferroui_base::ferro_impl_classes!(Spinner: FerroObjectImpl);
124
125impl SpinnerImpl for Spinner {
126    fn on_valid_spin_direction_changed(_this: &Self, _old_value: ValidSpinDirections, _new_value: ValidSpinDirections) {}
127
128    fn on_spin(this: &Self, e: &SpinEventArgs) {
129        let valid = if e.direction() == SpinDirection::Increase {
130            ValidSpinDirections::INCREASE
131        } else {
132            ValidSpinDirections::DECREASE
133        };
134
135        // Only raise the event if spin is allowed.
136        if this.valid_spin_direction() & valid == valid {
137            this.raise_event(e);
138        }
139    }
140}
141
142ferroui_base::ferro_properties! { impl Spinner {
143    ferro_property!(
144        /// Defines the `ValidSpinDirection` property.
145        pub fn valid_spin_direction_property() -> StyledProperty<ValidSpinDirections> {
146            FerroProperty::register::<Spinner, _>(
147                "ValidSpinDirection",
148                ValidSpinDirections::INCREASE | ValidSpinDirections::DECREASE,
149            )
150        }
151    );
152} }
153
154impl Spinner {
155    ferro_routed_event!(
156        /// Defines the `Spin` routed event.
157        pub fn spin_event() -> RoutedEvent<SpinEventArgs> {
158            RoutedEvent::register::<Spinner, _>("Spin", RoutingStrategies::BUBBLE)
159        }
160    );
161
162    fn static_constructor() {
163        Self::valid_spin_direction_property().changed().add_class_handler::<Spinner>(|spinner, e| {
164            let (old_value, new_value) = e.get_old_and_new_value::<ValidSpinDirections>();
165            spinner.on_valid_spin_direction_changed(old_value, new_value);
166        });
167    }
168
169    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
170    pub fn construct() -> Self {
171        Self { base: ContentControl::construct() }
172    }
173
174    /// Occurs when spinning is initiated by the end-user.
175    pub fn spin(&self, handler: impl Fn(&Interactive, &SpinEventArgs) + 'static) -> RoutedEventHandlerToken {
176        self.add_handler(Self::spin_event(), handler)
177    }
178
179    /// The valid spin directions.
180    pub fn valid_spin_direction(&self) -> ValidSpinDirections {
181        self.get_value(Self::valid_spin_direction_property())
182    }
183
184    pub fn set_valid_spin_direction(&self, value: ValidSpinDirections) {
185        self.set_value(Self::valid_spin_direction_property(), value)
186    }
187}