diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index c857943fd..854bc7ef2 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -70,6 +70,12 @@ struct App { frames: u64, /// Keeps the custom title bar's window borderless (Windows, #1758). window_frame: Option, + /// The main window; used to read and set maximized state the UI layer does not see on Linux (#3635). + winit_window: Option>, + /// A saved or fitted geometry asked for maximize once the window is mapped (Linux). + pending_maximize: bool, + /// Frames left to retry [`window::apply_pending_maximize`]. + maximize_tries: u8, } impl eframe::App for App { @@ -147,7 +153,15 @@ impl App { open_files(&mut self.app, open_documents::take()); } self.app.logic(ctx); - window::track(ctx, &mut self.app.ui.window); + if self.pending_maximize { + if let Some(w) = &self.winit_window { + self.pending_maximize = window::apply_pending_maximize(w, true, &mut self.maximize_tries); + } else { + self.pending_maximize = false; + } + } + let host_maximized = self.winit_window.as_ref().map(|w| w.is_maximized()); + window::track(ctx, &mut self.app.ui.window, host_maximized); if self.app.ui.status == "quit" { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } @@ -953,11 +967,15 @@ fn main() -> std::process::ExitCode { // which takes over from the copy the preferences file used to hold. prefs_dir::attach_pdf_preset_store(&mut app.session); // Fit the window to its monitor, or put it back where it was (still hidden). - if let Some(w) = cc.winit_window() { - app.ui.window = Some(window::restore(w, saved_window)); + let winit_window = cc.winit_window().cloned(); + if let Some(w) = &winit_window { + let g = window::restore(w, saved_window); + app.ui.window = Some(g); #[cfg(windows)] set_taskbar_icon(w); } + // Linux may not map the window before the first frames, so a saved maximize is retried then (#3635). + let pending_maximize = cfg!(target_os = "linux") && app.ui.window.as_ref().is_some_and(|g| g.maximized); // User Defined swatch and graphic style libraries live next to the preferences. let swatches = prefs_path().and_then(|p| Some(p.parent()?.join("Swatches").to_string_lossy().to_string())); app.session.swatch_libraries.set_user_dir(swatches); @@ -1038,7 +1056,16 @@ fn main() -> std::process::ExitCode { created.ready(); // The content and the pointer stay aligned on Windows (#1758). let window_frame = window_frame::service(cc, custom_titlebar); - Ok(Box::new(App { app, graphics_loss, graphics_lost: false, frames: 0, window_frame })) + Ok(Box::new(App { + app, + graphics_loss, + graphics_lost: false, + frames: 0, + window_frame, + winit_window, + pending_maximize, + maximize_tries: 8, + })) }), ) }); diff --git a/apps/vectorcraft/src/window.rs b/apps/vectorcraft/src/window.rs index e95326423..abe60f69f 100644 --- a/apps/vectorcraft/src/window.rs +++ b/apps/vectorcraft/src/window.rs @@ -110,7 +110,8 @@ fn fit(size: [f32; 2], room: [f32; 2]) -> [f32; 2] { } /// Size and place the new window (eframe shows it after drawing the first frame) and return its -/// geometry. +/// geometry. On Linux the window may not be mapped yet, so [`apply_pending_maximize`] must run +/// until the host reports maximized (fixes #3635). pub fn restore(window: &winit::window::Window, saved: Option) -> WindowGeometry { let handles: Vec<_> = window.available_monitors().collect(); let monitors: Vec = handles.iter().map(|m| Monitor::new(m.position(), m.size(), m.scale_factor())).collect(); @@ -121,22 +122,41 @@ pub fn restore(window: &winit::window::Window, saved: Option) -> } // Returns the new size when it applies at once; either way the window gets it. let _ = window.request_inner_size(LogicalSize::new(g.size[0], g.size[1])); + #[cfg(not(target_os = "linux"))] if g.maximized { window.set_maximized(true); } g } +/// Keep asking the host to maximize until it sticks, or give up after a few frames. +pub fn apply_pending_maximize(window: &winit::window::Window, want: bool, tries: &mut u8) -> bool { + if !want { + return false; + } + if window.is_maximized() { + return false; + } + if *tries == 0 { + return false; + } + *tries -= 1; + window.set_maximized(true); + true +} + /// Per frame: remember the window's geometry for the next launch. Minimized or full screen keeps /// the last one; maximized only sets its flag, so un-maximizing next time restores the last size. -pub fn track(ctx: &egui::Context, geometry: &mut Option) { +/// `host_maximized` is the window manager's idea of maximized when egui does not report it (#3635). +pub fn track(ctx: &egui::Context, geometry: &mut Option, host_maximized: Option) { let zoom = ctx.zoom_factor(); ctx.input(|i| { let v = i.viewport(); if v.minimized == Some(true) || v.fullscreen == Some(true) { return; } - if v.maximized == Some(true) { + let maximized = v.maximized == Some(true) || host_maximized == Some(true); + if maximized { if let Some(g) = geometry { g.maximized = true; } @@ -222,7 +242,7 @@ mod tests { let mut frame = |info: egui::ViewportInfo| { let mut input = egui::RawInput::default(); input.viewports.insert(egui::ViewportId::ROOT, info); - ctx.run_ui(input, |ui| track(ui.ctx(), &mut geometry)).textures_delta.clear(); + ctx.run_ui(input, |ui| track(ui.ctx(), &mut geometry, None)).textures_delta.clear(); geometry }; let rect = egui::Rect::from_min_size(egui::pos2(20.0, 30.0), egui::vec2(1000.0, 600.0)); @@ -253,4 +273,19 @@ mod tests { let back: vectorcraft_ui_egui::UiState = serde_json::from_value(json).unwrap(); assert_eq!(back.window, None); } + + #[test] + fn the_host_maximized_flag_is_remembered_when_egui_does_not_report_it() { + let ctx = egui::Context::default(); + let mut geometry = Some(WindowGeometry { pos: Some([10, 20]), size: [800.0, 600.0], maximized: false }); + let rect = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(1920.0, 1080.0)); + let mut input = egui::RawInput::default(); + input.viewports.insert( + egui::ViewportId::ROOT, + egui::ViewportInfo { inner_rect: Some(rect), outer_rect: Some(rect), maximized: None, ..Default::default() }, + ); + let mut out = ctx.run_ui(input, |ui| track(ui.ctx(), &mut geometry, Some(true))); + out.textures_delta.clear(); + assert_eq!(geometry, Some(WindowGeometry { pos: Some([10, 20]), size: [800.0, 600.0], maximized: true })); + } }