Skip to content

Commit 08a3d51

Browse files
committed
Implement automatic window state save and recovery
- Save window state when the application exits - Restore the last saved window position and size on startup - Fix the `archive_cache_size` comment in settings
1 parent 9d3f0df commit 08a3d51

File tree

7 files changed

+76
-9
lines changed

7 files changed

+76
-9
lines changed

src/app.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod macos {
1919
pub use iced_custom as iced;
2020
}
2121

22+
use iced_winit::winit::dpi::{ PhysicalPosition, PhysicalSize };
2223
#[cfg(target_os = "linux")]
2324
use other_os::*;
2425

@@ -109,6 +110,8 @@ pub struct DataViewer {
109110
pub selection_manager: SelectionManager, // Manages image selections/exclusions
110111
#[cfg(feature = "coco")]
111112
pub annotation_manager: crate::coco::annotation_manager::AnnotationManager, // Manages COCO annotations
113+
pub window_size: PhysicalSize<u32>,
114+
pub window_position: PhysicalPosition<i32>,
112115
}
113116

114117
// Implement Deref to expose RuntimeSettings fields directly on DataViewer
@@ -193,6 +196,9 @@ impl DataViewer {
193196
selection_manager: SelectionManager::new(),
194197
#[cfg(feature = "coco")]
195198
annotation_manager: crate::coco::annotation_manager::AnnotationManager::new(),
199+
window_position: PhysicalPosition { x: 0, y: 0 },
200+
window_size: PhysicalSize { width: crate::config::DEFAULT_WINDOW_WIDTH,
201+
height: crate::config::DEFAULT_WINDOW_HEIGHT }
196202
}
197203
}
198204

src/app/keyboard_handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl DataViewer {
108108
Key::Character("q") => {
109109
// Terminate the app
110110
if is_platform_modifier(&modifiers) {
111-
std::process::exit(0);
111+
tasks.push(Task::done(Message::Quit));
112112
}
113113
}
114114

src/app/message.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use iced_core::Event;
22
use iced_core::image::Handle;
33
use iced_core::Color;
4+
use iced_winit::winit::dpi::{PhysicalPosition, PhysicalSize};
45

56
use crate::cache::img_cache::{CachedData, CacheStrategy, LoadOperation};
67
use crate::menu::PaneLayout;
@@ -69,4 +70,6 @@ pub enum Message {
6970
// Advanced settings input
7071
AdvancedSettingChanged(String, String), // (field_name, value)
7172
ResetAdvancedSettings,
73+
SizeChanged(PhysicalSize<u32>),
74+
PositionChanged(PhysicalPosition<i32>),
7275
}

src/app/message_handlers.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub fn handle_message(app: &mut DataViewer, message: Message) -> Task<Message> {
4242
Task::none()
4343
}
4444
Message::Quit => {
45+
let _ = handle_save_settings(app);
4546
std::process::exit(0);
4647
}
4748

@@ -80,7 +81,8 @@ pub fn handle_message(app: &mut DataViewer, message: Message) -> Task<Message> {
8081
Message::ToggleMouseWheelZoom(_) | Message::ToggleCopyButtons(_) | Message::ToggleFullScreen(_) |
8182
Message::ToggleFpsDisplay(_) | Message::ToggleSplitOrientation(_) |
8283
Message::CursorOnTop(_) | Message::CursorOnMenu(_) | Message::CursorOnFooter(_) |
83-
Message::PaneSelected(_, _) | Message::SetCacheStrategy(_) | Message::SetCompressionStrategy(_) => {
84+
Message::PaneSelected(_, _) | Message::SetCacheStrategy(_) | Message::SetCompressionStrategy(_) |
85+
Message::SizeChanged(_) | Message::PositionChanged(_) => {
8486
handle_toggle_messages(app, message)
8587
}
8688

@@ -523,6 +525,14 @@ pub fn handle_toggle_messages(app: &mut DataViewer, message: Message) -> Task<Me
523525
app.update_compression_strategy(strategy);
524526
Task::none()
525527
}
528+
Message::SizeChanged(size) => {
529+
app.window_size = size;
530+
Task::none()
531+
}
532+
Message::PositionChanged(position) => {
533+
app.window_position = position;
534+
Task::none()
535+
}
526536
_ => Task::none()
527537
}
528538
}
@@ -843,12 +853,15 @@ fn handle_save_settings(app: &mut DataViewer) -> Task<Message> {
843853
cache_size,
844854
max_loading_queue_size,
845855
max_being_loaded_queue_size,
846-
window_width,
847-
window_height,
856+
window_width: app.window_size.width,
857+
window_height: app.window_size.height,
848858
atlas_size,
849859
double_click_threshold_ms,
850860
archive_cache_size,
851861
archive_warning_threshold_mb,
862+
is_fullscreen: app.is_fullscreen,
863+
window_position_x: app.window_position.x,
864+
window_position_y: app.window_position.y,
852865
};
853866

854867
let old_settings = UserSettings::load(None);

src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ pub struct Config {
2222
pub window_height: u32, // Default window height
2323
pub atlas_size: u32, // Size of the square texture atlas used in iced_wgpu (affects slider performance)
2424
pub double_click_threshold_ms: u16, // Double-click detection threshold in milliseconds
25+
pub window_position_x: i32,
26+
pub window_position_y: i32,
27+
pub is_fullscreen: bool,
2528
}
2629

2730
pub static CONFIG: Lazy<Config> = Lazy::new(|| {
@@ -36,5 +39,8 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| {
3639
window_height: settings.window_height,
3740
atlas_size: settings.atlas_size,
3841
double_click_threshold_ms: settings.double_click_threshold_ms,
42+
window_position_x: settings.window_position_x,
43+
window_position_y: settings.window_position_y,
44+
is_fullscreen: settings.is_fullscreen,
3945
}
4046
});

src/main.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod settings_modal;
2626
mod macos_file_access;
2727
mod archive_cache;
2828

29+
use iced_winit::winit::dpi::PhysicalPosition;
2930
#[allow(unused_imports)]
3031
use log::{Level, trace, debug, info, warn, error};
3132

@@ -491,16 +492,19 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
491492
}
492493
WindowEvent::Resized(size) => {
493494
if size.width > 0 && size.height > 0 {
495+
state.queue_message(Message::SizeChanged(size));
494496
*resized = true;
495497
} else {
496498
// Skip resizing and avoid configuring the surface
497499
*resized = false;
498500
}
499501
}
500-
WindowEvent::Moved(_) => {
502+
WindowEvent::Moved(position) => {
503+
state.queue_message(Message::PositionChanged(position));
501504
*moved = true;
502505
}
503506
WindowEvent::CloseRequested => {
507+
state.queue_message(Message::SaveSettings);
504508
#[cfg(target_os = "macos")]
505509
{
506510
// Clean up all active security-scoped access before shutdown
@@ -914,6 +918,7 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
914918
}
915919
}
916920
Control::Exit => {
921+
state.queue_message(Message::SaveSettings);
917922
#[cfg(target_os = "macos")]
918923
{
919924
// Clean up all active security-scoped access before shutdown
@@ -975,6 +980,14 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
975980
)
976981
.expect("Create window"),
977982
);
983+
window.set_outer_position(PhysicalPosition::new(CONFIG.window_position_x, CONFIG.window_position_y));
984+
985+
let size = window.current_monitor().unwrap_or(
986+
window.available_monitors().collect::<Vec<_>>().first().unwrap().clone()).size();
987+
// If window size is larger than current monitor size, simply maximized the window
988+
if CONFIG.window_width >= size.width && CONFIG.window_height > (size.height - 80) {
989+
window.set_maximized(true);
990+
}
978991

979992
if let Some(icon) = load_icon() {
980993
window.set_window_icon(Some(icon));
@@ -1097,13 +1110,25 @@ pub fn main() -> Result<(), winit::error::EventLoopError> {
10971110

10981111
// Update state creation to lock renderer
10991112
let mut renderer_guard = renderer.lock().unwrap();
1100-
let state = program::State::new(
1113+
let mut state = program::State::new(
11011114
shader_widget,
11021115
viewport.logical_size(),
11031116
&mut *renderer_guard,
11041117
&mut debug_tool,
11051118
);
11061119

1120+
if CONFIG.is_fullscreen {
1121+
let fullscreen = Some(winit::window::Fullscreen::Borderless(None));
1122+
state.queue_message(Message::ToggleFullScreen(true));
1123+
#[cfg(target_os = "macos")] {
1124+
use iced_winit::winit::platform::macos::WindowExtMacOS;
1125+
window.set_simple_fullscreen(fullscreen.is_some());
1126+
}
1127+
#[cfg(not(target_os = "macos"))] {
1128+
window.set_fullscreen(fullscreen);
1129+
}
1130+
}
1131+
11071132
// Set control flow
11081133
event_loop.set_control_flow(ControlFlow::Poll);
11091134

src/settings.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,21 @@ pub struct UserSettings {
7474
#[serde(default = "default_double_click_threshold_ms")]
7575
pub double_click_threshold_ms: u16,
7676

77-
/// Max size for compressed file cache (bytes)
77+
/// Max size for compressed file cache (MB)
7878
#[serde(default = "default_archive_cache_size")]
7979
pub archive_cache_size: u64,
8080

8181
/// Warning threshold for solid archives (MB)
8282
#[serde(default = "default_archive_warning_threshold_mb")]
8383
pub archive_warning_threshold_mb: u64,
84+
85+
// Window position and state
86+
#[serde(default)]
87+
pub window_position_x: i32,
88+
#[serde(default)]
89+
pub window_position_y: i32,
90+
#[serde(default)]
91+
pub is_fullscreen: bool,
8492
}
8593

8694
fn default_show_footer() -> bool {
@@ -161,6 +169,9 @@ impl Default for UserSettings {
161169
double_click_threshold_ms: config::DEFAULT_DOUBLE_CLICK_THRESHOLD_MS,
162170
archive_cache_size: config::DEFAULT_ARCHIVE_CACHE_SIZE,
163171
archive_warning_threshold_mb: config::DEFAULT_ARCHIVE_WARNING_THRESHOLD_MB,
172+
window_position_x: 0,
173+
window_position_y: 0,
174+
is_fullscreen: false,
164175
}
165176
}
166177
}
@@ -285,6 +296,9 @@ impl UserSettings {
285296
result = Self::replace_yaml_value_or_track(&result, "double_click_threshold_ms", &self.double_click_threshold_ms.to_string(), &mut missing_keys);
286297
result = Self::replace_yaml_value_or_track(&result, "archive_cache_size", &self.archive_cache_size.to_string(), &mut missing_keys);
287298
result = Self::replace_yaml_value_or_track(&result, "archive_warning_threshold_mb", &self.archive_warning_threshold_mb.to_string(), &mut missing_keys);
299+
result = Self::replace_yaml_value_or_track(&result, "window_position_x", &self.window_position_x.to_string(), &mut missing_keys);
300+
result = Self::replace_yaml_value_or_track(&result, "window_position_y", &self.window_position_y.to_string(), &mut missing_keys);
301+
result = Self::replace_yaml_value_or_track(&result, "is_fullscreen", &self.is_fullscreen.to_string(), &mut missing_keys);
288302

289303
// Append missing keys with comments
290304
if !missing_keys.is_empty() {
@@ -325,7 +339,7 @@ impl UserSettings {
325339
"window_height" => "# Default window height (pixels)".to_string(),
326340
"atlas_size" => "# Texture atlas size (affects slider performance, power of 2)".to_string(),
327341
"double_click_threshold_ms" => "# Double-click detection threshold (milliseconds)".to_string(),
328-
"archive_cache_size" => "# Max size for compressed file cache (bytes)".to_string(),
342+
"archive_cache_size" => "# Max size for compressed file cache (MB)".to_string(),
329343
"archive_warning_threshold_mb" => "# Warning threshold for solid archives (megabytes)".to_string(),
330344
_ => String::new(),
331345
}
@@ -418,7 +432,7 @@ atlas_size: {}
418432
# Double-click detection threshold (milliseconds)
419433
double_click_threshold_ms: {}
420434
421-
# Max size for compressed file cache (bytes)
435+
# Max size for compressed file cache (MB)
422436
archive_cache_size: {}
423437
424438
# Warning threshold for solid archives (megabytes)

0 commit comments

Comments
 (0)