소스 검색

Added close behaviour option for specific window (#3754)

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
Klemen 1 일 전
부모
커밋
9573af115a

+ 6 - 3
examples/multiwindow.rs

@@ -5,12 +5,15 @@
 //! own context, root elements, etc.
 
 use dioxus::prelude::*;
-use dioxus::{desktop::Config, desktop::WindowCloseBehaviour};
+use dioxus::{desktop::Config, desktop::DefaultWindowCloseBehaviour};
 
 fn main() {
     dioxus::LaunchBuilder::desktop()
-        // We can choose the close behavior of the last window to hide. See WindowCloseBehaviour for more options.
-        .with_cfg(Config::new().with_close_behaviour(WindowCloseBehaviour::LastWindowHides))
+        // We can choose the close behavior of the last window to hide. See DefaultWindowCloseBehaviour for more options.
+        .with_cfg(
+            Config::new()
+                .with_default_window_close_behaviour(DefaultWindowCloseBehaviour::LastWindowHides),
+        )
         .launch(app);
 }
 

+ 38 - 0
examples/multiwindow_with_tray_icon.rs

@@ -0,0 +1,38 @@
+//! Multiwindow with tray icon example
+//!
+//! This example shows how to implement a simple multiwindow application and tray icon using dioxus.
+//! This works by spawning a new window when the user clicks a button. We have to build a new virtualdom which has its
+//! own context, root elements, etc.
+
+use dioxus::desktop::{
+    trayicon::{default_tray_icon, init_tray_icon},
+    Config, WindowCloseBehaviour,
+};
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::LaunchBuilder::desktop()
+        // We can choose the close behavior of this window to hide. See WindowCloseBehaviour for more options.
+        .with_cfg(Config::new().with_window_close_behaviour(WindowCloseBehaviour::WindowHides))
+        .launch(app);
+}
+
+fn app() -> Element {
+    // async should not be needed, check if issue 3542 has been resolved
+    let onclick = move |_| async {
+        let dom = VirtualDom::new(popup);
+        dioxus::desktop::window().new_window(dom, Default::default());
+    };
+
+    init_tray_icon(default_tray_icon(), None);
+
+    rsx! {
+        button { onclick, "New Window" }
+    }
+}
+
+fn popup() -> Element {
+    rsx! {
+        div { "This is a popup window!" }
+    }
+}

+ 5 - 2
examples/window_focus.rs

@@ -8,12 +8,15 @@
 use dioxus::desktop::tao::event::Event as WryEvent;
 use dioxus::desktop::tao::event::WindowEvent;
 use dioxus::desktop::use_wry_event_handler;
-use dioxus::desktop::{Config, WindowCloseBehaviour};
+use dioxus::desktop::{Config, DefaultWindowCloseBehaviour};
 use dioxus::prelude::*;
 
 fn main() {
     dioxus::LaunchBuilder::desktop()
-        .with_cfg(Config::new().with_close_behaviour(WindowCloseBehaviour::CloseWindow))
+        .with_cfg(
+            Config::new()
+                .with_default_window_close_behaviour(DefaultWindowCloseBehaviour::WindowsCloses),
+        )
         .launch(app)
 }
 

+ 56 - 11
packages/desktop/src/app.rs

@@ -1,5 +1,5 @@
 use crate::{
-    config::{Config, WindowCloseBehaviour},
+    config::{Config, DefaultWindowCloseBehaviour, WindowCloseBehaviour},
     edits::EditWebsocket,
     event_handlers::WindowEventHandlers,
     file_upload::{DesktopFileUploadForm, FileDialogRequest, NativeFileEngine},
@@ -36,7 +36,7 @@ pub(crate) struct App {
     // Stuff we need mutable access to
     pub(crate) control_flow: ControlFlow,
     pub(crate) is_visible_before_start: bool,
-    pub(crate) window_behavior: WindowCloseBehaviour,
+    pub(crate) default_window_close_behavior: DefaultWindowCloseBehaviour,
     pub(crate) webviews: HashMap<WindowId, WebviewInstance>,
     pub(crate) float_all: bool,
     pub(crate) show_devtools: bool,
@@ -65,7 +65,7 @@ impl App {
             .unwrap_or_else(|| EventLoopBuilder::<UserWindowEvent>::with_user_event().build());
 
         let app = Self {
-            window_behavior: cfg.last_window_close_behavior,
+            default_window_close_behavior: cfg.default_window_close_behaviour,
             is_visible_before_start: true,
             webviews: HashMap::new(),
             control_flow: ControlFlow::Wait,
@@ -188,10 +188,55 @@ impl App {
         }
     }
 
+    pub fn change_window_close_behaviour(
+        &mut self,
+        id: WindowId,
+        behaviour: Option<WindowCloseBehaviour>,
+    ) {
+        if let Some(webview) = self.webviews.get_mut(&id) {
+            webview.close_behaviour = behaviour
+        }
+    }
+
     pub fn handle_close_requested(&mut self, id: WindowId) {
+        use DefaultWindowCloseBehaviour::*;
         use WindowCloseBehaviour::*;
 
-        match self.window_behavior {
+        let mut remove = false;
+
+        if let Some(webview) = self.webviews.get(&id) {
+            if let Some(close_behaviour) = &webview.close_behaviour {
+                match close_behaviour {
+                    WindowExitsApp => {
+                        self.control_flow = ControlFlow::Exit;
+                        return;
+                    }
+                    WindowHides => {
+                        hide_window(&webview.desktop_context.window);
+                        return;
+                    }
+                    WindowCloses => {
+                        remove = true;
+                    }
+                }
+            }
+        }
+
+        // needed in case of `default_window_close_behavior WindowsHides | LastWindowHides` since they may not remove a window on `WindowCloses`
+        if remove {
+            #[cfg(debug_assertions)]
+            self.persist_window_state();
+
+            self.webviews.remove(&id);
+            if matches!(self.default_window_close_behavior, LastWindowExitsApp)
+                && self.webviews.is_empty()
+            {
+                self.control_flow = ControlFlow::Exit
+            }
+            return;
+        }
+
+        match self.default_window_close_behavior {
             LastWindowExitsApp => {
                 #[cfg(debug_assertions)]
                 self.persist_window_state();
@@ -206,13 +251,13 @@ impl App {
                 self.webviews.remove(&id);
             }
 
-            LastWindowHides => {
+            WindowsHides | LastWindowHides => {
                 if let Some(webview) = self.webviews.get(&id) {
-                    hide_last_window(&webview.desktop_context.window);
+                    hide_window(&webview.desktop_context.window);
                 }
             }
 
-            CloseWindow => {
+            WindowsCloses => {
                 self.webviews.remove(&id);
             }
         }
@@ -222,8 +267,8 @@ impl App {
         self.webviews.remove(&id);
 
         if matches!(
-            self.window_behavior,
-            WindowCloseBehaviour::LastWindowExitsApp
+            self.default_window_close_behavior,
+            DefaultWindowCloseBehaviour::LastWindowExitsApp
         ) && self.webviews.is_empty()
         {
             self.control_flow = ControlFlow::Exit
@@ -678,13 +723,13 @@ struct PreservedWindowState {
     url: Option<String>,
 }
 
-/// Hide the last window when using LastWindowHides.
+/// Hides a window.
 ///
 /// On macOS, if we use `set_visibility(false)` on the window, it will hide the window but not show
 /// it again when the user switches back to the app. `NSApplication::hide:` has the correct behaviour,
 /// so we need to special case it.
 #[allow(unused)]
-fn hide_last_window(window: &Window) {
+fn hide_window(window: &Window) {
     #[cfg(target_os = "windows")]
     {
         use tao::platform::windows::WindowExtWindows;

+ 35 - 10
packages/desktop/src/config.rs

@@ -17,16 +17,30 @@ type CustomEventHandler = Box<
         ),
 >;
 
-/// The behaviour of the application when the last window is closed.
+/// The closing behaviour of the application when the last window is closed, you can overwrite this behaviour for specific window with WindowCloseBehaviour.
 #[derive(Copy, Clone, Eq, PartialEq)]
 #[non_exhaustive]
-pub enum WindowCloseBehaviour {
-    /// Default behaviour, closing the last window exits the app
+pub enum DefaultWindowCloseBehaviour {
+    /// Default behaviour, closing the last window will exit the app, others will close,
     LastWindowExitsApp,
-    /// Closing the last window will not actually close it, just hide it
+    /// Closing the last window will hide it, others will close
     LastWindowHides,
-    /// Closing the last window will close it but the app will keep running so that new windows can be opened
-    CloseWindow,
+    /// Every window will hide
+    WindowsHides,
+    /// Every window will close
+    WindowsCloses,
+}
+
+/// The closing behaviour of specific application window.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum WindowCloseBehaviour {
+    /// Closing the window will exit the app
+    WindowExitsApp,
+    /// Window will hide
+    WindowHides,
+    /// Window will close
+    WindowCloses,
 }
 
 /// The state of the menu builder. We need to keep track of if the state is default
@@ -61,7 +75,8 @@ pub struct Config {
     pub(crate) custom_index: Option<String>,
     pub(crate) root_name: String,
     pub(crate) background_color: Option<(u8, u8, u8, u8)>,
-    pub(crate) last_window_close_behavior: WindowCloseBehaviour,
+    pub(crate) default_window_close_behaviour: DefaultWindowCloseBehaviour,
+    pub(crate) window_close_behaviour: Option<WindowCloseBehaviour>,
     pub(crate) custom_event_handler: Option<CustomEventHandler>,
     pub(crate) disable_file_drop_handler: bool,
 }
@@ -107,7 +122,8 @@ impl Config {
             custom_index: None,
             root_name: "main".to_string(),
             background_color: None,
-            last_window_close_behavior: WindowCloseBehaviour::LastWindowExitsApp,
+            default_window_close_behaviour: DefaultWindowCloseBehaviour::LastWindowExitsApp,
+            window_close_behaviour: None,
             custom_event_handler: None,
             disable_file_drop_handler: false,
         }
@@ -170,8 +186,17 @@ impl Config {
     }
 
     /// Sets the behaviour of the application when the last window is closed.
-    pub fn with_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {
-        self.last_window_close_behavior = behaviour;
+    pub fn with_default_window_close_behaviour(
+        mut self,
+        behaviour: DefaultWindowCloseBehaviour,
+    ) -> Self {
+        self.default_window_close_behaviour = behaviour;
+        self
+    }
+
+    /// Sets the behaviour of the application when the last window is closed.
+    pub fn with_window_close_behaviour(mut self, behaviour: WindowCloseBehaviour) -> Self {
+        self.window_close_behaviour = Some(behaviour);
         self
     }
 

+ 21 - 1
packages/desktop/src/desktop_context.rs

@@ -6,7 +6,7 @@ use crate::{
     query::QueryEngine,
     shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
     webview::PendingWebview,
-    AssetRequest, Config, WryEventHandler,
+    AssetRequest, Config, WindowCloseBehaviour, WryEventHandler,
 };
 use dioxus_core::{prelude::Callback, VirtualDom};
 use std::{
@@ -181,6 +181,26 @@ impl DesktopService {
             .send_event(UserWindowEvent::CloseWindow(id));
     }
 
+    /// Change close behaviour of this window
+    pub fn change_close_behaviour(&self, behaviour: Option<WindowCloseBehaviour>) {
+        let _ = self
+            .shared
+            .proxy
+            .send_event(UserWindowEvent::CloseBehaviour(self.id(), behaviour));
+    }
+
+    /// Change close behaviour of a specific window, given its ID
+    pub fn change_window_close_behaviour(
+        &self,
+        id: WindowId,
+        behaviour: Option<WindowCloseBehaviour>,
+    ) {
+        let _ = self
+            .shared
+            .proxy
+            .send_event(UserWindowEvent::CloseBehaviour(id, behaviour));
+    }
+
     /// change window to fullscreen
     pub fn set_fullscreen(&self, fullscreen: bool) {
         if let Some(handle) = &self.window.current_monitor() {

+ 4 - 0
packages/desktop/src/ipc.rs

@@ -1,6 +1,8 @@
 use serde::{Deserialize, Serialize};
 use tao::window::WindowId;
 
+use crate::WindowCloseBehaviour;
+
 #[non_exhaustive]
 #[derive(Debug, Clone)]
 pub enum UserWindowEvent {
@@ -17,6 +19,8 @@ pub enum UserWindowEvent {
     #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
     TrayMenuEvent(tray_icon::menu::MenuEvent),
 
+    CloseBehaviour(WindowId, Option<WindowCloseBehaviour>),
+
     /// Poll the virtualdom
     Poll(WindowId),
 

+ 3 - 0
packages/desktop/src/launch.rs

@@ -103,6 +103,9 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config:
                     IpcMethod::BrowserOpen => app.handle_browser_open(msg),
                     IpcMethod::Other(_) => {}
                 },
+                UserWindowEvent::CloseBehaviour(window_id, window_close_behaviour) => {
+                    app.change_window_close_behaviour(window_id, window_close_behaviour)
+                }
             },
             _ => {}
         }

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

@@ -47,7 +47,7 @@ pub mod trayicon;
 
 // Public exports
 pub use assets::AssetRequest;
-pub use config::{Config, WindowCloseBehaviour};
+pub use config::{Config, DefaultWindowCloseBehaviour, WindowCloseBehaviour};
 pub use desktop_context::{
     window, DesktopContext, DesktopService, PendingDesktopContext, WeakDesktopContext,
 };

+ 3 - 0
packages/desktop/src/webview.rs

@@ -2,6 +2,7 @@ use crate::element::DesktopElement;
 use crate::file_upload::DesktopFileDragEvent;
 use crate::menubar::DioxusMenu;
 use crate::PendingDesktopContext;
+use crate::WindowCloseBehaviour;
 use crate::{
     app::SharedContext,
     assets::AssetHandlerRegistry,
@@ -160,6 +161,7 @@ pub(crate) struct WebviewInstance {
     pub edits: WebviewEdits,
     pub desktop_context: DesktopContext,
     pub waker: Waker,
+    pub close_behaviour: Option<WindowCloseBehaviour>,
 
     // Wry assumes the webcontext is alive for the lifetime of the webview.
     // We need to keep the webcontext alive, otherwise the webview will crash
@@ -431,6 +433,7 @@ impl WebviewInstance {
             desktop_context,
             _menu: menu,
             _web_context: web_context,
+            close_behaviour: cfg.window_close_behaviour,
         }
     }