diff --git a/examples/shortcut.rs b/examples/shortcut.rs index d671e62a0..69d3fdb1b 100644 --- a/examples/shortcut.rs +++ b/examples/shortcut.rs @@ -8,10 +8,7 @@ fn main() { fn app() -> Element { let toggled = use_signal(|| false); - use_global_shortcut("ctrl+s", { - to_owned![toggled]; - move || toggled.modify(|t| !*t) - }); + use_global_shortcut("ctrl+s", move || toggled.toggle()); - rsx!("toggle: {toggled.get()}") + rsx!("toggle: {toggled}") } diff --git a/examples/svg.rs b/examples/svg.rs index df69b7ad2..4297bee61 100644 --- a/examples/svg.rs +++ b/examples/svg.rs @@ -1,103 +1,52 @@ // Thanks to @japsu and their project https://github.com/japsu/jatsi for the example! use dioxus::prelude::*; +use rand::{thread_rng, Rng}; fn main() { dioxus_desktop::launch(app); } fn app() -> Element { - let val = use_signal(|| 5); - rsx! { - div { - user_select: "none", - webkit_user_select: "none", - margin_left: "10%", - margin_right: "10%", + div { user_select: "none", webkit_user_select: "none", margin_left: "10%", margin_right: "10%", h1 { "Click die to generate a new value" } - div { - cursor: "pointer", - height: "80%", - width: "80%", - Die { - value: **val, - keep: true, - onclick: move |_| { - use rand::Rng; - let mut rng = rand::thread_rng(); - val.set(rng.gen_range(1..=6)); - } - } - } + div { cursor: "pointer", height: "100%", width: "100%", Dice {} } } } } -#[derive(Props)] -pub struct DieProps<'a> { - pub value: u64, - pub keep: bool, - pub onclick: EventHandler<'a, MouseEvent>, -} +#[component] +fn Dice() -> Element { + const Y: bool = true; + const N: bool = false; + const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)]; + const DOTS_FOR_VALUE: [[bool; 7]; 6] = [ + [N, N, N, N, N, N, Y], + [N, N, Y, Y, N, N, N], + [N, N, Y, Y, N, N, Y], + [Y, N, Y, Y, N, Y, N], + [Y, N, Y, Y, N, Y, Y], + [Y, Y, Y, Y, Y, Y, N], + ]; -const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)]; -const DOTS_FOR_VALUE: [[bool; 7]; 6] = [ - [false, false, false, false, false, false, true], - [false, false, true, true, false, false, false], - [false, false, true, true, false, false, true], - [true, false, true, true, false, true, false], - [true, false, true, true, false, true, true], - [true, true, true, true, true, true, false], -]; + let value = use_signal(|| 5); + let active_dots = use_selector(move || &DOTS_FOR_VALUE[(*value() - 1) as usize]); -const OFFSET: i64 = 600; -const DOT_RADIUS: &str = "200"; -const HELD_COLOR: &str = "#aaa"; -const UNHELD_COLOR: &str = "#ddd"; - -// A six-sided die (D6) with dots. -#[allow(non_snake_case)] -pub fn Die(props: DieProps) -> Element { - let &DieProps { value, keep, .. } = cx.props; - - let active_dots = &DOTS_FOR_VALUE[(value - 1) as usize]; - let fill = if keep { HELD_COLOR } else { UNHELD_COLOR }; - let dots = DOTS - .iter() - .zip(active_dots.iter()) - .filter(|(_, &active)| active) - .map(|((x, y), _)| { - let dcx = x * OFFSET; - let dcy = y * OFFSET; - - rsx! { + rsx! { + svg { + view_box: "-1000 -1000 2000 2000", + prevent_default: "onclick", + onclick: move |e| value.set(thread_rng().gen_range(1..=6)), + rect { x: -1000, y: -1000, width: 2000, height: 2000, rx: 200, fill: "#aaa" } + for ((x, y), _) in DOTS.iter().zip(active_dots.read().iter()).filter(|(_, &active)| active) { circle { - cx: "{dcx}", - cy: "{dcy}", - r: "{DOT_RADIUS}", + cx: *x * 600, + cy: *y * 600, + r: 200, fill: "#333" } } - }); - - rsx! { - svg { - onclick: move |e| cx.props.onclick.call(e), - prevent_default: "onclick", - class: "die", - view_box: "-1000 -1000 2000 2000", - - rect { - x: "-1000", - y: "-1000", - width: "2000", - height: "2000", - rx: "{DOT_RADIUS}", - fill: "{fill}", } - - {dots} - } } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 15abb552e..c159c5d92 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -867,6 +867,11 @@ impl IntoVNode for VNode { self } } +impl IntoVNode for &VNode { + fn into_vnode(self) -> VNode { + self.clone() + } +} impl IntoVNode for Element { fn into_vnode(self) -> VNode { match self { @@ -875,6 +880,14 @@ impl IntoVNode for Element { } } } +impl IntoVNode for &Element { + fn into_vnode(self) -> VNode { + match self { + Some(val) => val.into_vnode(), + _ => VNode::empty().unwrap(), + } + } +} // Note that we're using the E as a generic but this is never crafted anyways. pub struct FromNodeIterator; diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index b888d0cfb..a5af7633b 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -44,6 +44,7 @@ infer = "0.11.0" dunce = "1.0.2" slab = { workspace = true } rustc-hash = { workspace = true } +dioxus-hooks = { workspace = true } futures-util = { workspace = true } urlencoding = "2.1.2" diff --git a/packages/desktop/src/hooks.rs b/packages/desktop/src/hooks.rs index a269fe5cd..5080d2eb4 100644 --- a/packages/desktop/src/hooks.rs +++ b/packages/desktop/src/hooks.rs @@ -1,17 +1,21 @@ +use std::rc::Rc; + use crate::{ assets::*, ipc::UserWindowEvent, shortcut::IntoAccelerator, window, DesktopContext, ShortcutHandle, ShortcutRegistryError, WryEventHandler, }; -use dioxus_core::use_hook; +use dioxus_core::{ + prelude::{consume_context, current_scope_id}, + use_hook, +}; +use dioxus_hooks::use_on_drop; use tao::{event::Event, event_loop::EventLoopWindowTarget}; use wry::RequestAsyncResponder; -// /// Get an imperative handle to the current window -// pub fn use_window() -> &DesktopContext { -// cx.use_hook(|| cx.consume_context::()) -// .as_ref() -// .unwrap() -// } +/// Get an imperative handle to the current window +pub fn use_window() -> DesktopContext { + use_hook(|| consume_context::()).unwrap() +} /// Get a closure that executes any JavaScript in the WebView context. pub fn use_wry_event_handler( @@ -29,48 +33,42 @@ pub fn use_wry_event_handler( }) } -// /// Provide a callback to handle asset loading yourself. -// /// -// /// The callback takes a path as requested by the web view, and it should return `Some(response)` -// /// if you want to load the asset, and `None` if you want to fallback on the default behavior. -// pub fn use_asset_handler( -// , -// name: &str, -// handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static, -// ) { -// cx.use_hook(|| { -// crate::window().asset_handlers.register_handler( -// name.to_string(), -// Box::new(handler), -// cx.scope_id(), -// ); +/// Provide a callback to handle asset loading yourself. +/// +/// The callback takes a path as requested by the web view, and it should return `Some(response)` +/// if you want to load the asset, and `None` if you want to fallback on the default behavior. +pub fn use_asset_handler( + name: &str, + handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static, +) { + let name = use_hook(|| { + crate::window().asset_handlers.register_handler( + name.to_string(), + Box::new(handler), + current_scope_id().unwrap(), + ); -// Handler(name.to_string()) -// }); + Rc::new(name.to_string()) + }); -// // todo: can we just put ondrop in core? -// struct Handler(String); -// impl Drop for Handler { -// fn drop(&mut self) { -// _ = crate::window().asset_handlers.remove_handler(&self.0); -// } -// } -// } + use_on_drop(move || { + _ = crate::window().asset_handlers.remove_handler(name.as_ref()); + }); +} -// /// Get a closure that executes any JavaScript in the WebView context. -// pub fn use_global_shortcut( -// , -// accelerator: impl IntoAccelerator, -// handler: impl FnMut() + 'static, -// ) -> &Result { -// cx.use_hook(move || { -// let desktop = window(); +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_global_shortcut( + accelerator: impl IntoAccelerator, + handler: impl FnMut() + 'static, +) -> Result { + use_hook(move || { + let desktop = window(); -// let id = desktop.create_shortcut(accelerator.accelerator(), handler); + let id = desktop.create_shortcut(accelerator.accelerator(), handler); -// Ok(ShortcutHandle { -// desktop, -// shortcut_id: id?, -// }) -// }) -// } + Ok(ShortcutHandle { + desktop, + shortcut_id: id?, + }) + }) +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index af605366f..b0c74662f 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -43,7 +43,7 @@ pub use config::{Config, WindowCloseBehaviour}; pub use desktop_context::{ window, DesktopContext, DesktopService, WryEventHandler, WryEventHandlerId, }; -// pub use hooks::{use_asset_handler, use_global_shortcut, use_window, use_wry_event_handler}; +pub use hooks::{use_asset_handler, use_global_shortcut, use_window, use_wry_event_handler}; pub use shortcut::{ShortcutHandle, ShortcutId, ShortcutRegistryError}; pub use wry::RequestAsyncResponder; diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 18c7283b0..0951b29d5 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -57,8 +57,8 @@ macro_rules! to_owned { // pub mod computed; -// mod use_on_destroy; -// pub use use_on_destroy::*; +mod use_on_destroy; +pub use use_on_destroy::*; // mod use_const; // pub use use_const::*; diff --git a/packages/hooks/src/use_on_destroy.rs b/packages/hooks/src/use_on_destroy.rs index 8c180a324..6cc3d23fa 100644 --- a/packages/hooks/src/use_on_destroy.rs +++ b/packages/hooks/src/use_on_destroy.rs @@ -1,9 +1,11 @@ +use dioxus_core::use_hook; + #[deprecated( note = "Use `use_on_destroy` instead, which has the same functionality. \ This is deprecated because of the introduction of `use_on_create` which is better mirrored by `use_on_destroy`. \ The reason why `use_on_create` is not `use_on_mount` is because of potential confusion with `dioxus::events::onmounted`." )] -pub fn use_on_unmount(cx: &dioxus_core::ScopeState, destroy: D) { +pub fn use_on_unmount(destroy: D) { use_on_destroy(destroy); } @@ -67,19 +69,34 @@ pub fn use_on_unmount(cx: &dioxus_core::ScopeState, destr /// } /// } /// ``` -pub fn use_on_destroy(cx: &dioxus_core::ScopeState, destroy: D) { - cx.use_hook(|| LifeCycle { +pub fn use_on_destroy(destroy: D) { + struct LifeCycle { + /// Wrap the closure in an option so that we can take it out on drop. + ondestroy: Option, + } + + /// On drop, we want to run the closure. + impl Drop for LifeCycle { + fn drop(&mut self) { + if let Some(f) = self.ondestroy.take() { + f(); + } + } + } + + // We need to impl clone for the lifecycle, but we don't want the drop handler for the closure to be called twice. + impl Clone for LifeCycle { + fn clone(&self) -> Self { + Self { ondestroy: None } + } + } + + use_hook(|| LifeCycle { ondestroy: Some(destroy), }); } -struct LifeCycle { - ondestroy: Option, -} - -impl Drop for LifeCycle { - fn drop(&mut self) { - let f = self.ondestroy.take().unwrap(); - f(); - } +/// Creates a callback that will be run before the component is dropped +pub fn use_on_drop(ondrop: D) { + use_on_destroy(ondrop); }