Desktop: Window resize handling on Windows (#3167)
* Native window resize on windows * Fix linux build * Fix windows build * try clean up * clean up * Add module comment * FIx * Review improvements * Improve
This commit is contained in:
parent
da330b6dd0
commit
ab55b3225d
|
|
@ -41,7 +41,7 @@ vello = { workspace = true }
|
||||||
derivative = { workspace = true }
|
derivative = { workspace = true }
|
||||||
rfd = { workspace = true }
|
rfd = { workspace = true }
|
||||||
open = { workspace = true }
|
open = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true, features = ["thread_rng"] }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
||||||
# Hardware acceleration dependencies
|
# Hardware acceleration dependencies
|
||||||
|
|
@ -49,12 +49,17 @@ ash = { version = "0.38", optional = true }
|
||||||
|
|
||||||
# Windows-specific dependencies
|
# Windows-specific dependencies
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.58", features = [
|
windows = { version = "0.58.0", features = [
|
||||||
|
"Win32_Foundation",
|
||||||
"Win32_Graphics_Direct3D11",
|
"Win32_Graphics_Direct3D11",
|
||||||
"Win32_Graphics_Direct3D12",
|
"Win32_Graphics_Direct3D12",
|
||||||
"Win32_Graphics_Dxgi",
|
"Win32_Graphics_Dxgi",
|
||||||
"Win32_Graphics_Dxgi_Common",
|
"Win32_Graphics_Dxgi_Common",
|
||||||
"Win32_Foundation"
|
"Win32_Graphics_Dwm",
|
||||||
|
"Win32_Graphics_Gdi",
|
||||||
|
"Win32_System_LibraryLoader",
|
||||||
|
"Win32_UI_Controls",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
||||||
# macOS-specific dependencies
|
# macOS-specific dependencies
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,12 @@ use winit::window::Window;
|
||||||
use winit::window::WindowId;
|
use winit::window::WindowId;
|
||||||
|
|
||||||
use crate::cef;
|
use crate::cef;
|
||||||
|
use crate::native_window;
|
||||||
|
|
||||||
pub(crate) struct WinitApp {
|
pub(crate) struct WinitApp {
|
||||||
cef_context: Box<dyn cef::CefContext>,
|
cef_context: Box<dyn cef::CefContext>,
|
||||||
window: Option<Arc<Window>>,
|
window: Option<Arc<Window>>,
|
||||||
|
native_window: native_window::NativeWindowHandle,
|
||||||
cef_schedule: Option<Instant>,
|
cef_schedule: Option<Instant>,
|
||||||
window_size_sender: Sender<WindowSize>,
|
window_size_sender: Sender<WindowSize>,
|
||||||
graphics_state: Option<GraphicsState>,
|
graphics_state: Option<GraphicsState>,
|
||||||
|
|
@ -71,6 +73,7 @@ impl WinitApp {
|
||||||
web_communication_initialized: false,
|
web_communication_initialized: false,
|
||||||
web_communication_startup_buffer: Vec::new(),
|
web_communication_startup_buffer: Vec::new(),
|
||||||
persistent_data,
|
persistent_data,
|
||||||
|
native_window: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,22 +280,15 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
.with_title(APP_NAME)
|
.with_title(APP_NAME)
|
||||||
.with_min_inner_size(winit::dpi::LogicalSize::new(400, 300))
|
.with_min_inner_size(winit::dpi::LogicalSize::new(400, 300))
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800))
|
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800))
|
||||||
.with_decorations(false)
|
|
||||||
.with_resizable(true);
|
.with_resizable(true);
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
window = self.native_window.build(window, event_loop);
|
||||||
{
|
|
||||||
use crate::consts::APP_ID;
|
|
||||||
use winit::platform::wayland::ActiveEventLoopExtWayland;
|
|
||||||
|
|
||||||
window = if event_loop.is_wayland() {
|
let window = event_loop.create_window(window).unwrap();
|
||||||
winit::platform::wayland::WindowAttributesExtWayland::with_name(window, APP_ID, "")
|
|
||||||
} else {
|
|
||||||
winit::platform::x11::WindowAttributesExtX11::with_name(window, APP_ID, APP_NAME)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let window = Arc::new(event_loop.create_window(window).unwrap());
|
self.native_window.setup(&window);
|
||||||
|
|
||||||
|
let window = Arc::new(window);
|
||||||
let graphics_state = GraphicsState::new(window.clone(), self.wgpu_context.clone());
|
let graphics_state = GraphicsState::new(window.clone(), self.wgpu_context.clone());
|
||||||
|
|
||||||
self.window = Some(window);
|
self.window = Some(window);
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,6 @@ impl D3D11Importer {
|
||||||
|
|
||||||
fn import_d3d11_handle_to_d3d12(&self, hal_device: &<wgpu::hal::api::Dx12 as wgpu::hal::Api>::Device) -> Result<windows::Win32::Graphics::Direct3D12::ID3D12Resource, TextureImportError> {
|
fn import_d3d11_handle_to_d3d12(&self, hal_device: &<wgpu::hal::api::Dx12 as wgpu::hal::Api>::Device) -> Result<windows::Win32::Graphics::Direct3D12::ID3D12Resource, TextureImportError> {
|
||||||
use windows::Win32::Graphics::Direct3D12::*;
|
use windows::Win32::Graphics::Direct3D12::*;
|
||||||
use windows::core::*;
|
|
||||||
|
|
||||||
// Get D3D12 device from wgpu-hal
|
// Get D3D12 device from wgpu-hal
|
||||||
let d3d12_device = hal_device.raw_device();
|
let d3d12_device = hal_device.raw_device();
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub(crate) mod consts;
|
||||||
|
|
||||||
mod cef;
|
mod cef;
|
||||||
|
|
||||||
|
mod native_window;
|
||||||
|
|
||||||
mod render;
|
mod render;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
use winit::event_loop::ActiveEventLoop;
|
||||||
|
use winit::window::{Window, WindowAttributes};
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
pub(crate) enum NativeWindowHandle {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
#[expect(private_interfaces, dead_code)]
|
||||||
|
Windows(windows::WindowsNativeWindowHandle),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
impl Default for NativeWindowHandle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl NativeWindowHandle {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub(super) fn build(&mut self, window: WindowAttributes, event_loop: &ActiveEventLoop) -> WindowAttributes {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
use crate::consts::{APP_ID, APP_NAME};
|
||||||
|
use winit::platform::wayland::ActiveEventLoopExtWayland;
|
||||||
|
if event_loop.is_wayland() {
|
||||||
|
winit::platform::wayland::WindowAttributesExtWayland::with_name(window, APP_ID, "")
|
||||||
|
} else {
|
||||||
|
winit::platform::x11::WindowAttributesExtX11::with_name(window, APP_ID, APP_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub(crate) fn setup(&mut self, window: &Window) {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
*self = NativeWindowHandle::Windows(windows::WindowsNativeWindowHandle::new(window));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
//! Implements a Windows-specific custom window frame (no titlebar, but native boarder, shadows and resize).
|
||||||
|
//! Look and feel should be similar to a standard window.
|
||||||
|
//!
|
||||||
|
//! Implementation notes:
|
||||||
|
//! - Windows that don't use standard decorations don't get native resize handles or shadows by default.
|
||||||
|
//! - We implement resize handles (outside the main window) by creating an invisible "helper" window that
|
||||||
|
//! is a little larger than the main window and positioned on top of it. The helper window does hit-testing
|
||||||
|
//! and triggers native resize operations on the main window when the user clicks and drags a resize area.
|
||||||
|
//! - The helper window is a invisible window that never activates, so it doesn't steal focus from the main window.
|
||||||
|
//! - The main window needs to update the helper window's position and size whenever it moves or resizes.
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use wgpu::rwh::{HasWindowHandle, RawWindowHandle};
|
||||||
|
use windows::Win32::Foundation::*;
|
||||||
|
use windows::Win32::Graphics::{Dwm::*, Gdi::HBRUSH};
|
||||||
|
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||||
|
use windows::Win32::UI::Controls::MARGINS;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::*;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub(super) struct WindowsNativeWindowHandle {
|
||||||
|
inner: WindowsNativeWindowHandleInner,
|
||||||
|
}
|
||||||
|
impl WindowsNativeWindowHandle {
|
||||||
|
pub(super) fn new(window: &Window) -> Self {
|
||||||
|
let inner = WindowsNativeWindowHandleInner::new(window);
|
||||||
|
WindowsNativeWindowHandle { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for WindowsNativeWindowHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.inner.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct WindowsNativeWindowHandleInner {
|
||||||
|
main: HWND,
|
||||||
|
helper: HWND,
|
||||||
|
prev_window_message_handler: isize,
|
||||||
|
}
|
||||||
|
impl WindowsNativeWindowHandleInner {
|
||||||
|
fn new(window: &Window) -> WindowsNativeWindowHandleInner {
|
||||||
|
// Extract Win32 HWND from winit.
|
||||||
|
let hwnd = match window.window_handle().expect("No window handle").as_raw() {
|
||||||
|
RawWindowHandle::Win32(h) => HWND(h.hwnd.get() as *mut std::ffi::c_void),
|
||||||
|
_ => panic!("Not a Win32 window"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the invisible helper (resize ring) window class.
|
||||||
|
unsafe { ensure_helper_class() };
|
||||||
|
|
||||||
|
// Create the helper as a popup tool window that never activates.
|
||||||
|
// WS_EX_NOACTIVATE keeps focus on the main window; WS_EX_TOOLWINDOW hides it from Alt+Tab.
|
||||||
|
// https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles
|
||||||
|
let ex = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW;
|
||||||
|
let style = WS_POPUP;
|
||||||
|
let helper = unsafe {
|
||||||
|
CreateWindowExW(
|
||||||
|
ex,
|
||||||
|
PCWSTR(HELPER_CLASS_NAME.encode_utf16().collect::<Vec<_>>().as_ptr()),
|
||||||
|
PCWSTR::null(),
|
||||||
|
style,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
HINSTANCE(std::ptr::null_mut()),
|
||||||
|
// Pass the main window's HWND to WM_NCCREATE so the helper can store it.
|
||||||
|
Some(&hwnd as *const _ as _),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.expect("CreateWindowExW failed");
|
||||||
|
|
||||||
|
// Subclass the main window.
|
||||||
|
// https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowlongptra
|
||||||
|
let prev_window_message_handler = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, main_window_handle_message as isize) };
|
||||||
|
if prev_window_message_handler == 0 {
|
||||||
|
let _ = unsafe { DestroyWindow(helper) };
|
||||||
|
panic!("SetWindowLongPtrW failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner = WindowsNativeWindowHandleInner {
|
||||||
|
main: hwnd,
|
||||||
|
helper,
|
||||||
|
prev_window_message_handler,
|
||||||
|
};
|
||||||
|
registry::insert(&inner);
|
||||||
|
|
||||||
|
// Place the helper over the main window and show it without activation.
|
||||||
|
unsafe { position_helper(hwnd, helper) };
|
||||||
|
let _ = unsafe { ShowWindow(helper, SW_SHOWNOACTIVATE) };
|
||||||
|
|
||||||
|
// DwmExtendFrameIntoClientArea is needed to keep native window frame (but no titlebar).
|
||||||
|
// https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea
|
||||||
|
// https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||||
|
let mut boarder_size: u32 = 1;
|
||||||
|
let _ = unsafe { DwmGetWindowAttribute(hwnd, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::<u32>() as u32) };
|
||||||
|
let margins = MARGINS {
|
||||||
|
cxLeftWidth: 0,
|
||||||
|
cxRightWidth: 0,
|
||||||
|
cyBottomHeight: 0,
|
||||||
|
cyTopHeight: boarder_size as i32,
|
||||||
|
};
|
||||||
|
let _ = unsafe { DwmExtendFrameIntoClientArea(hwnd, &margins) };
|
||||||
|
|
||||||
|
// Force window update
|
||||||
|
let _ = unsafe { SetWindowPos(hwnd, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) };
|
||||||
|
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) {
|
||||||
|
registry::remove_by_main(self.main);
|
||||||
|
|
||||||
|
// Undo subclassing and destroy the helper window.
|
||||||
|
let _ = unsafe { SetWindowLongPtrW(self.main, GWLP_WNDPROC, self.prev_window_message_handler) };
|
||||||
|
if self.helper.0 != std::ptr::null_mut() {
|
||||||
|
let _ = unsafe { DestroyWindow(self.helper) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod registry {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
|
||||||
|
use crate::native_window::windows::WindowsNativeWindowHandleInner;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static STORE: RefCell<Vec<WindowsNativeWindowHandleInner>> = RefCell::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn find_by_main(main: HWND) -> Option<WindowsNativeWindowHandleInner> {
|
||||||
|
STORE.with_borrow(|vec| vec.iter().find(|h| h.main == main).cloned())
|
||||||
|
}
|
||||||
|
pub(super) fn remove_by_main(main: HWND) {
|
||||||
|
STORE.with_borrow_mut(|vec| {
|
||||||
|
vec.retain(|h| h.main != main);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub(super) fn insert(handle: &WindowsNativeWindowHandleInner) {
|
||||||
|
STORE.with_borrow_mut(|vec| {
|
||||||
|
vec.push(handle.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HELPER_CLASS_NAME: &str = "Helper\0";
|
||||||
|
|
||||||
|
static HELPER_CLASS_LOCK: OnceLock<u16> = OnceLock::new();
|
||||||
|
unsafe fn ensure_helper_class() {
|
||||||
|
// Register a window class for the invisible resize helper.
|
||||||
|
let _ = *HELPER_CLASS_LOCK.get_or_init(|| {
|
||||||
|
let class_name: Vec<u16> = HELPER_CLASS_NAME.encode_utf16().collect();
|
||||||
|
let wc = WNDCLASSW {
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
|
lpfnWndProc: Some(helper_window_handle_message),
|
||||||
|
hInstance: unsafe { GetModuleHandleW(None).unwrap().into() },
|
||||||
|
hIcon: HICON::default(),
|
||||||
|
hCursor: unsafe { LoadCursorW(HINSTANCE(std::ptr::null_mut()), IDC_ARROW).unwrap() },
|
||||||
|
// No painting; the ring is invisible.
|
||||||
|
hbrBackground: HBRUSH::default(),
|
||||||
|
lpszClassName: PCWSTR(class_name.as_ptr()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
unsafe { RegisterClassW(&wc) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main window message handler, called on the UI thread for every message the main window receives.
|
||||||
|
unsafe extern "system" fn main_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
if msg == WM_NCCALCSIZE {
|
||||||
|
if wparam.0 != 0 {
|
||||||
|
// Return 0 to to tell Windows to skip the default non-client area calculation and drawing.
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(handle) = registry::find_by_main(hwnd) else {
|
||||||
|
return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
|
||||||
|
};
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
// Keep the invisible resize helper in sync with moves/resizes/visibility.
|
||||||
|
WM_MOVE | WM_MOVING | WM_SIZE | WM_SIZING | WM_WINDOWPOSCHANGED | WM_SHOWWINDOW => {
|
||||||
|
if msg == WM_SHOWWINDOW {
|
||||||
|
if wparam.0 == 0 {
|
||||||
|
let _ = unsafe { ShowWindow(handle.helper, SW_HIDE) };
|
||||||
|
} else {
|
||||||
|
let _ = unsafe { ShowWindow(handle.helper, SW_SHOWNOACTIVATE) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { position_helper(hwnd, handle.helper) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the main window is destroyed, destroy the helper too.
|
||||||
|
// Should only be needed if windows forcefully destroys the main window.
|
||||||
|
WM_DESTROY => {
|
||||||
|
let _ = unsafe { DestroyWindow(handle.helper) };
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the previous window message handler is not null.
|
||||||
|
assert_ne!(handle.prev_window_message_handler, 0);
|
||||||
|
|
||||||
|
// Call the previous window message handler, this is a standard subclassing pattern.
|
||||||
|
let prev_window_message_handler_fn_ptr: *const () = std::ptr::without_provenance(handle.prev_window_message_handler as usize);
|
||||||
|
let prev_window_message_handler_fn = unsafe { std::mem::transmute::<_, _>(prev_window_message_handler_fn_ptr) };
|
||||||
|
return unsafe { CallWindowProcW(Some(prev_window_message_handler_fn), hwnd, msg, wparam, lparam) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper window message handler, called on the UI thread for every message the helper window receives.
|
||||||
|
unsafe extern "system" fn helper_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
match msg {
|
||||||
|
// Helper window creation, should be the first message that the helper window receives.
|
||||||
|
WM_NCCREATE => {
|
||||||
|
// Main window HWND is provided when creating the helper window with `CreateWindowExW`
|
||||||
|
// Save main window HWND in GWLP_USERDATA so we can extract it later
|
||||||
|
let crate_struct = lparam.0 as *const CREATESTRUCTW;
|
||||||
|
let create_param = unsafe { (*crate_struct).lpCreateParams as *const HWND };
|
||||||
|
unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, (*create_param).0 as isize) };
|
||||||
|
return LRESULT(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invisible; no background erase.
|
||||||
|
WM_ERASEBKGND => return LRESULT(1),
|
||||||
|
|
||||||
|
// Tell windows what resize areas we are hitting, this is used to decide what cursor to show.
|
||||||
|
WM_NCHITTEST => {
|
||||||
|
let ht = unsafe { calculate_hit(hwnd, lparam) };
|
||||||
|
return LRESULT(ht as isize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This starts the system's resize loop for the main window if a resize area is hit.
|
||||||
|
// Helper window button down translates to SC_SIZE | WMSZ_* on the main window.
|
||||||
|
WM_NCLBUTTONDOWN | WM_NCRBUTTONDOWN | WM_NCMBUTTONDOWN => {
|
||||||
|
// Extract the main window's HWND from GWLP_USERDATA that we saved earlier.
|
||||||
|
let main_ptr = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *mut std::ffi::c_void;
|
||||||
|
let main = HWND(main_ptr);
|
||||||
|
if unsafe { IsWindow(main).as_bool() } {
|
||||||
|
let Some(wmsz) = (unsafe { calculate_resize_direction(hwnd, lparam) }) else {
|
||||||
|
return LRESULT(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure that the main window can receive WM_SYSCOMMAND.
|
||||||
|
let _ = unsafe { SetForegroundWindow(main) };
|
||||||
|
|
||||||
|
// Start sizing on the main window in the calculated direction. (SC_SIZE + WMSZ_*)
|
||||||
|
let _ = unsafe { PostMessageW(main, WM_SYSCOMMAND, WPARAM((SC_SIZE + wmsz) as usize), lparam) };
|
||||||
|
}
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never activate the helper window, allows all inputs that don't hit the resize areas to pass through.
|
||||||
|
WM_MOUSEACTIVATE => return LRESULT(MA_NOACTIVATE as isize),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position the helper window to match the main window's location and size (plus the resize band size).
|
||||||
|
unsafe fn position_helper(main: HWND, helper: HWND) {
|
||||||
|
let mut r = RECT::default();
|
||||||
|
let _ = unsafe { GetWindowRect(main, &mut r) };
|
||||||
|
|
||||||
|
const RESIZE_BAND_SIZE: i32 = 8;
|
||||||
|
let x = r.left - RESIZE_BAND_SIZE;
|
||||||
|
let y = r.top - RESIZE_BAND_SIZE;
|
||||||
|
let w = (r.right - r.left) + RESIZE_BAND_SIZE * 2;
|
||||||
|
let h = (r.bottom - r.top) + RESIZE_BAND_SIZE * 2;
|
||||||
|
|
||||||
|
let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE) };
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 {
|
||||||
|
let x = (lparam.0 & 0xFFFF) as i16 as u32;
|
||||||
|
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as u32;
|
||||||
|
|
||||||
|
let mut r = RECT::default();
|
||||||
|
let _ = unsafe { GetWindowRect(helper, &mut r) };
|
||||||
|
|
||||||
|
const RESIZE_BAND_THICKNESS: i32 = 8;
|
||||||
|
let on_top = y < (r.top + RESIZE_BAND_THICKNESS) as u32;
|
||||||
|
let on_right = x >= (r.right - RESIZE_BAND_THICKNESS) as u32;
|
||||||
|
let on_bottom = y >= (r.bottom - RESIZE_BAND_THICKNESS) as u32;
|
||||||
|
let on_left = x < (r.left + RESIZE_BAND_THICKNESS) as u32;
|
||||||
|
|
||||||
|
match (on_top, on_right, on_bottom, on_left) {
|
||||||
|
(true, _, _, true) => HTTOPLEFT,
|
||||||
|
(true, true, _, _) => HTTOPRIGHT,
|
||||||
|
(_, true, true, _) => HTBOTTOMRIGHT,
|
||||||
|
(_, _, true, true) => HTBOTTOMLEFT,
|
||||||
|
(true, _, _, _) => HTTOP,
|
||||||
|
(_, true, _, _) => HTRIGHT,
|
||||||
|
(_, _, true, _) => HTBOTTOM,
|
||||||
|
(_, _, _, true) => HTLEFT,
|
||||||
|
_ => HTTRANSPARENT as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn calculate_resize_direction(helper: HWND, lparam: LPARAM) -> Option<u32> {
|
||||||
|
match unsafe { calculate_hit(helper, lparam) } {
|
||||||
|
HTLEFT => Some(WMSZ_LEFT),
|
||||||
|
HTRIGHT => Some(WMSZ_RIGHT),
|
||||||
|
HTTOP => Some(WMSZ_TOP),
|
||||||
|
HTBOTTOM => Some(WMSZ_BOTTOM),
|
||||||
|
HTTOPLEFT => Some(WMSZ_TOPLEFT),
|
||||||
|
HTTOPRIGHT => Some(WMSZ_TOPRIGHT),
|
||||||
|
HTBOTTOMLEFT => Some(WMSZ_BOTTOMLEFT),
|
||||||
|
HTBOTTOMRIGHT => Some(WMSZ_BOTTOMRIGHT),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue