Merge pull request #1791 from AyberkA0/fix/1758-cursor-hotspot

Fix #1758: keep the custom title bar window borderless on Windows
This commit is contained in:
BFlat 2026-10-14 04:24:20 +07:00 committed by GitHub
commit a1eb4fc090
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 86 additions and 1 deletions

View File

@ -45,6 +45,7 @@ mod printing;
#[cfg(all(windows, not(target_vendor = "win7")))]
mod system_fonts;
mod window;
mod window_frame;
mod xwayland;
use vectorcraft_engine::Session;
@ -62,6 +63,8 @@ struct App {
graphics_lost: bool,
/// Frames the UI has run (see [`end_before_teardown`]).
frames: u64,
/// Keeps the custom title bar's window borderless (Windows, #1758).
window_frame: Option<window_frame::WindowFrameFn>,
}
impl eframe::App for App {
@ -72,6 +75,11 @@ impl eframe::App for App {
file_access::unconfined(|| self.logic_frame(ctx, frame));
}
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
// winit gives the window its caption back when it changes the window's flags (maximize,
// restore, …): take it off again before the frame is laid out (#1758).
if let Some(window_frame) = self.window_frame.as_mut() {
window_frame();
}
#[cfg(target_os = "macos")]
if self.app.services.native_menu.is_some() {
mac_menu::raw_input_hook(raw);
@ -858,7 +866,9 @@ fn main() -> std::process::ExitCode {
// The first frame is due from now (`gpu::watch_first_frame`).
#[cfg(feature = "wgpu")]
created.ready();
Ok(Box::new(App { app, graphics_loss, graphics_lost: false, frames: 0 }))
// 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 }))
}),
)
});

View File

@ -0,0 +1,75 @@
//! Keeps the custom title bar's undecorated window truly borderless on Windows (#1758).
//!
//! winit 0.30 gives an undecorated window the `WS_CAPTION | WS_BORDER` styles and hides the
//! non-client area by spreading the client rect over it (`WM_NCCALCSIZE`). On some systems the
//! desktop compositor still reserves that frame for the window surface: the content is drawn
//! offset by the caption and border while the pointer keeps the un-offset client coordinates, so
//! it no longer points at what is drawn under it (rust-windowing/winit#2419; PhotoCraft fixed the
//! 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.
/// Run once per frame; a no-op once the window is borderless.
pub type WindowFrameFn = Box<dyn FnMut()>;
pub use platform::service;
#[cfg(all(windows, feature = "wgpu"))]
mod platform {
use eframe::wgpu::rwh::{HasWindowHandle, RawWindowHandle};
use winsafe::{HWND, HwndPlace, POINT, SIZE, co};
/// A guard that keeps the window borderless each frame, or `None` with the system title bar
/// (the window is decorated) or when its handle can't be found.
pub fn service(cc: &eframe::CreationContext<'_>, custom_titlebar: bool) -> Option<super::WindowFrameFn> {
if !custom_titlebar {
return None;
}
let target = match cc.window_handle().ok()?.as_raw() {
RawWindowHandle::Win32(h) => h.hwnd.get(),
_ => return None,
};
let hwnd = find_window(target)?;
strip(&hwnd);
Some(Box::new(move || strip(&hwnd)))
}
/// The top-level window with the given raw handle: `EnumWindows` hands out real `HWND`s, so
/// this needs no `unsafe` to wrap a raw one.
fn find_window(target: isize) -> Option<HWND> {
let mut found = None;
let _ = winsafe::EnumWindows(|hwnd| {
if hwnd.ptr() as isize == target {
found = Some(hwnd);
false
} else {
true
}
});
found
}
/// Removes `WS_CAPTION` (which includes `WS_BORDER`) and has Windows recalculate the frame;
/// the sizing border stays, so edge resizing and snapping keep working.
fn strip(hwnd: &HWND) {
let style = hwnd.style();
let without = style & !(co::WS::CAPTION | co::WS::BORDER);
if without.raw() == style.raw() {
return;
}
hwnd.set_style(without);
let _ = hwnd.SetWindowPos(
HwndPlace::None,
POINT::new(),
SIZE::new(),
co::SWP::FRAMECHANGED | co::SWP::NOMOVE | co::SWP::NOSIZE | co::SWP::NOZORDER | co::SWP::NOACTIVATE,
);
}
}
#[cfg(not(all(windows, feature = "wgpu")))]
mod platform {
pub fn service(_cc: &eframe::CreationContext<'_>, _custom_titlebar: bool) -> Option<super::WindowFrameFn> {
None
}
}