diff --git a/src/action.rs b/src/action.rs index 4e49a43e..da32a721 100644 --- a/src/action.rs +++ b/src/action.rs @@ -8,6 +8,7 @@ use std::sync::atomic::AtomicU64; +use floem_reactive::SignalWith; use floem_winit::window::ResizeDirection; use peniko::kurbo::{Point, Size, Vec2}; @@ -107,6 +108,11 @@ impl TimerToken { pub const fn into_raw(self) -> u64 { self.0 } + + /// Cancel a timer + pub fn cancel(self) { + add_app_update_event(AppUpdateEvent::CancelTimer { timer: self }); + } } /// Execute a callback after a specified duration @@ -131,6 +137,43 @@ pub fn exec_after(duration: Duration, action: impl FnOnce(TimerToken) + 'static) token } +/// Debounce an action +/// +/// This tracks a signal and checks if the inner value has changed by checking it's hash and will run the action only once an **uninterrupted** duration has passed +pub fn debounce_action(signal: impl SignalWith + 'static, duration: Duration, action: F) +where + T: std::hash::Hash + 'static, + F: Fn() + Clone + 'static, +{ + crate::reactive::create_stateful_updater( + move |prev_opt: Option<(u64, Option)>| { + use std::hash::Hasher; + let mut hasher = std::hash::DefaultHasher::new(); + signal.with(|v| v.hash(&mut hasher)); + let hash = hasher.finish(); + let execute = prev_opt + .map(|(prev_hash, _)| prev_hash != hash) + .unwrap_or(true); + (execute, (hash, prev_opt.and_then(|(_, timer)| timer))) + }, + move |execute, (hash, prev_timer): (u64, Option)| { + // Cancel the previous timer if it exists + if let Some(timer) = prev_timer { + timer.cancel(); + } + let timer_token = if execute { + let action = action.clone(); + Some(exec_after(duration, move |_| { + action(); + })) + } else { + None + }; + (hash, timer_token) + }, + ); +} + /// Show a system context menu at the specified position /// /// Platform support: diff --git a/src/app.rs b/src/app.rs index 0655d409..6f3f41b8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -11,7 +11,7 @@ use parking_lot::Mutex; use raw_window_handle::HasRawDisplayHandle; use crate::{ - action::Timer, + action::{Timer, TimerToken}, app_handle::ApplicationHandle, clipboard::Clipboard, inspector::Capture, @@ -76,6 +76,9 @@ pub(crate) enum AppUpdateEvent { RequestTimer { timer: Timer, }, + CancelTimer { + timer: TimerToken, + }, #[cfg(any(target_os = "linux", target_os = "freebsd"))] MenuAction { window_id: WindowId, diff --git a/src/app_handle.rs b/src/app_handle.rs index 2b65ccd3..dd279f8b 100644 --- a/src/app_handle.rs +++ b/src/app_handle.rs @@ -92,6 +92,9 @@ impl ApplicationHandle { AppUpdateEvent::RequestTimer { timer } => { self.request_timer(timer, event_loop); } + AppUpdateEvent::CancelTimer { timer } => { + self.remove_timer(&timer); + } AppUpdateEvent::CaptureWindow { window_id, capture } => { capture.set(self.capture_window(window_id).map(Rc::new)); } @@ -467,6 +470,10 @@ impl ApplicationHandle { self.fire_timer(event_loop); } + fn remove_timer(&mut self, timer: &TimerToken) { + self.timers.remove(timer); + } + fn fire_timer(&mut self, event_loop: &EventLoopWindowTarget) { if self.timers.is_empty() { return;