mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
feat(core,gui): remember the main window's size
The app always opened at 980x680. It now keeps the unmaximized size and whether the window was maximized in config.json under `window`, written when the window closes and when the app quits with it still open. A missing field, or a saved size below 480x400, opens at the old default.
This commit is contained in:
1 parent
11d03091a7
commit
770d4fda38
2 files changed
+124
-3
No files matched your search
@@ -163,6 +163,10 @@ pub struct AppConfig {
|
||||
/// Defaulted for configs predating the field.
|
||||
#[serde(default)]
|
||||
pub files_view: FilesView,
|
||||
/// The desktop app's main window size, remembered between runs. Defaulted
|
||||
/// for configs predating the field.
|
||||
#[serde(default)]
|
||||
pub window: WindowState,
|
||||
/// The user hid the tray icon from its own menu. The tray exits at start
|
||||
/// while this is set, and the app does not spawn it; Preferences turns it
|
||||
/// back on. Defaulted for configs predating the field.
|
||||
@@ -211,6 +215,44 @@ impl Default for FilesView {
|
||||
}
|
||||
}
|
||||
|
||||
/// The desktop app's main window, as it was when it last closed.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct WindowState {
|
||||
/// The unmaximised size, in logical pixels.
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub maximized: bool,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
/// Below this the saved size is taken for damage and the default is used.
|
||||
pub const MIN_WIDTH: i32 = 480;
|
||||
pub const MIN_HEIGHT: i32 = 400;
|
||||
|
||||
/// This state, with a size too small to use replaced by the default.
|
||||
pub fn usable(self) -> Self {
|
||||
if self.width < Self::MIN_WIDTH || self.height < Self::MIN_HEIGHT {
|
||||
Self {
|
||||
maximized: self.maximized,
|
||||
..Self::default()
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WindowState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 980,
|
||||
height: 680,
|
||||
maximized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The key a My Files listing is ordered by.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -702,6 +744,11 @@ mod tests {
|
||||
descending: true,
|
||||
folders_first: false,
|
||||
},
|
||||
window: WindowState {
|
||||
width: 1200,
|
||||
height: 800,
|
||||
maximized: true,
|
||||
},
|
||||
tray_hidden: true,
|
||||
language: Some("de".to_string()),
|
||||
online_map: true,
|
||||
@@ -721,6 +768,39 @@ mod tests {
|
||||
assert_eq!(decoded.upload_limit, Some(500_000));
|
||||
assert_eq!(decoded.download_limit, None);
|
||||
assert_eq!(decoded.files_view, config.files_view);
|
||||
assert_eq!(decoded.window, config.window);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_config_without_a_window_opens_at_the_default_size() {
|
||||
let decoded: AppConfig =
|
||||
serde_json::from_str(r#"{"app_version":"a","user_agent":"b"}"#).unwrap();
|
||||
assert_eq!(decoded.window, WindowState::default());
|
||||
let partial: WindowState = serde_json::from_str(r#"{"maximized":true}"#).unwrap();
|
||||
assert_eq!((partial.width, partial.height), (980, 680));
|
||||
assert!(partial.maximized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_saved_too_small_opens_at_the_default_size() {
|
||||
let tiny = WindowState {
|
||||
width: 0,
|
||||
height: 500,
|
||||
maximized: true,
|
||||
};
|
||||
assert_eq!(
|
||||
tiny.usable(),
|
||||
WindowState {
|
||||
maximized: true,
|
||||
..WindowState::default()
|
||||
}
|
||||
);
|
||||
let fine = WindowState {
|
||||
width: 640,
|
||||
height: 480,
|
||||
maximized: false,
|
||||
};
|
||||
assert_eq!(fine.usable(), fine);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -69,7 +69,7 @@ use gtk4::glib::BoxedAnyObject;
|
||||
|
||||
use pdfs_core::auth;
|
||||
|
||||
use pdfs_core::config::{AppDirs, FileSort, FilesView};
|
||||
use pdfs_core::config::{AppDirs, FileSort, FilesView, WindowState};
|
||||
|
||||
use pdfs_core::control::{
|
||||
ActivityEntry, ActivityKind, AlbumInfo, BookmarkInfo, ConflictInfo, ConflictKeep, DeviceInfo,
|
||||
@@ -716,13 +716,18 @@ fn build_window(app: &adw::Application) {
|
||||
}
|
||||
});
|
||||
|
||||
let saved = ui.dirs.load_config().window.usable();
|
||||
let window = adw::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("Proton Drive")
|
||||
.default_width(980)
|
||||
.default_height(680)
|
||||
.default_width(saved.width)
|
||||
.default_height(saved.height)
|
||||
.maximized(saved.maximized)
|
||||
.width_request(WindowState::MIN_WIDTH)
|
||||
.height_request(WindowState::MIN_HEIGHT)
|
||||
.content(&toasts)
|
||||
.build();
|
||||
remember_window_state(&ui, app, &window);
|
||||
// A wide window docks the details pane beside the files instead of laying it
|
||||
// over them.
|
||||
let wide = adw::Breakpoint::new(
|
||||
@@ -743,6 +748,42 @@ fn build_window(app: &adw::Application) {
|
||||
window.present();
|
||||
}
|
||||
|
||||
/// Save the window's size when it closes, and when the app quits with the
|
||||
/// window still open (Ctrl+Q, the tray's Quit), which never asks it to close.
|
||||
fn remember_window_state(ui: &Rc<Ui>, app: &adw::Application, window: &adw::ApplicationWindow) {
|
||||
let ui_close = ui.clone();
|
||||
window.connect_close_request(move |window| {
|
||||
save_window_state(&ui_close, window);
|
||||
glib::Propagation::Proceed
|
||||
});
|
||||
let ui_quit = ui.clone();
|
||||
let window_quit = window.downgrade();
|
||||
app.connect_shutdown(move |_| {
|
||||
if let Some(window) = window_quit.upgrade() {
|
||||
save_window_state(&ui_quit, &window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Write the window's unmaximised size and maximised state to the config, if
|
||||
/// either changed. Runs as the window goes away, so a failure is only logged.
|
||||
fn save_window_state(ui: &Ui, window: &adw::ApplicationWindow) {
|
||||
let (width, height) = window.default_size();
|
||||
let state = WindowState {
|
||||
width,
|
||||
height,
|
||||
maximized: window.is_maximized(),
|
||||
};
|
||||
let mut config = ui.dirs.load_config();
|
||||
if config.window == state {
|
||||
return;
|
||||
}
|
||||
config.window = state;
|
||||
if let Err(e) = ui.dirs.save_config(&config) {
|
||||
tracing::warn!("cannot save the window size: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `window` is minimised or otherwise out of sight (another workspace,
|
||||
/// fully covered), as far as the compositor tells us.
|
||||
fn window_hidden(window: &adw::ApplicationWindow) -> bool {
|
||||
|
||||
Reference in new issue
Block a user