1
0
Эх сурвалжийг харах

Merge pull request #751 from Demonthos/expose-event-handle-desktop

Create with_event_handler to expose window event on desktop
Jon Kelley 2 жил өмнө
parent
commit
f65b051102

+ 42 - 0
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!"
+                }
+            }
+        }
+    })
+}

+ 1 - 0
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"

+ 0 - 10
packages/desktop/src/cfg.rs

@@ -16,7 +16,6 @@ pub struct Config {
     pub(crate) file_drop_handler: Option<DropHandler>,
     pub(crate) protocols: Vec<WryProtocol>,
     pub(crate) pre_rendered: Option<String>,
-    // pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
     pub(crate) disable_context_menu: bool,
     pub(crate) resource_dir: Option<PathBuf>,
     pub(crate) custom_head: Option<String>,
@@ -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,

+ 134 - 0
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<UserWindowEvent>,
 
+    pub(crate) event_handlers: WindowEventHandlers,
+
     #[cfg(target_os = "ios")]
     pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
 }
@@ -76,6 +80,7 @@ impl DesktopContext {
         proxy: ProxyType,
         event_loop: EventLoopWindowTarget<UserWindowEvent>,
         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<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + '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<objc::runtime::Object>) {
@@ -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<RefCell<Slab<WryWindowEventHandlerInner>>>,
+}
+
+impl WindowEventHandlers {
+    pub(crate) fn add(
+        &self,
+        window_id: WindowId,
+        handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + '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<UserWindowEvent>,
+        target: &EventLoopWindowTarget<UserWindowEvent>,
+    ) {
+        for (_, handler) in self.handlers.borrow_mut().iter_mut() {
+            handler.apply_event(event, target);
+        }
+    }
+}
+
+struct WryWindowEventHandlerInner {
+    window_id: WindowId,
+    handler: WryEventHandlerCallback,
+}
+
+type WryEventHandlerCallback =
+    Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>;
+
+impl WryWindowEventHandlerInner {
+    fn apply_event(
+        &mut self,
+        event: &Event<UserWindowEvent>,
+        target: &EventLoopWindowTarget<UserWindowEvent>,
+    ) {
+        // 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<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + '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);
+    }
+}

+ 13 - 3
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<P: 'static>(root: Component<P>, props: P, cfg: Config)
     // Store them in a hashmap so we can remove them when they're closed
     let mut webviews = HashMap::<WindowId, WebviewHandler>::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<P: 'static>(root: Component<P>, 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<UserWindowEvent>,
     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();