feat: return window

This commit is contained in:
Jonathan Kelley 2022-12-31 12:19:21 -05:00
parent 8ce1d8a70a
commit 22e71a71bd
4 changed files with 175 additions and 35 deletions

82
examples/compose.rs Normal file
View file

@ -0,0 +1,82 @@
//! This example shows how to create a popup window and send data back to the parent window.
use dioxus::prelude::*;
use dioxus_desktop::use_window;
use futures_util::StreamExt;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let window = use_window(cx);
let emails_sent = use_ref(cx, || vec![]);
let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| {
to_owned![emails_sent];
async move {
while let Some(message) = rx.next().await {
emails_sent.write().push(message);
}
}
});
cx.render(rsx! {
div {
h1 { "This is your email" }
button {
onclick: move |_| {
let dom = VirtualDom::new_with_props(compose, ComposeProps {
app_tx: tx.clone()
});
// this returns a weak reference to the other window
// Be careful not to keep a strong reference to the other window or it will never be dropped
// and the window will never close.
window.new_window(dom, Default::default());
},
"Click to compose a new email"
}
ul {
emails_sent.read().iter().map(|message| cx.render(rsx! {
li {
h3 { "email" }
span {"{message}"}
}
}))
}
}
})
}
struct ComposeProps {
app_tx: Coroutine<String>,
}
fn compose(cx: Scope<ComposeProps>) -> Element {
let user_input = use_state(cx, || String::new());
let window = use_window(cx);
cx.render(rsx! {
div {
h1 { "Compose a new email" }
button {
onclick: move |_| {
cx.props.app_tx.send(user_input.get().clone());
window.close();
},
"Click to send"
}
input {
oninput: move |e| {
user_input.set(e.value.clone());
},
value: "{user_input}"
}
}
})
}

View file

@ -1,13 +1,17 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::rc::Weak;
use crate::create_new_window;
use crate::eval::EvalResult;
use crate::events::IpcMessage;
use crate::Config;
use crate::WebviewHandler;
use dioxus_core::ScopeState;
use dioxus_core::VirtualDom;
use serde_json::Value;
use wry::application::event_loop::EventLoopProxy;
use wry::application::event_loop::EventLoopWindowTarget;
#[cfg(target_os = "ios")]
use wry::application::platform::ios::WindowExtIOS;
use wry::application::window::Fullscreen as WryFullscreen;
@ -24,7 +28,7 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
.unwrap()
}
pub type WebviewQueue = Rc<RefCell<Vec<(VirtualDom, crate::cfg::Config)>>>;
pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
/// An imperative interface to the current window.
///
@ -51,6 +55,8 @@ pub struct DesktopContext {
pub(super) pending_windows: WebviewQueue,
pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
#[cfg(target_os = "ios")]
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
}
@ -65,10 +71,16 @@ impl std::ops::Deref for DesktopContext {
}
impl DesktopContext {
pub(crate) fn new(webview: Rc<WebView>, proxy: ProxyType, webviews: WebviewQueue) -> Self {
pub(crate) fn new(
webview: Rc<WebView>,
proxy: ProxyType,
event_loop: EventLoopWindowTarget<UserWindowEvent>,
webviews: WebviewQueue,
) -> Self {
Self {
webview,
proxy,
event_loop,
eval: tokio::sync::broadcast::channel(8).0,
pending_windows: webviews,
#[cfg(target_os = "ios")]
@ -77,11 +89,36 @@ impl DesktopContext {
}
/// Create a new window using the props and window builder
pub fn new_window(&self, dom: VirtualDom, cfg: Config) {
self.pending_windows.borrow_mut().push((dom, cfg));
///
/// Returns the webview handle for the new window.
///
/// 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.
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> Weak<WebView> {
let window = create_new_window(
cfg,
&self.event_loop,
&self.proxy,
dom,
&self.pending_windows,
);
let id = window.webview.window().id();
self.proxy
.send_event(UserWindowEvent(EventData::NewWindow, self.id()))
.send_event(UserWindowEvent(EventData::NewWindow, id))
.unwrap();
self.proxy
.send_event(UserWindowEvent(EventData::Poll, id))
.unwrap();
let webview = window.webview.clone();
self.pending_windows.borrow_mut().push(window);
Rc::downgrade(&webview)
}
/// trigger the drag-window event
@ -115,6 +152,13 @@ impl DesktopContext {
.send_event(UserWindowEvent(EventData::CloseWindow, self.id()));
}
/// close window
pub fn close_window(&self, id: WindowId) {
let _ = self
.proxy
.send_event(UserWindowEvent(EventData::CloseWindow, id));
}
/// change window to fullscreen
pub fn set_fullscreen(&self, fullscreen: bool) {
if let Some(handle) = self.webview.window().current_monitor() {
@ -209,10 +253,10 @@ impl DesktopContext {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct UserWindowEvent(pub EventData, pub WindowId);
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum EventData {
Poll,

View file

@ -2,7 +2,7 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct IpcMessage {
method: String,
params: serde_json::Value,

View file

@ -17,16 +17,16 @@ mod hot_reload;
pub use cfg::Config;
pub use desktop_context::{use_window, DesktopContext};
use desktop_context::{EventData, UserWindowEvent};
use desktop_context::{EventData, UserWindowEvent, WebviewQueue};
use dioxus_core::*;
use dioxus_html::HtmlEvent;
pub use eval::{use_eval, EvalResult};
use futures_util::{pin_mut, FutureExt};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::task::Waker;
pub use tao::dpi::{LogicalSize, PhysicalSize};
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
pub use tao::window::WindowBuilder;
use tao::{
event::{Event, StartCause, WindowEvent},
@ -105,8 +105,6 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
/// }
/// ```
pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config) {
let mut _dom = VirtualDom::new_with_props(root, props);
let event_loop = EventLoop::<UserWindowEvent>::with_user_event();
let proxy = event_loop.create_proxy();
@ -125,9 +123,18 @@ 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();
let queue = Rc::new(RefCell::new(vec![(_dom, cfg)]));
let queue = WebviewQueue::default();
event_loop.run(move |window_event, event_loop, control_flow| {
// By default, we'll create a new window when the app starts
queue.borrow_mut().push(create_new_window(
cfg,
&event_loop,
&proxy,
VirtualDom::new_with_props(root, props),
&queue,
));
event_loop.run(move |window_event, _event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
match window_event {
@ -153,28 +160,9 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
Event::NewEvents(StartCause::Init)
| Event::UserEvent(UserWindowEvent(EventData::NewWindow, _)) => {
for (dom, mut cfg) in queue.borrow_mut().drain(..) {
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
dom.base_scope().provide_context(DesktopContext::new(
webview.clone(),
proxy.clone(),
queue.clone(),
));
let id = webview.window().id();
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
let waker = waker::tao_waker(&proxy, id);
let handler = WebviewHandler {
webview,
waker,
dom,
};
for handler in queue.borrow_mut().drain(..) {
let id = handler.webview.window().id();
webviews.insert(id, handler);
_ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
}
}
@ -246,6 +234,32 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
})
}
fn create_new_window(
mut cfg: Config,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
proxy: &EventLoopProxy<UserWindowEvent>,
dom: VirtualDom,
queue: &WebviewQueue,
) -> WebviewHandler {
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
dom.base_scope().provide_context(DesktopContext::new(
webview.clone(),
proxy.clone(),
event_loop.clone(),
queue.clone(),
));
let id = webview.window().id();
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
WebviewHandler {
webview,
dom,
waker: waker::tao_waker(proxy, id),
}
}
struct WebviewHandler {
dom: VirtualDom,
webview: Rc<wry::webview::WebView>,