浏览代码

feat(core): expose `AppHandle`, add `create_window` API (#1855)

Lucas Fernandes Nogueira 4 年之前
父节点
当前提交
95d518afa1

+ 5 - 0
.changes/app-handle-create-window.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Adds `create_window` API to the `AppHandle` struct.

+ 5 - 0
.changes/app-handle.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Adds a `handle` function to the `App` struct, which returns a `Send` handle to the app instance.

+ 53 - 1
core/tauri-runtime-wry/src/lib.rs

@@ -13,7 +13,7 @@ use tauri_runtime::{
     dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
     DetachedWindow, PendingWindow, WindowEvent,
   },
-  Dispatch, Error, Icon, Params, Result, Runtime,
+  Dispatch, Error, Icon, Params, Result, Runtime, RuntimeHandle,
 };
 
 #[cfg(feature = "menu")]
@@ -539,6 +539,8 @@ impl Dispatch for WryDispatcher {
       .map_err(|_| Error::FailedToSendMessage)
   }
 
+  // Creates a window by dispatching a message to the event loop.
+  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
   fn create_window<P: Params<Runtime = Self::Runtime>>(
     &mut self,
     pending: PendingWindow<P>,
@@ -763,8 +765,46 @@ pub struct Wry {
   task_rx: Receiver<MainThreadTask>,
 }
 
+/// A handle to the Wry runtime.
+#[derive(Clone)]
+pub struct WryHandle {
+  dispatcher_context: DispatcherContext,
+}
+
+impl RuntimeHandle for WryHandle {
+  type Runtime = Wry;
+
+  // Creates a window by dispatching a message to the event loop.
+  // Note that this must be called from a separate thread, otherwise the channel will introduce a deadlock.
+  fn create_window<P: Params<Runtime = Self::Runtime>>(
+    &self,
+    pending: PendingWindow<P>,
+  ) -> Result<DetachedWindow<P>> {
+    let (tx, rx) = channel();
+    let label = pending.label.clone();
+    let dispatcher_context = self.dispatcher_context.clone();
+    self
+      .dispatcher_context
+      .proxy
+      .send_event(Message::CreateWebview(
+        Arc::new(Mutex::new(Some(Box::new(move |event_loop| {
+          create_webview(event_loop, dispatcher_context, pending)
+        })))),
+        tx,
+      ))
+      .map_err(|_| Error::FailedToSendMessage)?;
+    let window_id = rx.recv().unwrap();
+    let dispatcher = WryDispatcher {
+      window_id,
+      context: self.dispatcher_context.clone(),
+    };
+    Ok(DetachedWindow { label, dispatcher })
+  }
+}
+
 impl Runtime for Wry {
   type Dispatcher = WryDispatcher;
+  type Handle = WryHandle;
 
   fn new() -> Result<Self> {
     let event_loop = EventLoop::<Message>::with_user_event();
@@ -782,6 +822,18 @@ impl Runtime for Wry {
     })
   }
 
+  fn handle(&self) -> Self::Handle {
+    WryHandle {
+      dispatcher_context: DispatcherContext {
+        proxy: self.event_loop.create_proxy(),
+        task_tx: self.task_tx.clone(),
+        window_event_listeners: self.window_event_listeners.clone(),
+        #[cfg(feature = "menu")]
+        menu_event_listeners: self.menu_event_listeners.clone(),
+      },
+    }
+  }
+
   fn create_window<P: Params<Runtime = Self>>(
     &self,
     pending: PendingWindow<P>,

+ 15 - 0
core/tauri-runtime/src/lib.rs

@@ -104,14 +104,29 @@ pub struct SystemTrayEvent {
   pub menu_item_id: u32,
 }
 
+/// A [`Send`] handle to the runtime.
+pub trait RuntimeHandle: Send + Sized + Clone + 'static {
+  type Runtime: Runtime<Handle = Self>;
+  /// Create a new webview window.
+  fn create_window<P: Params<Runtime = Self::Runtime>>(
+    &self,
+    pending: PendingWindow<P>,
+  ) -> crate::Result<DetachedWindow<P>>;
+}
+
 /// The webview runtime interface.
 pub trait Runtime: Sized + 'static {
   /// The message dispatcher.
   type Dispatcher: Dispatch<Runtime = Self>;
+  /// The runtime handle type.
+  type Handle: RuntimeHandle<Runtime = Self>;
 
   /// Creates a new webview runtime.
   fn new() -> crate::Result<Self>;
 
+  /// Gets a runtime handle.
+  fn handle(&self) -> Self::Handle;
+
   /// Create a new webview window.
   fn create_window<P: Params<Runtime = Self>>(
     &self,

+ 66 - 29
core/tauri/src/app.rs

@@ -95,16 +95,32 @@ impl<P: Params> GlobalWindowEvent<P> {
 
 crate::manager::default_args! {
   /// A handle to the currently running application.
+  ///
+  /// This type implements [`Manager`] which allows for manipulation of global application items.
   pub struct AppHandle<P: Params> {
+    runtime_handle: <P::Runtime as Runtime>::Handle,
     manager: WindowManager<P>,
   }
 }
 
+impl<P: Params> Clone for AppHandle<P> {
+  fn clone(&self) -> Self {
+    Self {
+      runtime_handle: self.runtime_handle.clone(),
+      manager: self.manager.clone(),
+    }
+  }
+}
+
 impl<P: Params> Manager<P> for AppHandle<P> {}
 impl<P: Params> ManagerBase<P> for AppHandle<P> {
   fn manager(&self) -> &WindowManager<P> {
     &self.manager
   }
+
+  fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
+    RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
+  }
 }
 
 crate::manager::default_args! {
@@ -122,29 +138,51 @@ impl<P: Params> ManagerBase<P> for App<P> {
   fn manager(&self) -> &WindowManager<P> {
     &self.manager
   }
+
+  fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
+    RuntimeOrDispatch::Runtime(&self.runtime)
+  }
+}
+
+macro_rules! shared_app_impl {
+  ($app: ty) => {
+    impl<P: Params> $app {
+      /// Creates a new webview window.
+      pub fn create_window<F>(&self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()>
+      where
+        F: FnOnce(
+          <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
+          WebviewAttributes,
+        ) -> (
+          <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
+          WebviewAttributes,
+        ),
+      {
+        let (window_builder, webview_attributes) = setup(
+          <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
+          WebviewAttributes::new(url),
+        );
+        self.create_new_window(PendingWindow::new(
+          window_builder,
+          webview_attributes,
+          label,
+        ))?;
+        Ok(())
+      }
+    }
+  };
 }
 
+shared_app_impl!(App<P>);
+shared_app_impl!(AppHandle<P>);
+
 impl<P: Params> App<P> {
-  /// Creates a new webview window.
-  pub fn create_window<F>(&mut self, label: P::Label, url: WindowUrl, setup: F) -> crate::Result<()>
-  where
-    F: FnOnce(
-      <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
-      WebviewAttributes,
-    ) -> (
-      <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder,
-      WebviewAttributes,
-    ),
-  {
-    let (window_builder, webview_attributes) = setup(
-      <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
-      WebviewAttributes::new(url),
-    );
-    self.create_new_window(
-      RuntimeOrDispatch::Runtime(&self.runtime),
-      PendingWindow::new(window_builder, webview_attributes, label),
-    )?;
-    Ok(())
+  /// Gets a handle to the application instance.
+  pub fn handle(&self) -> AppHandle<P> {
+    AppHandle {
+      runtime_handle: self.runtime.handle(),
+      manager: self.manager.clone(),
+    }
   }
 }
 
@@ -577,17 +615,16 @@ where
         )
         .expect("failed to run tray");
       for listener in self.system_tray_event_listeners {
-        let app_handle = AppHandle {
-          manager: app.manager.clone(),
-        };
+        let app_handle = app.handle();
         let ids = ids.clone();
+        let listener = Arc::new(std::sync::Mutex::new(listener));
         app.runtime.on_system_tray_event(move |event| {
-          listener(
-            &app_handle,
-            SystemTrayEvent {
-              menu_item_id: ids.get(&event.menu_item_id).unwrap().clone(),
-            },
-          );
+          let app_handle = app_handle.clone();
+          let menu_item_id = ids.get(&event.menu_item_id).unwrap().clone();
+          let listener = listener.clone();
+          crate::async_runtime::spawn(async move {
+            listener.lock().unwrap()(&app_handle, SystemTrayEvent { menu_item_id });
+          });
         });
       }
     }

+ 11 - 4
core/tauri/src/lib.rs

@@ -60,7 +60,7 @@ pub use {
     config::{Config, WindowUrl},
     PackageInfo,
   },
-  self::app::{App, Builder, GlobalWindowEvent},
+  self::app::{App, AppHandle, Builder, GlobalWindowEvent},
   self::hooks::{
     Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
     PageLoadPayload, SetupHook,
@@ -316,13 +316,16 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
 /// Prevent implementation details from leaking out of the [`Manager`] trait.
 pub(crate) mod sealed {
   use crate::manager::WindowManager;
-  use tauri_runtime::{Params, Runtime};
+  use tauri_runtime::{Params, Runtime, RuntimeHandle};
 
   /// A running [`Runtime`] or a dispatcher to it.
   pub enum RuntimeOrDispatch<'r, P: Params> {
     /// Reference to the running [`Runtime`].
     Runtime(&'r P::Runtime),
 
+    /// Handle to the running [`Runtime`].
+    RuntimeHandle(<P::Runtime as Runtime>::Handle),
+
     /// A dispatcher to the running [`Runtime`].
     Dispatch(<P::Runtime as Runtime>::Dispatcher),
   }
@@ -332,17 +335,21 @@ pub(crate) mod sealed {
     /// The manager behind the [`Managed`] item.
     fn manager(&self) -> &WindowManager<P>;
 
+    fn runtime(&self) -> RuntimeOrDispatch<'_, P>;
+
     /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
     fn create_new_window(
       &self,
-      runtime: RuntimeOrDispatch<'_, P>,
       pending: crate::PendingWindow<P>,
     ) -> crate::Result<crate::Window<P>> {
       use crate::runtime::Dispatch;
       let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
       let pending = self.manager().prepare_window(pending, &labels)?;
-      match runtime {
+      match self.runtime() {
         RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending).map_err(Into::into),
+        RuntimeOrDispatch::RuntimeHandle(handle) => {
+          handle.create_window(pending).map_err(Into::into)
+        }
         RuntimeOrDispatch::Dispatch(mut dispatcher) => {
           dispatcher.create_window(pending).map_err(Into::into)
         }

+ 9 - 4
core/tauri/src/window.rs

@@ -136,6 +136,10 @@ impl<P: Params> ManagerBase<P> for Window<P> {
   fn manager(&self) -> &WindowManager<P> {
     &self.manager
   }
+
+  fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
+    RuntimeOrDispatch::Dispatch(self.dispatcher())
+  }
 }
 
 impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
@@ -171,10 +175,11 @@ impl<P: Params> Window<P> {
       <<P::Runtime as Runtime>::Dispatcher as Dispatch>::WindowBuilder::new(),
       WebviewAttributes::new(url),
     );
-    self.create_new_window(
-      RuntimeOrDispatch::Dispatch(self.dispatcher()),
-      PendingWindow::new(window_builder, webview_attributes, label),
-    )
+    self.create_new_window(PendingWindow::new(
+      window_builder,
+      webview_attributes,
+      label,
+    ))
   }
 
   /// The current window's dispatcher.

+ 21 - 9
examples/api/src-tauri/src/main.rs

@@ -11,7 +11,7 @@ mod cmd;
 mod menu;
 
 use serde::Serialize;
-use tauri::{CustomMenuItem, Manager, SystemTrayMenuItem};
+use tauri::{CustomMenuItem, Manager, SystemTrayMenuItem, WindowBuilder, WindowUrl};
 
 #[derive(Serialize)]
 struct Reply {
@@ -37,15 +37,27 @@ fn main() {
     .on_menu_event(|event| {
       println!("{:?}", event.menu_item_id());
     })
-    .system_tray(vec![SystemTrayMenuItem::Custom(CustomMenuItem::new(
-      "toggle".into(),
-      "Toggle",
-    ))])
+    .system_tray(vec![
+      SystemTrayMenuItem::Custom(CustomMenuItem::new("toggle".into(), "Toggle")),
+      SystemTrayMenuItem::Custom(CustomMenuItem::new("new".into(), "New window")),
+    ])
     .on_system_tray_event(|app, event| {
-      if event.menu_item_id() == "toggle" {
-        let window = app.get_window("main").unwrap();
-        // TODO: window.is_visible API
-        window.hide().unwrap();
+      match event.menu_item_id().as_str() {
+        "toggle" => {
+          let window = app.get_window("main").unwrap();
+          // TODO: window.is_visible API
+          window.hide().unwrap();
+        }
+        "new" => app
+          .create_window(
+            "new".into(),
+            WindowUrl::App("index.html".into()),
+            |window_builder, webview_attributes| {
+              (window_builder.title("Tauri"), webview_attributes)
+            },
+          )
+          .unwrap(),
+        _ => {}
       }
     })
     .invoke_handler(tauri::generate_handler![