ferroui_xaml_interactions/core/
timer_trigger.rs1use ferroui_base::reactive::IDisposable;
4use ferroui_base::threading::DispatcherTimer;
5use ferroui_base::utilities::EventArgs;
6use ferroui_base::{
7 ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObjectImpl, FerroProperty, Ref,
8 StyledElementImpl, StyledProperty,
9};
10use ferroui_xaml_interactivity::{
11 EventTriggerBase, EventTriggerBaseImpl, Interaction, StyledElementBehaviorImpl, StyledElementBehaviorImplExt,
12};
13use std::cell::{Cell, RefCell};
14use std::rc::Rc;
15use std::time::Duration;
16
17#[repr(C)]
22pub struct TimerTrigger {
23 base: EventTriggerBase,
24 timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
25 current_tick: Cell<i32>,
26}
27
28ferro_class!(TimerTrigger: EventTriggerBase);
29ferro_class_info!(TimerTrigger { new: TimerTrigger::new });
30ferroui_base::ferro_impl_classes!(TimerTrigger: FerroObjectImpl, StyledElementImpl);
31
32ferro_properties! {
33 impl TimerTrigger {
34 pub fn milliseconds_per_tick_property() -> StyledProperty<i32> {
36 FerroProperty::register::<TimerTrigger, _>("MillisecondsPerTick", 1000)
37 }
38
39 pub fn total_ticks_property() -> StyledProperty<i32> {
41 FerroProperty::register::<TimerTrigger, _>("TotalTicks", 1)
42 }
43
44 pub fn repeat_forever_property() -> StyledProperty<bool> {
46 FerroProperty::register::<TimerTrigger, _>("RepeatForever", false)
47 }
48 }
49}
50
51ferroui_base::ferro_overrides! {
52 impl StyledElementBehaviorImpl for TimerTrigger {
53 fn on_attached(this: &Self) {
54 Self::parent_on_attached(this);
55 this.start_timer();
56 }
57
58 fn on_detaching(this: &Self) {
59 this.stop_timer();
60 Self::parent_on_detaching(this);
61 }
62 }
63}
64
65ferroui_base::ferro_overrides! {
66 impl EventTriggerBaseImpl for TimerTrigger {
67 fn on_event(this: &Self, _event_args: Option<&BoxedValue>) {
69 this.stop_timer();
70 this.start_timer();
71 }
72 }
73}
74
75impl TimerTrigger {
76 pub fn construct() -> Self {
78 Self { base: EventTriggerBase::construct(), timer: RefCell::new(None), current_tick: Cell::new(0) }
79 }
80
81 pub fn new() -> Ref<Self> {
83 instantiate(Self::construct())
84 }
85
86 pub fn milliseconds_per_tick(&self) -> i32 {
88 self.get_value(Self::milliseconds_per_tick_property())
89 }
90
91 pub fn set_milliseconds_per_tick(&self, value: i32) {
92 self.set_value(Self::milliseconds_per_tick_property(), value)
93 }
94
95 pub fn total_ticks(&self) -> i32 {
97 self.get_value(Self::total_ticks_property())
98 }
99
100 pub fn set_total_ticks(&self, value: i32) {
101 self.set_value(Self::total_ticks_property(), value)
102 }
103
104 pub fn repeat_forever(&self) -> bool {
106 self.get_value(Self::repeat_forever_property())
107 }
108
109 pub fn set_repeat_forever(&self, value: bool) {
110 self.set_value(Self::repeat_forever_property(), value)
111 }
112
113 fn start_timer(&self) {
114 if self.timer.borrow().is_some() {
115 return;
116 }
117
118 self.current_tick.set(0);
119 let milliseconds_per_tick = self.milliseconds_per_tick();
120 if milliseconds_per_tick < 0 {
121 panic!("TimeSpan period must be greater than or equal to zero. (Parameter 'value')");
122 }
123
124 let timer = DispatcherTimer::new();
125 timer.set_interval(Duration::from_millis(milliseconds_per_tick as u64));
126 let trigger = self.to_ref().downgrade();
127 let tick = timer.tick(move |_timer: &DispatcherTimer| {
128 if let Some(trigger) = trigger.upgrade() {
129 trigger.on_tick();
130 }
131 });
132 timer.start();
133 *self.timer.borrow_mut() = Some((timer, tick));
134 }
135
136 fn stop_timer(&self) {
137 let timer = self.timer.borrow_mut().take();
138 let Some((timer, tick)) = timer else {
139 return;
140 };
141
142 tick.dispose();
143 timer.stop();
144 }
145
146 fn on_tick(&self) {
147 self.current_tick.set(self.current_tick.get() + 1);
148 self.execute(Some(&(Rc::new(EventArgs::EMPTY) as BoxedValue)));
149
150 if !self.repeat_forever() && self.current_tick.get() >= self.total_ticks() {
151 self.stop_timer();
152 }
153 }
154
155 fn execute(&self, parameter: Option<&BoxedValue>) {
156 if !self.is_enabled() {
157 return;
158 }
159
160 let sender = self.associated_object().map(|associated_object| Rc::new(associated_object) as BoxedValue);
161 Interaction::execute_actions(sender.as_ref(), self.actions().as_ref(), parameter);
162 }
163}