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.
|
/// Defaulted for configs predating the field.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub files_view: FilesView,
|
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
|
/// 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
|
/// while this is set, and the app does not spawn it; Preferences turns it
|
||||||
/// back on. Defaulted for configs predating the field.
|
/// 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.
|
/// The key a My Files listing is ordered by.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
@@ -702,6 +744,11 @@ mod tests {
|
|||||||
descending: true,
|
descending: true,
|
||||||
folders_first: false,
|
folders_first: false,
|
||||||
},
|
},
|
||||||
|
window: WindowState {
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
maximized: true,
|
||||||
|
},
|
||||||
tray_hidden: true,
|
tray_hidden: true,
|
||||||
language: Some("de".to_string()),
|
language: Some("de".to_string()),
|
||||||
online_map: true,
|
online_map: true,
|
||||||
@@ -721,6 +768,39 @@ mod tests {
|
|||||||
assert_eq!(decoded.upload_limit, Some(500_000));
|
assert_eq!(decoded.upload_limit, Some(500_000));
|
||||||
assert_eq!(decoded.download_limit, None);
|
assert_eq!(decoded.download_limit, None);
|
||||||
assert_eq!(decoded.files_view, config.files_view);
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ use gtk4::glib::BoxedAnyObject;
|
|||||||
|
|
||||||
use pdfs_core::auth;
|
use pdfs_core::auth;
|
||||||
|
|
||||||
use pdfs_core::config::{AppDirs, FileSort, FilesView};
|
use pdfs_core::config::{AppDirs, FileSort, FilesView, WindowState};
|
||||||
|
|
||||||
use pdfs_core::control::{
|
use pdfs_core::control::{
|
||||||
ActivityEntry, ActivityKind, AlbumInfo, BookmarkInfo, ConflictInfo, ConflictKeep, DeviceInfo,
|
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()
|
let window = adw::ApplicationWindow::builder()
|
||||||
.application(app)
|
.application(app)
|
||||||
.title("Proton Drive")
|
.title("Proton Drive")
|
||||||
.default_width(980)
|
.default_width(saved.width)
|
||||||
.default_height(680)
|
.default_height(saved.height)
|
||||||
|
.maximized(saved.maximized)
|
||||||
|
.width_request(WindowState::MIN_WIDTH)
|
||||||
|
.height_request(WindowState::MIN_HEIGHT)
|
||||||
.content(&toasts)
|
.content(&toasts)
|
||||||
.build();
|
.build();
|
||||||
|
remember_window_state(&ui, app, &window);
|
||||||
// A wide window docks the details pane beside the files instead of laying it
|
// A wide window docks the details pane beside the files instead of laying it
|
||||||
// over them.
|
// over them.
|
||||||
let wide = adw::Breakpoint::new(
|
let wide = adw::Breakpoint::new(
|
||||||
@@ -743,6 +748,42 @@ fn build_window(app: &adw::Application) {
|
|||||||
window.present();
|
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,
|
/// Whether `window` is minimised or otherwise out of sight (another workspace,
|
||||||
/// fully covered), as far as the compositor tells us.
|
/// fully covered), as far as the compositor tells us.
|
||||||
fn window_hidden(window: &adw::ApplicationWindow) -> bool {
|
fn window_hidden(window: &adw::ApplicationWindow) -> bool {
|
||||||
|
|||||||
Reference in new issue
Block a user