Merge pull request #3660 from nrq6993-a11y/stream/vc-issue-3635-linux-maximize

Restore and remember maximized window state
This commit is contained in:
BFlat
2026-10-23 00:10:28 +07:00
committed by GitHub
2 changed files with 70 additions and 8 deletions
+31 -4
View File
@@ -70,6 +70,12 @@ struct App {
frames: u64,
/// Keeps the custom title bar's window borderless (Windows, #1758).
window_frame: Option<window_frame::WindowFrameFn>,
/// The main window; used to read and set maximized state the UI layer does not see on Linux (#3635).
winit_window: Option<std::sync::Arc<winit::window::Window>>,
/// 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,
}))
}),
)
});
+39 -4
View File
@@ -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>) -> WindowGeometry {
let handles: Vec<_> = window.available_monitors().collect();
let monitors: Vec<Monitor> = 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<WindowGeometry>) ->
}
// 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<WindowGeometry>) {
/// `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<WindowGeometry>, host_maximized: Option<bool>) {
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 }));
}
}