diff --git a/src/app.rs b/src/app.rs index 2f1f296..6e33660 100644 --- a/src/app.rs +++ b/src/app.rs @@ -339,8 +339,7 @@ pub(crate) enum Message { ExportChatPicked(Option, String), ModelManagerOpened(window::Id), WindowOpened(window::Id), - WindowCloseRequested(window::Id), - WindowClosed(window::Id), + WindowEvent(window::Id, window::Event), RequestQuit, ConfirmQuit, CancelQuit, @@ -1046,33 +1045,50 @@ impl App { return focus_composer(); } } - Message::WindowCloseRequested(id) => { - if id == self.main_window { - return self.update(Message::RequestQuit); + Message::WindowEvent(id, event) => match event { + window::Event::Opened { position, size } if id == self.main_window => { + self.config.interface.window_size = + [size.width.round() as u32, size.height.round() as u32]; + self.config.interface.window_position = position + .map(|position| [position.x.round() as i32, position.y.round() as i32]); } - return window::close(id); - } - Message::WindowClosed(id) => { - if self.model_manager_window == Some(id) { - self.model_manager_window = None; - self.pending_model_delete = None; + window::Event::Moved(position) if id == self.main_window => { + self.config.interface.window_position = + Some([position.x.round() as i32, position.y.round() as i32]); } - if self.preferences_window == Some(id) { - self.preferences_window = None; - self.preference_error = None; - self.restore_dev_brain_confirmation = false; + window::Event::Resized(size) if id == self.main_window => { + self.config.interface.window_size = + [size.width.round() as u32, size.height.round() as u32]; } - if self.help_window == Some(id) { - self.help_window = None; + window::Event::CloseRequested => { + if id == self.main_window { + return self.update(Message::RequestQuit); + } + return window::close(id); } - } + window::Event::Closed => { + if self.model_manager_window == Some(id) { + self.model_manager_window = None; + self.pending_model_delete = None; + } + if self.preferences_window == Some(id) { + self.preferences_window = None; + self.preference_error = None; + self.restore_dev_brain_confirmation = false; + } + if self.help_window == Some(id) { + self.help_window = None; + } + } + _ => {} + }, Message::RequestQuit => { if self.active_chat_count() == 0 { - return iced::exit(); + return self.quit(); } self.quit_confirmation = true; } - Message::ConfirmQuit => return iced::exit(), + Message::ConfirmQuit => return self.quit(), Message::CancelQuit => self.quit_confirmation = false, Message::Escape(id) => { if self.preferences_window == Some(id) { @@ -1888,7 +1904,7 @@ impl App { // focus, so a dialog would never see it through `on_key_press`. iced::event::listen_with(|event, _, id| { matches!( - event, + &event, iced::Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Escape), .. @@ -1896,8 +1912,17 @@ impl App { ) .then_some(Message::Escape(id)) }), - window::close_requests().map(Message::WindowCloseRequested), - window::close_events().map(Message::WindowClosed), + window::events().filter_map(|(id, event)| { + matches!( + event, + window::Event::Opened { .. } + | window::Event::Moved(_) + | window::Event::Resized(_) + | window::Event::CloseRequested + | window::Event::Closed + ) + .then_some(Message::WindowEvent(id, event)) + }), ]; subscriptions .push(iced::time::every(METRICS_SAMPLE_INTERVAL).map(|_| Message::MetricsTick)); @@ -2071,6 +2096,20 @@ impl App { } } + fn quit(&mut self) -> Task { + if self.database.is_none() { + return iced::exit(); + } + match self.config.save(&config_path()) { + Ok(()) => iced::exit(), + Err(error) => { + self.quit_confirmation = false; + self.error = Some(error); + Task::none() + } + } + } + fn finish_cache_change(&mut self) { self.metrics.rescan_cache(&kv_cache_path()); self.metrics_snapshot = self.metrics.snapshot(); diff --git a/src/config.rs b/src/config.rs index da87487..4bbab01 100644 --- a/src/config.rs +++ b/src/config.rs @@ -229,6 +229,8 @@ impl Default for EndpointConfig { pub struct InterfaceConfig { pub sidebar_collapsed: bool, pub sidebar_width: i32, + pub window_size: [u32; 2], + pub window_position: Option<[i32; 2]>, /// Project the app reopens on. Cleared when that project goes away. pub last_project_id: Option, } @@ -238,6 +240,8 @@ impl Default for InterfaceConfig { Self { sidebar_collapsed: false, sidebar_width: 276, + window_size: [1120, 720], + window_position: None, last_project_id: None, } } @@ -348,6 +352,11 @@ mod tests { whitespace: GitDiffWhitespace::IgnoreEndOfLine, ..GitConfig::default() }, + interface: InterfaceConfig { + window_size: [1280, 800], + window_position: Some([120, -40]), + ..InterfaceConfig::default() + }, ..Config::default() }; config.save(&path).unwrap(); @@ -358,7 +367,8 @@ mod tests { "model: glm-5.2\ndefault_permission_mode: ai\na2ui_enabled: false\n\ generation:\n context_tokens: 65536\n reasoning_mode: none\n\ runtime:\n ssd:\n enabled: true\n cache: 64GB\n\ - git:\n diff_layout: split\n diff_algorithm: patience\n context_lines: 5\n whitespace: ignore-end-of-line\n" + git:\n diff_layout: split\n diff_algorithm: patience\n context_lines: 5\n whitespace: ignore-end-of-line\n\ + interface:\n window_size:\n - 1280\n - 800\n window_position:\n - 120\n - -40\n" ); assert_eq!(Config::load(&path).unwrap(), config); fs::remove_dir_all(&directory).unwrap(); diff --git a/src/main.rs b/src/main.rs index e495110..32cf0f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ mod server; mod settings; use app::{App, Message, app_icon, app_theme}; -use iced::{Size, window}; +use iced::{Point, Size, window}; fn main() -> iced::Result { if std::env::args().nth(1).as_deref() == Some("validate-a2ui") { @@ -42,8 +42,19 @@ fn main() -> iced::Result { } iced::daemon( || { + let interface = config::Config::load(&app::config_path()) + .unwrap_or_default() + .interface; let (main_window, open) = window::open(window::Settings { - size: Size::new(1120.0, 720.0), + size: Size::new( + interface.window_size[0] as f32, + interface.window_size[1] as f32, + ), + position: interface + .window_position + .map_or(window::Position::Default, |[x, y]| { + window::Position::Specific(Point::new(x as f32, y as f32)) + }), min_size: Some(Size::new(760.0, 480.0)), icon: Some(app_icon()), exit_on_close_request: false,