mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
fix(linux): restore and remember maximized window state
## Summary On Linux, asking winit to maximize before the window is mapped often does nothing, and egui sometimes omits the maximized viewport flag while the window manager still has the window maximized. Defer maximize until the first frames after startup, and read maximized state from winit when saving geometry for the next launch. ## Tests cargo test -p vectorcraft window:: cargo clippy -p vectorcraft --all-targets -- -D warnings Closes storytold/vectorcraft#3635 Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
This commit is contained in:
committed by
bflatastic
co-authored by
nrq6993-a11y
parent
329916c2b8
commit
0490ca0788
@@ -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,
|
||||
}))
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user