Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add debounce action #627

Merged
merged 2 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use std::sync::atomic::AtomicU64;

use floem_reactive::SignalWith;
use floem_winit::window::ResizeDirection;
use peniko::kurbo::{Point, Size, Vec2};

Expand Down Expand Up @@ -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
Expand All @@ -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<T, F>(signal: impl SignalWith<T> + '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<TimerToken>)>| {
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<TimerToken>)| {
// 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:
Expand Down
5 changes: 4 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions src/app_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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<UserEvent>) {
if self.timers.is_empty() {
return;
Expand Down