Make new_window asynchronous (#4297)
* Make new_window asynchronous * fix clippy
This commit is contained in:
1 parent
99a6b23df8
commit
e6ca0baa46
4 files changed
+118
-24
No files matched your search
@@ -5,7 +5,7 @@ use crate::{
|
|||||||
ipc::{IpcMessage, UserWindowEvent},
|
ipc::{IpcMessage, UserWindowEvent},
|
||||||
query::QueryResult,
|
query::QueryResult,
|
||||||
shortcut::ShortcutRegistry,
|
shortcut::ShortcutRegistry,
|
||||||
webview::WebviewInstance,
|
webview::{PendingWebview, WebviewInstance},
|
||||||
};
|
};
|
||||||
use dioxus_core::{ElementId, ScopeId, VirtualDom};
|
use dioxus_core::{ElementId, ScopeId, VirtualDom};
|
||||||
use dioxus_history::History;
|
use dioxus_history::History;
|
||||||
@@ -49,7 +49,7 @@ pub(crate) struct App {
|
|||||||
/// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.
|
/// A bundle of state shared between all the windows, providing a way for us to communicate with running webview.
|
||||||
pub(crate) struct SharedContext {
|
pub(crate) struct SharedContext {
|
||||||
pub(crate) event_handlers: WindowEventHandlers,
|
pub(crate) event_handlers: WindowEventHandlers,
|
||||||
pub(crate) pending_webviews: RefCell<Vec<WebviewInstance>>,
|
pub(crate) pending_webviews: RefCell<Vec<PendingWebview>>,
|
||||||
pub(crate) shortcut_manager: ShortcutRegistry,
|
pub(crate) shortcut_manager: ShortcutRegistry,
|
||||||
pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
|
pub(crate) proxy: EventLoopProxy<UserWindowEvent>,
|
||||||
pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,
|
pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,
|
||||||
@@ -177,9 +177,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_new_window(&mut self) {
|
pub fn handle_new_window(&mut self) {
|
||||||
for handler in self.shared.pending_webviews.borrow_mut().drain(..) {
|
for pending_webview in self.shared.pending_webviews.borrow_mut().drain(..) {
|
||||||
let id = handler.desktop_context.window.id();
|
let window = pending_webview.create_window(&self.shared);
|
||||||
self.webviews.insert(id, handler);
|
let id = window.desktop_context.window.id();
|
||||||
|
self.webviews.insert(id, window);
|
||||||
_ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));
|
_ = self.shared.proxy.send_event(UserWindowEvent::Poll(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ use crate::{
|
|||||||
ipc::UserWindowEvent,
|
ipc::UserWindowEvent,
|
||||||
query::QueryEngine,
|
query::QueryEngine,
|
||||||
shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
|
shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
|
||||||
webview::WebviewInstance,
|
webview::PendingWebview,
|
||||||
AssetRequest, Config, WryEventHandler,
|
AssetRequest, Config, WryEventHandler,
|
||||||
};
|
};
|
||||||
use dioxus_core::{
|
use dioxus_core::{prelude::Callback, VirtualDom};
|
||||||
prelude::{Callback, ScopeId},
|
use std::{
|
||||||
VirtualDom,
|
future::{Future, IntoFuture},
|
||||||
|
pin::Pin,
|
||||||
|
rc::{Rc, Weak},
|
||||||
};
|
};
|
||||||
use std::rc::{Rc, Weak};
|
|
||||||
use tao::{
|
use tao::{
|
||||||
event::Event,
|
event::Event,
|
||||||
event_loop::EventLoopWindowTarget,
|
event_loop::EventLoopWindowTarget,
|
||||||
@@ -99,21 +100,39 @@ impl DesktopService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new window using the props and window builder
|
/// Start the creation of a new window using the props and window builder
|
||||||
///
|
///
|
||||||
/// Returns the webview handle for the new window.
|
/// Returns a future that resolves to the webview handle for the new window. You can use this
|
||||||
///
|
/// to control other windows from the current window once the new window is created.
|
||||||
/// You can use this to control other windows from the current window.
|
|
||||||
///
|
///
|
||||||
/// Be careful to not create a cycle of windows, or you might leak memory.
|
/// Be careful to not create a cycle of windows, or you might leak memory.
|
||||||
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> WeakDesktopContext {
|
///
|
||||||
let window = WebviewInstance::new(cfg, dom, self.shared.clone());
|
/// # Example
|
||||||
|
///
|
||||||
let cx = window.dom.in_runtime(|| {
|
/// ```rust, no_run
|
||||||
ScopeId::ROOT
|
/// use dioxus::prelude::*;
|
||||||
.consume_context::<Rc<DesktopService>>()
|
/// fn popup() -> Element {
|
||||||
.unwrap()
|
/// rsx! {
|
||||||
});
|
/// div { "This is a popup window!" }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Create a new window with a component that will be rendered in the new window.
|
||||||
|
/// let dom = VirtualDom::new(popup);
|
||||||
|
/// // Create and wait for the window
|
||||||
|
/// let window = dioxus::desktop::window().new_window(dom, Default::default()).await;
|
||||||
|
/// // Fullscreen the new window
|
||||||
|
/// window.set_fullscreen(true);
|
||||||
|
/// ```
|
||||||
|
// Note: This method is asynchronous because webview2 does not support creating a new window from
|
||||||
|
// inside of an existing webview callback. Dioxus runs event handlers synchronously inside of a webview
|
||||||
|
// callback. See [this page](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#reentrancy) for more information.
|
||||||
|
//
|
||||||
|
// Related issues:
|
||||||
|
// - https://github.com/tauri-apps/wry/issues/583
|
||||||
|
// - https://github.com/DioxusLabs/dioxus/issues/3080
|
||||||
|
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> PendingDesktopContext {
|
||||||
|
let (window, context) = PendingWebview::new(dom, cfg);
|
||||||
|
|
||||||
self.shared
|
self.shared
|
||||||
.proxy
|
.proxy
|
||||||
@@ -122,7 +141,7 @@ impl DesktopService {
|
|||||||
|
|
||||||
self.shared.pending_webviews.borrow_mut().push(window);
|
self.shared.pending_webviews.borrow_mut().push(window);
|
||||||
|
|
||||||
Rc::downgrade(&cx)
|
context
|
||||||
}
|
}
|
||||||
|
|
||||||
/// trigger the drag-window event
|
/// trigger the drag-window event
|
||||||
@@ -300,3 +319,43 @@ fn is_main_thread() -> bool {
|
|||||||
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
|
let result: BOOL = unsafe { msg_send![cls, isMainThread] };
|
||||||
result != NO
|
result != NO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`DesktopContext`] that is pending creation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// // Create a new window asynchronously
|
||||||
|
/// let pending_context = desktop_service.new_window(dom, config);
|
||||||
|
/// // Wait for the context to be created
|
||||||
|
/// let window = pending_context.await;
|
||||||
|
/// window.set_fullscreen(true);
|
||||||
|
/// ```
|
||||||
|
pub struct PendingDesktopContext {
|
||||||
|
pub(crate) receiver: tokio::sync::oneshot::Receiver<DesktopContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingDesktopContext {
|
||||||
|
/// Resolve the pending context into a [`DesktopContext`].
|
||||||
|
pub async fn resolve(self) -> DesktopContext {
|
||||||
|
self.try_resolve()
|
||||||
|
.await
|
||||||
|
.expect("Failed to resolve pending desktop context")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to resolve the pending context into a [`DesktopContext`].
|
||||||
|
pub async fn try_resolve(
|
||||||
|
self,
|
||||||
|
) -> Result<DesktopContext, tokio::sync::oneshot::error::RecvError> {
|
||||||
|
self.receiver.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoFuture for PendingDesktopContext {
|
||||||
|
type Output = DesktopContext;
|
||||||
|
|
||||||
|
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
|
||||||
|
|
||||||
|
fn into_future(self) -> Self::IntoFuture {
|
||||||
|
Box::pin(self.resolve())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,9 @@ pub mod trayicon;
|
|||||||
// Public exports
|
// Public exports
|
||||||
pub use assets::AssetRequest;
|
pub use assets::AssetRequest;
|
||||||
pub use config::{Config, WindowCloseBehaviour};
|
pub use config::{Config, WindowCloseBehaviour};
|
||||||
pub use desktop_context::{window, DesktopContext, DesktopService, WeakDesktopContext};
|
pub use desktop_context::{
|
||||||
|
window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext,
|
||||||
|
};
|
||||||
pub use event_handlers::WryEventHandler;
|
pub use event_handlers::WryEventHandler;
|
||||||
pub use hooks::*;
|
pub use hooks::*;
|
||||||
pub use shortcut::{HotKeyState, ShortcutHandle, ShortcutRegistryError};
|
pub use shortcut::{HotKeyState, ShortcutHandle, ShortcutRegistryError};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::element::DesktopElement;
|
use crate::element::DesktopElement;
|
||||||
use crate::file_upload::DesktopFileDragEvent;
|
use crate::file_upload::DesktopFileDragEvent;
|
||||||
use crate::menubar::DioxusMenu;
|
use crate::menubar::DioxusMenu;
|
||||||
|
use crate::PendingDesktopContext;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::SharedContext,
|
app::SharedContext,
|
||||||
assets::AssetHandlerRegistry,
|
assets::AssetHandlerRegistry,
|
||||||
@@ -207,6 +208,7 @@ impl WebviewInstance {
|
|||||||
|
|
||||||
// https://developer.apple.com/documentation/appkit/nswindowcollectionbehavior/nswindowcollectionbehaviormanaged
|
// https://developer.apple.com/documentation/appkit/nswindowcollectionbehavior/nswindowcollectionbehaviormanaged
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
#[allow(deprecated)]
|
||||||
{
|
{
|
||||||
use cocoa::appkit::NSWindowCollectionBehavior;
|
use cocoa::appkit::NSWindowCollectionBehavior;
|
||||||
use cocoa::base::id;
|
use cocoa::base::id;
|
||||||
@@ -524,3 +526,33 @@ impl SynchronousEventResponse {
|
|||||||
Self { prevent_default }
|
Self { prevent_default }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A webview that is queued to be created. We can't spawn webviews outside of the main event loop because it may
|
||||||
|
/// block on windows so we queue them into the shared context and then create them when the main event loop is ready.
|
||||||
|
pub(crate) struct PendingWebview {
|
||||||
|
dom: VirtualDom,
|
||||||
|
cfg: Config,
|
||||||
|
sender: tokio::sync::oneshot::Sender<DesktopContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingWebview {
|
||||||
|
pub(crate) fn new(dom: VirtualDom, cfg: Config) -> (Self, PendingDesktopContext) {
|
||||||
|
let (sender, receiver) = tokio::sync::oneshot::channel();
|
||||||
|
let webview = Self { dom, cfg, sender };
|
||||||
|
let pending = PendingDesktopContext { receiver };
|
||||||
|
(webview, pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance {
|
||||||
|
let window = WebviewInstance::new(self.cfg, self.dom, shared.clone());
|
||||||
|
|
||||||
|
let cx = window.dom.in_runtime(|| {
|
||||||
|
ScopeId::ROOT
|
||||||
|
.consume_context::<Rc<DesktopService>>()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
_ = self.sender.send(cx);
|
||||||
|
|
||||||
|
window
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user