Skip to main content

ferroui_controls/
repeat_button.rs

1use crate::primitives::TemplatedControlImpl;
2use crate::{Button, ButtonImpl, ContentControlImpl, ControlImpl};
3use ferroui_base::input::{
4    InputElementImpl, InputElementImplExt, Key, KeyEventArgs, MouseButton, PointerPressedEventArgs,
5    PointerReleasedEventArgs,
6};
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::LayoutableImpl;
9use ferroui_base::threading::DispatcherTimer;
10use ferroui_base::{
11    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt,
12    FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty, VisualImpl,
13};
14use std::cell::RefCell;
15use std::rc::Rc;
16use std::time::Duration;
17
18/// Represents a control that raises its `Click` event repeatedly when it is
19/// pressed and held.
20#[repr(C)]
21pub struct RepeatButton {
22    base: Button,
23    repeat_timer: RefCell<Option<Rc<DispatcherTimer>>>,
24}
25
26ferro_class!(RepeatButton: Button);
27ferroui_base::ferro_class_info!(RepeatButton { new: RepeatButton::new });
28ferro_impl_classes!(
29    RepeatButton: StyledElementImpl,
30    VisualImpl,
31    LayoutableImpl,
32    InteractiveImpl,
33    ControlImpl,
34    TemplatedControlImpl,
35    ContentControlImpl,
36    ButtonImpl
37);
38
39impl FerroObjectImpl for RepeatButton {
40    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
41        Self::parent_on_property_changed(this, change);
42
43        if change.property() == Button::is_pressed_property().as_property() && !change.get_new_value::<bool>() {
44            this.stop_timer();
45        }
46    }
47}
48
49impl InputElementImpl for RepeatButton {
50    fn on_key_down(this: &Self, e: &KeyEventArgs) {
51        Self::parent_on_key_down(this, e);
52
53        if e.key == Key::Space {
54            this.start_timer();
55        }
56    }
57
58    fn on_key_up(this: &Self, e: &KeyEventArgs) {
59        Self::parent_on_key_up(this, e);
60
61        this.stop_timer();
62    }
63
64    fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
65        Self::parent_on_pointer_pressed(this, e);
66
67        if e.get_current_point(Some(this)).properties.is_left_button_pressed {
68            this.start_timer();
69        }
70    }
71
72    fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
73        Self::parent_on_pointer_released(this, e);
74
75        if e.initial_press_mouse_button() == MouseButton::Left {
76            this.stop_timer();
77        }
78    }
79}
80
81ferroui_base::ferro_properties! { impl RepeatButton {
82    ferro_property!(
83        /// Defines the `Interval` property.
84        pub fn interval_property() -> StyledProperty<i32> {
85            FerroProperty::register::<RepeatButton, _>("Interval", 100)
86        }
87    );
88
89    ferro_property!(
90        /// Defines the `Delay` property.
91        pub fn delay_property() -> StyledProperty<i32> {
92            FerroProperty::register::<RepeatButton, _>("Delay", 300)
93        }
94    );
95} }
96
97impl RepeatButton {
98    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
99    pub fn construct() -> Self {
100        Self { base: Button::construct(), repeat_timer: RefCell::new(None) }
101    }
102
103    /// Creates a repeat button.
104    pub fn new() -> Ref<Self> {
105        instantiate(Self::construct())
106    }
107
108    /// The amount of time, in milliseconds, of repeating clicks.
109    pub fn interval(&self) -> i32 {
110        self.get_value(Self::interval_property())
111    }
112
113    pub fn set_interval(&self, value: i32) {
114        self.set_value(Self::interval_property(), value)
115    }
116
117    /// The amount of time, in milliseconds, to wait before repeating begins.
118    pub fn delay(&self) -> i32 {
119        self.get_value(Self::delay_property())
120    }
121
122    pub fn set_delay(&self, value: i32) {
123        self.set_value(Self::delay_property(), value)
124    }
125
126    /// A time span of the given number of milliseconds; a negative number
127    /// is taken as zero.
128    fn from_milliseconds(milliseconds: i32) -> Duration {
129        Duration::from_millis(u64::try_from(milliseconds).unwrap_or(0))
130    }
131
132    fn start_timer(&self) {
133        let existing = self.repeat_timer.borrow().clone();
134        let repeat_timer = match existing {
135            Some(repeat_timer) => repeat_timer,
136            None => {
137                let repeat_timer = DispatcherTimer::new();
138                let weak = self.to_ref().downgrade();
139                // The subscription lives as long as the timer.
140                let _ = repeat_timer.tick(move |_| {
141                    if let Some(this) = weak.upgrade() {
142                        this.repeat_timer_on_tick();
143                    }
144                });
145                *self.repeat_timer.borrow_mut() = Some(repeat_timer.clone());
146                repeat_timer
147            }
148        };
149
150        if repeat_timer.is_enabled() {
151            return;
152        }
153
154        repeat_timer.set_interval(Self::from_milliseconds(self.delay()));
155        repeat_timer.start();
156    }
157
158    fn repeat_timer_on_tick(&self) {
159        let interval = Self::from_milliseconds(self.interval());
160        let repeat_timer = self.repeat_timer.borrow().clone();
161        if let Some(repeat_timer) = repeat_timer {
162            if repeat_timer.interval() != interval {
163                repeat_timer.set_interval(interval);
164            }
165        }
166        self.on_click();
167    }
168
169    fn stop_timer(&self) {
170        let repeat_timer = self.repeat_timer.borrow().clone();
171        if let Some(repeat_timer) = repeat_timer {
172            repeat_timer.stop();
173        }
174    }
175}
176
177/// A running timer is kept alive by the dispatcher: stop it with the button.
178impl Drop for RepeatButton {
179    fn drop(&mut self) {
180        if let Some(repeat_timer) = self.repeat_timer.get_mut().take() {
181            repeat_timer.stop();
182        }
183    }
184}