Skip to content

Commit

Permalink
feat(core): expose AppHandle, add create_window API (#1855)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog authored May 18, 2021
1 parent 3d8dcbb commit 95d518a
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changes/app-handle-create-window.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Adds `create_window` API to the `AppHandle` struct.
5 changes: 5 additions & 0 deletions .changes/app-handle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Adds a `handle` function to the `App` struct, which returns a `Send` handle to the app instance.
54 changes: 53 additions & 1 deletion core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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();
Expand All @@ -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>,
Expand Down
15 changes: 15 additions & 0 deletions core/tauri-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
95 changes: 66 additions & 29 deletions core/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand All @@ -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(),
}
}
}

Expand Down Expand Up @@ -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 });
});
});
}
}
Expand Down
15 changes: 11 additions & 4 deletions core/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
}
Expand All @@ -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)
}
Expand Down
13 changes: 9 additions & 4 deletions core/tauri/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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.
Expand Down
30 changes: 21 additions & 9 deletions examples/api/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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![
Expand Down

0 comments on commit 95d518a

Please sign in to comment.