Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: close behaviour for specific window #3754

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions examples/multiwindow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
38 changes: 38 additions & 0 deletions examples/multiwindow_with_tray_icon.rs
Original file line number Diff line number Diff line change
@@ -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!" }
}
}
7 changes: 5 additions & 2 deletions examples/window_focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
67 changes: 56 additions & 11 deletions packages/desktop/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
config::{Config, WindowCloseBehaviour},
config::{Config, DefaultWindowCloseBehaviour, WindowCloseBehaviour},
event_handlers::WindowEventHandlers,
file_upload::{DesktopFileUploadForm, FileDialogRequest, NativeFileEngine},
ipc::{IpcMessage, UserWindowEvent},
Expand Down Expand Up @@ -33,7 +33,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,
Expand Down Expand Up @@ -61,7 +61,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,
Expand Down Expand Up @@ -184,10 +184,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();
Expand All @@ -202,13 +247,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);
}
}
Expand All @@ -218,8 +263,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
Expand Down Expand Up @@ -572,13 +617,13 @@ struct PreservedWindowState {
monitor: 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;
Expand Down
45 changes: 35 additions & 10 deletions packages/desktop/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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
}

Expand Down
22 changes: 21 additions & 1 deletion packages/desktop/src/desktop_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
query::QueryEngine,
shortcut::{HotKey, ShortcutHandle, ShortcutRegistryError},
webview::WebviewInstance,
AssetRequest, Config, WryEventHandler,
AssetRequest, Config, WindowCloseBehaviour, WryEventHandler,
};
use dioxus_core::{
prelude::{Callback, ScopeId},
Expand Down Expand Up @@ -160,6 +160,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() {
Expand Down
4 changes: 4 additions & 0 deletions packages/desktop/src/ipc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize};
use tao::window::WindowId;

use crate::WindowCloseBehaviour;

#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum UserWindowEvent {
Expand All @@ -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),

Expand Down
3 changes: 3 additions & 0 deletions packages/desktop/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
},
_ => {}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, WeakDesktopContext};
pub use event_handlers::WryEventHandler;
pub use hooks::*;
Expand Down
3 changes: 3 additions & 0 deletions packages/desktop/src/webview.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::element::DesktopElement;
use crate::file_upload::DesktopFileDragEvent;
use crate::menubar::DioxusMenu;
use crate::WindowCloseBehaviour;
use crate::{
app::SharedContext,
assets::AssetHandlerRegistry,
Expand Down Expand Up @@ -159,6 +160,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
Expand Down Expand Up @@ -434,6 +436,7 @@ impl WebviewInstance {
desktop_context,
_menu: menu,
_web_context: web_context,
close_behaviour: cfg.window_close_behaviour,
}
}

Expand Down
Loading