mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Windows: a maximized window stays clear of the taskbar (fixes #1977)
This commit is contained in:
committed by
bflatastic
parent
52b072873d
commit
4b8a987445
@@ -8,6 +8,13 @@
|
||||
//! same in storytold/photocraft#2401). [`service`] strips the two styles and has Windows
|
||||
//! recalculate the frame; winit puts them back whenever it changes the window flags (maximize,
|
||||
//! restore, …), so the returned guard runs every frame.
|
||||
//!
|
||||
//! Windows sizes a maximize by the frame its styles describe, so a window maximized while its
|
||||
//! caption is gone is sized without one: its rect covers the whole monitor, taskbar included, and
|
||||
//! the shell reads a caption-less window covering its monitor as a full-screen app (#1977). The
|
||||
//! guard therefore also trims a parked maximize's rect back to the work area plus its own frame,
|
||||
//! clear of the taskbar, as PhotoCraft does (storytold/photocraft#3308). winit pins the client
|
||||
//! area to the work area either way (`WM_NCCALCSIZE`), so the app's pixels do not move.
|
||||
|
||||
/// Run once per frame; a no-op once the window is borderless.
|
||||
pub type WindowFrameFn = Box<dyn FnMut()>;
|
||||
@@ -30,8 +37,15 @@ mod platform {
|
||||
_ => return None,
|
||||
};
|
||||
let hwnd = find_window(target)?;
|
||||
strip(&hwnd);
|
||||
Some(Box::new(move || strip(&hwnd)))
|
||||
reapply(&hwnd);
|
||||
Some(Box::new(move || reapply(&hwnd)))
|
||||
}
|
||||
|
||||
/// One pass of the per-frame guard: the styles winit put back come off again, and a parked
|
||||
/// maximize stays clear of the taskbar.
|
||||
fn reapply(hwnd: &HWND) {
|
||||
strip(hwnd);
|
||||
keep_clear_of_the_taskbar(hwnd);
|
||||
}
|
||||
|
||||
/// The top-level window with the given raw handle: `EnumWindows` hands out real `HWND`s, so
|
||||
@@ -65,6 +79,29 @@ mod platform {
|
||||
co::SWP::FRAMECHANGED | co::SWP::NOMOVE | co::SWP::NOSIZE | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
|
||||
);
|
||||
}
|
||||
|
||||
/// [`super::clamped_height`] for this frame's window: trim a maximized window whose rect runs
|
||||
/// under the taskbar back to the work area plus its frame. No-op otherwise.
|
||||
fn keep_clear_of_the_taskbar(hwnd: &HWND) {
|
||||
if !hwnd.IsZoomed() {
|
||||
return;
|
||||
}
|
||||
let (Ok(r), Ok(mi)) = (hwnd.GetWindowRect(), hwnd.MonitorFromWindow(co::MONITOR::DEFAULTTONEAREST).GetMonitorInfo()) else {
|
||||
return;
|
||||
};
|
||||
let (m, w) = (mi.rcMonitor, mi.rcWork);
|
||||
let Some(height) =
|
||||
super::clamped_height((r.left, r.top, r.right, r.bottom), (m.left, m.top, m.right, m.bottom), (w.left, w.top, w.right, w.bottom))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = hwnd.SetWindowPos(
|
||||
HwndPlace::None,
|
||||
POINT::new(),
|
||||
SIZE { cx: r.right - r.left, cy: height },
|
||||
co::SWP::NOMOVE | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(windows, feature = "wgpu")))]
|
||||
@@ -73,3 +110,65 @@ mod platform {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// How far a parked maximize reaches past its monitor on the left, top and right: the resize border
|
||||
/// (about 8 pt at 100 %, more with DPI). Past that, a maximized window's rect is not parked but
|
||||
/// being dragged or snapped, and is left alone.
|
||||
const FRAME: i32 = 32;
|
||||
|
||||
/// The height to trim a maximize to so its rect stops at the taskbar, or `None` when it already
|
||||
/// does or `rect` (`left, top, right, bottom`, desktop coordinates) is not parked at its maximize
|
||||
/// place. `monitor` is the monitor's full rect and `work` its work area (the monitor less the
|
||||
/// taskbar) in the same coordinates. The parked check is against the full monitor, because that is
|
||||
/// where Windows puts a caption-less maximize; the trim is against the work area. Platform-neutral
|
||||
/// so it is tested everywhere.
|
||||
#[cfg_attr(not(all(windows, feature = "wgpu")), allow(dead_code))]
|
||||
fn clamped_height(rect: (i32, i32, i32, i32), monitor: (i32, i32, i32, i32), work: (i32, i32, i32, i32)) -> Option<i32> {
|
||||
let (l, t, r, b) = rect;
|
||||
let (ml, mt, mr, _) = monitor;
|
||||
let (_, _, _, wb) = work;
|
||||
let side = ml - l;
|
||||
if !(0..=FRAME).contains(&side) || !(0..=FRAME).contains(&(mt - t)) || !(0..=FRAME).contains(&(r - mr)) {
|
||||
return None;
|
||||
}
|
||||
if b <= wb + side {
|
||||
return None;
|
||||
}
|
||||
Some(wb + side - t)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::clamped_height;
|
||||
|
||||
#[test]
|
||||
fn a_maximize_covering_the_taskbar_is_trimmed_to_the_work_area() {
|
||||
// 1920 × 1080 with a 34 px taskbar: a caption-less maximize covers the whole monitor,
|
||||
// taskbar included (#1977). Trimmed so the rect ends at the work area plus the frame the
|
||||
// window was parked with.
|
||||
let monitor = (0, 0, 1920, 1080);
|
||||
let work = (0, 0, 1920, 1046);
|
||||
assert_eq!(clamped_height((-7, -7, 1927, 1087), monitor, work), Some(1046 + 7 - (-7)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_maximize_already_clear_of_the_taskbar_is_left_alone() {
|
||||
// What Windows sizes a framed maximize to: the work area plus the frame.
|
||||
assert_eq!(clamped_height((-8, -8, 1928, 1054), (0, 0, 1920, 1080), (0, 0, 1920, 1046)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_maximize_beside_a_side_taskbar_is_left_alone() {
|
||||
// Taskbar on the left: the work area starts further right, but the monitor's bottom is
|
||||
// still clear, so the rect reaches nothing.
|
||||
assert_eq!(clamped_height((-8, -8, 1928, 1088), (0, 0, 1920, 1080), (48, 0, 1920, 1080)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_maximize_being_dragged_away_is_left_alone() {
|
||||
let monitor = (0, 0, 1920, 1080);
|
||||
let work = (0, 0, 1920, 1046);
|
||||
assert_eq!(clamped_height((100, 200, 1100, 900), monitor, work), None, "moved");
|
||||
assert_eq!(clamped_height((-500, -7, 1427, 1087), monitor, work), None, "resized");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user