Merge fix/remember-window-size: persist + restore window size

This commit is contained in:
2026-06-06 17:44:11 -04:00
2 changed files with 69 additions and 11 deletions
+32 -11
View File
@@ -250,13 +250,15 @@ impl Default for AppState {
let _ = UI_RX.set(Mutex::new(Some(ui_rx)));
let mut config = AppConfig::load();
// A persisted divider size from a differently-sized window could be out of
// range for the default window — clamp it before first render.
// The window opens at the restored size (see `run_gui`); clamp the
// persisted divider positions against THAT size, not a hardcoded default,
// so they stay valid for the window we're actually about to show.
let (ww, wh) = (config.window_width, config.window_height);
config.participants_width =
clamp_participants_width(config.participants_width, 900.0);
config.chat_height = clamp_chat_height(config.chat_height, 760.0);
config.controls_width = clamp_controls_width(config.controls_width, 900.0);
config.chat_drawer_width = clamp_chat_drawer_width(config.chat_drawer_width, 900.0);
clamp_participants_width(config.participants_width, ww);
config.chat_height = clamp_chat_height(config.chat_height, wh);
config.controls_width = clamp_controls_width(config.controls_width, ww);
config.chat_drawer_width = clamp_chat_drawer_width(config.chat_drawer_width, ww);
notify::set_enabled(config.notifications_enabled);
let _ = controller.send(CoreCommand::SetNoiseGateThreshold(config.noise_gate_threshold));
let _ = controller.send(CoreCommand::SetInputVolume(config.input_volume));
@@ -300,7 +302,7 @@ impl Default for AppState {
recording_started: None,
chat_messages: Vec::new(),
chat_input: String::new(),
window_size: Size::new(900.0, 760.0),
window_size: Size::new(ww, wh),
layout_picker_open: false,
drawer_chat_open: false,
mic_level: 0.0,
@@ -320,16 +322,22 @@ fn theme(_state: &AppState) -> Theme {
}
pub fn run_gui() -> iced::Result {
// Restore the last window size (saved on close). Position can't be restored on
// Wayland — xdg-shell gives clients no way to set their own position — so we
// only persist size and leave placement to the compositor.
let saved = AppConfig::load();
let init_size = iced::Size::new(saved.window_width, saved.window_height);
iced::application(AppState::default, update, view)
.title("PeerSpeak P2P Voice Chat")
.theme(theme)
.subscription(subscription)
.window(iced::window::Settings {
// Taller default so the bottom chat dock doesn't squeeze the controls
// column. Layout is responsive (Fill), so resizing still works.
size: iced::Size::new(900.0, 760.0),
// Restored from config (defaults 900×760: taller so the bottom chat
// dock doesn't squeeze the controls column). Layout is responsive.
size: init_size,
position: iced::window::Position::Centered,
exit_on_close_request: true,
// We save the final size ourselves on CloseRequested, then exit.
exit_on_close_request: false,
..Default::default()
})
.run()
@@ -734,6 +742,12 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
}
AppMessage::EventOccurred(Event::Window(iced::window::Event::Resized(size))) => {
state.window_size = size;
// Remember the size in-memory; it's written to disk once on close.
// Guard against bogus tiny/zero sizes some compositors emit transiently.
if size.width >= 200.0 && size.height >= 200.0 {
state.config.window_width = size.width;
state.config.window_height = size.height;
}
// Keep divider positions valid for the new window dimensions. (Saved
// with the next drag-release or other config write; not worth a disk
// write on every resize tick.)
@@ -746,6 +760,13 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
state.config.chat_drawer_width =
clamp_chat_drawer_width(state.config.chat_drawer_width, size.width);
}
AppMessage::EventOccurred(Event::Window(iced::window::Event::CloseRequested)) => {
// We took over the close path (exit_on_close_request:false) so we can
// persist the final window size before quitting. The latest size is
// already mirrored into config by the Resized handler above.
state.config.save();
return iced::exit();
}
AppMessage::EventOccurred(_) => {}
AppMessage::NavigateToSettings => {
state.current_screen = Screen::Settings;
+37
View File
@@ -92,6 +92,14 @@ fn default_chat_drawer_width() -> f32 {
320.0
}
fn default_window_width() -> f32 {
900.0
}
fn default_window_height() -> f32 {
760.0
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AppConfig {
/// Last nickname used to join/create a room; pre-filled on the launch screen.
@@ -149,6 +157,12 @@ pub struct AppConfig {
/// Empty / unset = look it up on `$PATH`. Hand-editable; no Settings UI yet.
#[serde(default)]
pub pixelpass_path: Option<String>,
/// Last window size (px), restored as the initial size on next launch.
/// Saved on close. (Window *position* can't be restored on Wayland.)
#[serde(default = "default_window_width")]
pub window_width: f32,
#[serde(default = "default_window_height")]
pub window_height: f32,
}
impl Default for AppConfig {
@@ -177,6 +191,8 @@ impl Default for AppConfig {
custom_sound_mic_toggle: None,
custom_sound_reconnect_failed: None,
pixelpass_path: None,
window_width: default_window_width(),
window_height: default_window_height(),
}
}
}
@@ -251,6 +267,27 @@ mod tests {
assert!(deserialized.custom_sound_self_leave.is_none());
assert!(deserialized.custom_sound_mic_toggle.is_none());
assert!(deserialized.custom_sound_reconnect_failed.is_none());
// Configs predating the remembered window size load the default size.
assert_eq!(deserialized.window_width, 900.0);
assert_eq!(deserialized.window_height, 760.0);
}
#[test]
fn test_window_size_fields() {
// Default impl is the standard launch size.
let def = AppConfig::default();
assert_eq!(def.window_width, 900.0);
assert_eq!(def.window_height, 760.0);
// A saved size round-trips.
let cfg = AppConfig {
window_width: 1280.0,
window_height: 720.0,
..AppConfig::default()
};
let json = serde_json::to_string(&cfg).unwrap();
let back: AppConfig = serde_json::from_str(&json).unwrap();
assert_eq!(back.window_width, 1280.0);
assert_eq!(back.window_height, 720.0);
}
#[test]