1
0
Jonathan Kelley 2 жил өмнө
parent
commit
22e71a71bd

+ 82 - 0
examples/compose.rs

@@ -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}"
+            }
+        }
+    })
+}

+ 51 - 7
packages/desktop/src/desktop_context.rs

@@ -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, id))
+            .unwrap();
+
         self.proxy
-            .send_event(UserWindowEvent(EventData::NewWindow, self.id()))
+            .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,
 

+ 1 - 1
packages/desktop/src/events.rs

@@ -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,

+ 41 - 27
packages/desktop/src/lib.rs

@@ -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();
+
+    // 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| {
+    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>,