mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-17 04:39:54 +02:00
Fix custom-frame maximization with a top taskbar (#5237)
* fix(windows): respect top and side taskbars in custom-frame maximize * Keep work-area correction stable for narrow taskbar reservations * style: apply rustfmt to taskbar window correction * fix(window): repaint after custom-frame work-area correction
This commit is contained in:
parent
8215b0b969
commit
c73bb0ed95
|
|
@ -81,17 +81,16 @@ mod platform {
|
|||
HwndPlace::None,
|
||||
POINT::new(),
|
||||
SIZE::new(),
|
||||
co::SWP::FRAMECHANGED | co::SWP::NOMOVE | co::SWP::NOSIZE | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
|
||||
co::SWP::FRAMECHANGED | co::SWP::NOCOPYBITS | co::SWP::NOMOVE | co::SWP::NOSIZE | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
|
||||
);
|
||||
}
|
||||
|
||||
/// How far a parked maximize reaches past its monitor on the left, top and right: the resize
|
||||
/// How far a parked maximize reaches past a monitor/work-area edge: 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;
|
||||
|
||||
/// [`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 (#3144). No-op otherwise.
|
||||
/// Keep a parked maximize inside all four work-area edges, including a top taskbar.
|
||||
fn keep_clear_of_the_taskbar(hwnd: &HWND) {
|
||||
if !hwnd.IsZoomed() {
|
||||
return;
|
||||
|
|
@ -100,34 +99,58 @@ mod platform {
|
|||
return;
|
||||
};
|
||||
let (m, w) = (mi.rcMonitor, mi.rcWork);
|
||||
let Some(height) = 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 {
|
||||
let Some((left, top, right, bottom)) =
|
||||
clamped_rect((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 (Some(width), Some(height)) = (right.checked_sub(left), bottom.checked_sub(top)) else {
|
||||
return;
|
||||
};
|
||||
// Moving the outer frame while winit pins the client rect to the work area must
|
||||
// not copy the old frame's pixels into the custom title bar.
|
||||
let _ = hwnd.SetWindowPos(
|
||||
HwndPlace::None,
|
||||
POINT::new(),
|
||||
SIZE { cx: r.right - r.left, cy: height },
|
||||
co::SWP::NOMOVE | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
|
||||
POINT { x: left, y: top },
|
||||
SIZE { cx: width, cy: height },
|
||||
co::SWP::FRAMECHANGED | co::SWP::NOCOPYBITS | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
|
||||
);
|
||||
let _ = hwnd.InvalidateRect(None, false);
|
||||
}
|
||||
|
||||
/// 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 maximize; the trim is against the work area.
|
||||
fn clamped_height(rect: (i32, i32, i32, i32), monitor: (i32, i32, i32, i32), work: (i32, i32, i32, i32)) -> Option<i32> {
|
||||
/// Return the corrected outer rect in desktop coordinates, preserving the resize border.
|
||||
/// Each edge must be parked just outside either the monitor or work-area edge. Accepting
|
||||
/// both makes the guard idempotent and handles Windows already correcting some edges.
|
||||
/// Dragged/snapped windows are left alone; no monitor-origin or DPI assumptions are needed.
|
||||
fn clamped_rect(rect: (i32, i32, i32, i32), monitor: (i32, i32, i32, i32), work: (i32, i32, i32, i32)) -> Option<(i32, i32, i32, 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)) {
|
||||
let (ml, mt, mr, mb) = monitor;
|
||||
let (wl, wt, wr, wb) = work;
|
||||
if ml >= mr || mt >= mb || wl < ml || wt < mt || wr > mr || wb > mb || wl >= wr || wt >= wb {
|
||||
return None;
|
||||
}
|
||||
if b <= wb + side {
|
||||
// Infer the frame from an edge without a taskbar. On an obstructed edge, the
|
||||
// monitor and work-area offsets can both look like valid borders (e.g. auto-hide).
|
||||
let border = [
|
||||
(ml == wl).then(|| ml.checked_sub(l)).flatten(),
|
||||
(mt == wt).then(|| mt.checked_sub(t)).flatten(),
|
||||
(mr == wr).then(|| r.checked_sub(mr)).flatten(),
|
||||
(mb == wb).then(|| b.checked_sub(mb)).flatten(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|n| (0..=FRAME).contains(n))
|
||||
.min()?;
|
||||
let parked = |monitor: Option<i32>, work: Option<i32>| monitor == Some(border) || work == Some(border);
|
||||
if !parked(ml.checked_sub(l), wl.checked_sub(l))
|
||||
|| !parked(mt.checked_sub(t), wt.checked_sub(t))
|
||||
|| !parked(r.checked_sub(mr), r.checked_sub(wr))
|
||||
|| !parked(b.checked_sub(mb), b.checked_sub(wb))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(wb + side - t)
|
||||
let corrected = (wl.checked_sub(border)?, wt.checked_sub(border)?, wr.checked_add(border)?, wb.checked_add(border)?);
|
||||
(corrected != rect).then_some(corrected)
|
||||
}
|
||||
|
||||
/// `style` without the OS title bar and frame (`WS_CAPTION`, which already includes
|
||||
|
|
@ -139,7 +162,7 @@ mod platform {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{borderless, clamped_height};
|
||||
use super::{borderless, clamped_rect};
|
||||
use winsafe::co;
|
||||
|
||||
#[test]
|
||||
|
|
@ -156,34 +179,47 @@ mod platform {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn a_maximize_covering_the_taskbar_is_trimmed_to_the_work_area() {
|
||||
// 1920 × 1080 with a 34 pt taskbar: a caption-less maximize covers the whole
|
||||
// monitor, taskbar included (#3144). Trimmed so the rect ends at the work area
|
||||
// plus the frame the window was parked with.
|
||||
fn a_maximize_respects_taskbars_on_every_edge() {
|
||||
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)));
|
||||
let rect = (-8, -8, 1928, 1088);
|
||||
for (work, expected) in [
|
||||
((0, 0, 1920, 1046), (-8, -8, 1928, 1054)),
|
||||
((0, 48, 1920, 1080), (-8, 40, 1928, 1088)),
|
||||
((0, 2, 1920, 1080), (-8, -6, 1928, 1088)),
|
||||
((0, 0, 1920, 1078), (-8, -8, 1928, 1086)),
|
||||
((48, 0, 1920, 1080), (40, -8, 1928, 1088)),
|
||||
((0, 0, 1872, 1080), (-8, -8, 1880, 1088)),
|
||||
] {
|
||||
assert_eq!(clamped_rect(rect, monitor, work), Some(expected));
|
||||
assert_eq!(clamped_rect(expected, monitor, work), None, "already corrected");
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
fn a_top_taskbar_keeps_its_offset_at_other_origins_and_frame_sizes() {
|
||||
let monitor = (-1920, -1080, 0, 0);
|
||||
let work = (-1920, -1032, 0, 0);
|
||||
for border in [0, 8, 16, 32] {
|
||||
let rect = (-1920 - border, -1080 - border, border, border);
|
||||
let expected = (-1920 - border, -1032 - border, border, border);
|
||||
assert_eq!(clamped_rect(rect, monitor, work), Some(expected));
|
||||
assert_eq!(clamped_rect(expected, monitor, work), 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() {
|
||||
fn a_maximize_without_a_reserved_taskbar_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");
|
||||
assert_eq!(clamped_rect((-8, -8, 1928, 1088), monitor, monitor), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_maximize_being_dragged_or_snapped_is_left_alone() {
|
||||
let monitor = (0, 0, 1920, 1080);
|
||||
let work = (0, 48, 1920, 1080);
|
||||
assert_eq!(clamped_rect((100, 200, 1100, 900), monitor, work), None, "moved");
|
||||
assert_eq!(clamped_rect((-500, -8, 1428, 1088), monitor, work), None, "dragged");
|
||||
assert_eq!(clamped_rect((0, 48, 960, 1080), monitor, work), None, "half-screen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user