diff --git a/examples/window_focus.rs b/examples/window_focus.rs new file mode 100644 index 000000000..59b4d5166 --- /dev/null +++ b/examples/window_focus.rs @@ -0,0 +1,42 @@ +use dioxus::prelude::*; +use dioxus_desktop::tao::event::WindowEvent; +use dioxus_desktop::use_wry_event_handler; +use dioxus_desktop::wry::application::event::Event as WryEvent; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let focused = use_state(cx, || false); + + use_wry_event_handler(cx, { + to_owned![focused]; + move |event, _| { + if let WryEvent::WindowEvent { + event: WindowEvent::Focused(new_focused), + .. + } = event + { + focused.set(*new_focused); + } + } + }); + + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + display: "flex", + flex_direction: "column", + align_items: "center", + { + if *focused.get() { + "This window is focused!" + } else { + "This window is not focused!" + } + } + } + }) +} diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 93fd0b29a..9b008ef55 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -32,6 +32,7 @@ tokio = { version = "1.16.1", features = [ webbrowser = "0.8.0" infer = "0.11.0" dunce = "1.0.2" +slab = "0.4" interprocess = { version = "1.1.1", optional = true } futures-util = "0.3.25" diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 4456eb2d7..3127a7639 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -16,7 +16,6 @@ pub struct Config { pub(crate) file_drop_handler: Option, pub(crate) protocols: Vec, pub(crate) pre_rendered: Option, - // pub(crate) event_handler: Option>, pub(crate) disable_context_menu: bool, pub(crate) resource_dir: Option, pub(crate) custom_head: Option, @@ -77,15 +76,6 @@ impl Config { self } - // /// Set a custom event handler - // pub fn with_event_handler( - // mut self, - // handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static, - // ) -> Self { - // self.event_handler = Some(Box::new(handler)); - // self - // } - /// Set a file drop handler pub fn with_file_drop_handler( mut self, diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index d3b9216b0..c90e97354 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -10,6 +10,8 @@ use crate::WebviewHandler; use dioxus_core::ScopeState; use dioxus_core::VirtualDom; use serde_json::Value; +use slab::Slab; +use wry::application::event::Event; use wry::application::event_loop::EventLoopProxy; use wry::application::event_loop::EventLoopWindowTarget; #[cfg(target_os = "ios")] @@ -57,6 +59,8 @@ pub struct DesktopContext { pub(crate) event_loop: EventLoopWindowTarget, + pub(crate) event_handlers: WindowEventHandlers, + #[cfg(target_os = "ios")] pub(crate) views: Rc>>, } @@ -76,6 +80,7 @@ impl DesktopContext { proxy: ProxyType, event_loop: EventLoopWindowTarget, webviews: WebviewQueue, + event_handlers: WindowEventHandlers, ) -> Self { Self { webview, @@ -83,6 +88,7 @@ impl DesktopContext { event_loop, eval: tokio::sync::broadcast::channel(8).0, pending_windows: webviews, + event_handlers, #[cfg(target_os = "ios")] views: Default::default(), } @@ -102,6 +108,7 @@ impl DesktopContext { &self.proxy, dom, &self.pending_windows, + &self.event_handlers, ); let id = window.webview.window().id(); @@ -216,6 +223,22 @@ impl DesktopContext { EvalResult::new(self.eval.clone()) } + /// Create a wry event handler that listens for wry events. + /// This event handler is scoped to the currently active window and will only recieve events that are either global or related to the current window. + /// + /// The id this function returns can be used to remove the event handler with [`DesktopContext::remove_wry_event_handler`] + pub fn create_wry_event_handler( + &self, + handler: impl FnMut(&Event, &EventLoopWindowTarget) + 'static, + ) -> WryEventHandlerId { + self.event_handlers.add(self.id(), handler) + } + + /// Remove a wry event handler created with [`DesktopContext::create_wry_event_handler`] + pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) { + self.event_handlers.remove(id) + } + /// Push an objc view to the window #[cfg(target_os = "ios")] pub fn push_view(&self, view: objc_id::ShareId) { @@ -276,3 +299,114 @@ fn is_main_thread() -> bool { let result: BOOL = unsafe { msg_send![cls, isMainThread] }; result != NO } + +/// The unique identifier of a window event handler. This can be used to later remove the handler. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WryEventHandlerId(usize); + +#[derive(Clone, Default)] +pub(crate) struct WindowEventHandlers { + handlers: Rc>>, +} + +impl WindowEventHandlers { + pub(crate) fn add( + &self, + window_id: WindowId, + handler: impl FnMut(&Event, &EventLoopWindowTarget) + 'static, + ) -> WryEventHandlerId { + WryEventHandlerId( + self.handlers + .borrow_mut() + .insert(WryWindowEventHandlerInner { + window_id, + handler: Box::new(handler), + }), + ) + } + + pub(crate) fn remove(&self, id: WryEventHandlerId) { + self.handlers.borrow_mut().try_remove(id.0); + } + + pub(crate) fn apply_event( + &self, + event: &Event, + target: &EventLoopWindowTarget, + ) { + for (_, handler) in self.handlers.borrow_mut().iter_mut() { + handler.apply_event(event, target); + } + } +} + +struct WryWindowEventHandlerInner { + window_id: WindowId, + handler: WryEventHandlerCallback, +} + +type WryEventHandlerCallback = + Box, &EventLoopWindowTarget) + 'static>; + +impl WryWindowEventHandlerInner { + fn apply_event( + &mut self, + event: &Event, + target: &EventLoopWindowTarget, + ) { + // if this event does not apply to the window this listener cares about, return + match event { + Event::WindowEvent { window_id, .. } + | Event::MenuEvent { + window_id: Some(window_id), + .. + } => { + if *window_id != self.window_id { + return; + } + } + _ => (), + } + (self.handler)(event, target) + } +} + +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_wry_event_handler( + cx: &ScopeState, + handler: impl FnMut(&Event, &EventLoopWindowTarget) + 'static, +) -> &WryEventHandler { + let desktop = use_window(cx); + cx.use_hook(move || { + let desktop = desktop.clone(); + + let id = desktop.create_wry_event_handler(handler); + + WryEventHandler { + handlers: desktop.event_handlers, + id, + } + }) +} + +/// A wry event handler that is scoped to the current component and window. The event handler will only receive events for the window it was created for and global events. +/// +/// This will automatically be removed when the component is unmounted. +pub struct WryEventHandler { + handlers: WindowEventHandlers, + /// The unique identifier of the event handler. + pub id: WryEventHandlerId, +} + +impl WryEventHandler { + /// Remove the event handler. + pub fn remove(&self) { + self.handlers.remove(self.id); + } +} + +impl Drop for WryEventHandler { + fn drop(&mut self) { + self.handlers.remove(self.id); + } +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a75e6bbbb..9d3130fac 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -16,8 +16,10 @@ mod webview; mod hot_reload; pub use cfg::Config; -pub use desktop_context::{use_window, DesktopContext}; -use desktop_context::{EventData, UserWindowEvent, WebviewQueue}; +pub use desktop_context::{ + use_window, use_wry_event_handler, DesktopContext, WryEventHandler, WryEventHandlerId, +}; +use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers}; use dioxus_core::*; use dioxus_html::HtmlEvent; pub use eval::{use_eval, EvalResult}; @@ -123,6 +125,9 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) // Store them in a hashmap so we can remove them when they're closed let mut webviews = HashMap::::new(); + // We use this to allow dynamically adding and removing window event handlers + let event_handlers = WindowEventHandlers::default(); + let queue = WebviewQueue::default(); // By default, we'll create a new window when the app starts @@ -132,11 +137,14 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) &proxy, VirtualDom::new_with_props(root, props), &queue, + &event_handlers, )); - event_loop.run(move |window_event, _event_loop, control_flow| { + event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; + event_handlers.apply_event(&window_event, event_loop); + match window_event { Event::WindowEvent { event, window_id, .. @@ -240,6 +248,7 @@ fn create_new_window( proxy: &EventLoopProxy, dom: VirtualDom, queue: &WebviewQueue, + event_handlers: &WindowEventHandlers, ) -> WebviewHandler { let webview = webview::build(&mut cfg, event_loop, proxy.clone()); @@ -248,6 +257,7 @@ fn create_new_window( proxy.clone(), event_loop.clone(), queue.clone(), + event_handlers.clone(), )); let id = webview.window().id();