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 dcf032f10..3127a7639 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -1,7 +1,5 @@ use std::path::PathBuf; -use wry::application::event::Event; -use wry::application::event_loop::EventLoopWindowTarget; use wry::application::window::Icon; use wry::{ application::window::{Window, WindowBuilder}, @@ -10,18 +8,14 @@ use wry::{ Result as WryResult, }; -use crate::desktop_context::UserWindowEvent; - // pub(crate) type DynEventHandlerFn = dyn Fn(&mut EventLoop<()>, &mut WebView); /// The configuration for the desktop application. pub struct Config { pub(crate) window: WindowBuilder, pub(crate) file_drop_handler: Option, - pub(crate) event_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, @@ -30,7 +24,6 @@ pub struct Config { } type DropHandler = Box bool>; -type EventHandler = Box, &EventLoopWindowTarget)>; pub(crate) type WryProtocol = ( String, @@ -48,7 +41,6 @@ impl Config { window, protocols: Vec::new(), file_drop_handler: None, - event_handler: None, pre_rendered: None, disable_context_menu: !cfg!(debug_assertions), resource_dir: None, @@ -84,16 +76,6 @@ impl Config { self } - /// Set a custom wry event handler. This can be used to listen to window and webview events - /// This only has an effect on the main window - pub fn with_event_handler( - mut self, - handler: impl Fn(&Event, &EventLoopWindowTarget) + '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..bea8c4ecf 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,112 @@ 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: + 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.clone(), + 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 0fa27d99f..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}; @@ -104,7 +106,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// }) /// } /// ``` -pub fn launch_with_props(root: Component

, props: P, mut cfg: Config) { +pub fn launch_with_props(root: Component

, props: P, cfg: Config) { let event_loop = EventLoop::::with_user_event(); let proxy = event_loop.create_proxy(); @@ -123,9 +125,10 @@ pub fn launch_with_props(root: Component

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

, props: P, mut cfg: Conf &proxy, VirtualDom::new_with_props(root, props), &queue, + &event_handlers, )); event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; - if let Some(handler) = &event_handler { - handler(&window_event, event_loop); - } + event_handlers.apply_event(&window_event, event_loop); match window_event { Event::WindowEvent { @@ -246,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()); @@ -254,6 +257,7 @@ fn create_new_window( proxy.clone(), event_loop.clone(), queue.clone(), + event_handlers.clone(), )); let id = webview.window().id();