diff --git a/README.md b/README.md index 731a641..3ffb5b4 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ - [x] use_channel - [x] use_window_size - [x] use_interval + - [x] use_debounce - [ ] use_timeout - - [ ] use_debouncer - [ ] Camera - [ ] WiFi - [ ] Bluetooth diff --git a/examples/interval/src/main.rs b/examples/interval/src/main.rs deleted file mode 100644 index ef28e29..0000000 --- a/examples/interval/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use dioxus::prelude::*; -use dioxus_sdk::utils::interval::use_interval; -use std::time::Duration; - -fn main() { - // init debug tool for WebAssembly - wasm_logger::init(wasm_logger::Config::default()); - console_error_panic_hook::set_once(); - - launch(app); -} - -fn app() -> Element { - let mut count = use_signal(|| 0); - - use_interval(Duration::from_millis(100), move || { - count += 1; - }); - - rsx!( p { "{count}" } ) -} diff --git a/examples/interval/Cargo.toml b/examples/timing/Cargo.toml similarity index 54% rename from examples/interval/Cargo.toml rename to examples/timing/Cargo.toml index f891737..becf3d7 100644 --- a/examples/interval/Cargo.toml +++ b/examples/timing/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "interval" +name = "timing" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["interval"] } -dioxus = { workspace = true, features = ["web"] } +dioxus-sdk = { workspace = true, features = ["timing"] } +dioxus = { workspace = true, features = ["desktop"] } log = "0.4.6" diff --git a/examples/interval/Dioxus.toml b/examples/timing/Dioxus.toml similarity index 100% rename from examples/interval/Dioxus.toml rename to examples/timing/Dioxus.toml diff --git a/examples/interval/README.md b/examples/timing/README.md similarity index 100% rename from examples/interval/README.md rename to examples/timing/README.md diff --git a/examples/interval/public/favicon.ico b/examples/timing/public/favicon.ico similarity index 100% rename from examples/interval/public/favicon.ico rename to examples/timing/public/favicon.ico diff --git a/examples/timing/src/main.rs b/examples/timing/src/main.rs new file mode 100644 index 0000000..732761c --- /dev/null +++ b/examples/timing/src/main.rs @@ -0,0 +1,35 @@ +use dioxus::prelude::*; +use dioxus_sdk::utils::timing::{use_debounce, use_interval}; +use std::time::Duration; + +fn main() { + // init debug tool for WebAssembly + wasm_logger::init(wasm_logger::Config::default()); + console_error_panic_hook::set_once(); + + launch(app); +} + +fn app() -> Element { + let mut count = use_signal(|| 0); + + use_interval(Duration::from_millis(100), move || { + count += 1; + }); + + let mut debounce = use_debounce(Duration::from_millis(2000), move |text| { + println!("{text}"); + count.set(0); + }); + + rsx! { + p { "{count}" }, + button { + onclick: move |_| { + // Reset the counter after 2 seconds pass since the last click. + debounce.action("button was clicked"); + }, + "Reset the counter! (2 second debounce)" + } + } +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 568df2a..cac84b2 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -47,7 +47,7 @@ use_window_size = [ # Desktop "dep:dioxus-desktop", - + # Wasm "web-sys/Window", "dep:wasm-bindgen", @@ -80,21 +80,25 @@ storage = [ # Not WASM "dep:directories", ] -interval = [ +timing = [ + # Shared + "dep:futures", + # Desktop - "dep:tokio", + "dep:tokio", "tokio/time", - + # Wasm - "dep:gloo-timers" + "dep:gloo-timers", ] # CI testing wasm-testing = [ - "geolocation", "color_scheme", + "geolocation", "channel", "use_window_size", + "timing", "i18n", ] desktop-testing = [ @@ -104,6 +108,7 @@ desktop-testing = [ "channel", "use_window_size", "i18n", + "timing", ] @@ -146,10 +151,10 @@ dioxus-signals = { version = "0.5.0-alpha.2", features = [ yazi = { version = "0.1.4", optional = true } tracing = "0.1.40" -# Used by: interval +# Used by: timing gloo-timers = { version = "0.3.0", optional = true } -# Used by: interval & storage +# Used by: timing & storage tokio = { version = "1.33.0", optional = true } # # # # # # # # # diff --git a/sdk/src/clipboard/mod.rs b/sdk/src/clipboard/mod.rs index f51ee17..7437962 100644 --- a/sdk/src/clipboard/mod.rs +++ b/sdk/src/clipboard/mod.rs @@ -1,3 +1,8 @@ -mod use_clipboard; - -pub use use_clipboard::*; +cfg_if::cfg_if! { + if #[cfg(not(target_family = "wasm"))] { + mod use_clipboard; + pub use use_clipboard::*; + } else { + compile_error!("the `clipboard` feature is only available on desktop targets"); + } +} diff --git a/sdk/src/color_scheme/mod.rs b/sdk/src/color_scheme/mod.rs index d28f503..7c2e002 100644 --- a/sdk/src/color_scheme/mod.rs +++ b/sdk/src/color_scheme/mod.rs @@ -1,3 +1,8 @@ -mod use_preferred_color_scheme; - -pub use use_preferred_color_scheme::*; +cfg_if::cfg_if! { + if #[cfg(target_family = "wasm")] { + mod use_preferred_color_scheme; + pub use use_preferred_color_scheme::*; + } else { + compile_error!("the `color_scheme` feature is only available on wasm targets"); + } +} diff --git a/sdk/src/geolocation/mod.rs b/sdk/src/geolocation/mod.rs index 79a3ec8..29008e7 100644 --- a/sdk/src/geolocation/mod.rs +++ b/sdk/src/geolocation/mod.rs @@ -7,6 +7,6 @@ cfg_if::cfg_if! { pub use self::use_geolocation::*; } else { - compile_error!("The geolocation module is not supported on this platform."); + compile_error!("the `geolocation` feature is only available on wasm and windows targets"); } } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 1956181..3124655 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -13,7 +13,7 @@ cfg_if::cfg_if! { } cfg_if::cfg_if! { - if #[cfg(any(feature = "channel", feature = "use_window_size", feature = "interval"))] { + if #[cfg(any(feature = "channel", feature = "use_window_size", feature = "timing"))] { pub mod utils; } } diff --git a/sdk/src/notification/mod.rs b/sdk/src/notification/mod.rs index 392d39d..9058d0d 100644 --- a/sdk/src/notification/mod.rs +++ b/sdk/src/notification/mod.rs @@ -1 +1,7 @@ -pub mod notification; \ No newline at end of file +cfg_if::cfg_if! { + if #[cfg(not(target_family = "wasm"))] { + pub mod notification; + } else { + compile_error!("the `notification` feature is only available on desktop targets"); + } +} \ No newline at end of file diff --git a/sdk/src/utils/mod.rs b/sdk/src/utils/mod.rs index b82a342..5b8e0a1 100644 --- a/sdk/src/utils/mod.rs +++ b/sdk/src/utils/mod.rs @@ -13,7 +13,7 @@ cfg_if::cfg_if! { } cfg_if::cfg_if! { - if #[cfg(feature = "interval")] { - pub mod interval; + if #[cfg(feature = "timing")] { + pub mod timing; } } diff --git a/sdk/src/utils/timing/debounce.rs b/sdk/src/utils/timing/debounce.rs new file mode 100644 index 0000000..6f62fa6 --- /dev/null +++ b/sdk/src/utils/timing/debounce.rs @@ -0,0 +1,89 @@ +use dioxus::prelude::*; +use futures::{ + channel::mpsc::{self, UnboundedSender as Sender}, + StreamExt, +}; +use std::time::Duration; + +/// The interface for calling a debounce. +/// +/// See [`use_debounce`] for more information. +pub struct UseDebounce { + sender: Signal>, +} + +impl UseDebounce { + /// Will start the debounce countdown, resetting it if already started. + pub fn action(&mut self, data: T) { + self.sender.write().unbounded_send(data).ok(); + } +} + +// Manually implement Clone, Copy, and PartialEq as #[derive] thinks that T needs to implement these (it doesn't). + +impl Clone for UseDebounce { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for UseDebounce {} + +impl PartialEq for UseDebounce { + fn eq(&self, other: &Self) -> bool { + self.sender == other.sender + } +} + +/// A hook for allowing a function to be called only after a provided [`Duration`] has passed. +/// +/// Once the [`UseDebounce::action`] method is called, a timer will start counting down until +/// the callback is ran. If the [`UseDebounce::action`] method is called again, the timer will restart. +/// +/// # Example +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_sdk::utils::timing::use_debounce; +/// use std::time::Duration; +/// +/// fn App() -> Element { +/// let mut debounce = use_debounce(Duration::from_millis(2000), |_| println!("ran")); +/// +/// rsx! { +/// button { +/// onclick: move |_| { +/// debounce.action(()); +/// }, +/// "Click!" +/// } +/// } +/// } +/// ``` +pub fn use_debounce(time: Duration, cb: impl FnOnce(T) + Copy + 'static) -> UseDebounce { + use_hook(|| { + let (sender, mut receiver) = mpsc::unbounded(); + let debouncer = UseDebounce { + sender: Signal::new(sender), + }; + + spawn(async move { + let mut current_task: Option = None; + + loop { + if let Some(data) = receiver.next().await { + if let Some(task) = current_task.take() { + task.cancel(); + } + + current_task = Some(spawn(async move { + tokio::time::sleep(time).await; + cb(data); + })); + } + } + }); + + debouncer + }) +} diff --git a/sdk/src/utils/interval/mod.rs b/sdk/src/utils/timing/interval.rs similarity index 99% rename from sdk/src/utils/interval/mod.rs rename to sdk/src/utils/timing/interval.rs index 1672b3b..208d607 100644 --- a/sdk/src/utils/interval/mod.rs +++ b/sdk/src/utils/timing/interval.rs @@ -1,6 +1,5 @@ -use std::time::Duration; - use dioxus::prelude::{use_hook, Writable}; +use std::time::Duration; #[derive(Clone, PartialEq, Copy)] pub struct UseInterval { diff --git a/sdk/src/utils/timing/mod.rs b/sdk/src/utils/timing/mod.rs new file mode 100644 index 0000000..113f4a4 --- /dev/null +++ b/sdk/src/utils/timing/mod.rs @@ -0,0 +1,7 @@ +//! Timing utilities. + +mod interval; +pub use interval::*; + +mod debounce; +pub use debounce::*;