1
0
Fork 0

Implement window resizing for Windows

This commit is contained in:
Robbert van der Helm 2022-11-30 18:57:56 +01:00
parent b8a5867436
commit 80b30ce617
2 changed files with 334 additions and 230 deletions

View file

@ -17,6 +17,7 @@ use winapi::um::winuser::{
}; };
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::ffi::{c_void, OsStr}; use std::ffi::{c_void, OsStr};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::windows::ffi::OsStrExt; use std::os::windows::ffi::OsStrExt;
@ -29,8 +30,8 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle};
const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1;
use crate::{ use crate::{
Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
WindowInfo, WindowOpenOptions, WindowScalePolicy, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
}; };
use super::keyboard::KeyboardState; use super::keyboard::KeyboardState;
@ -130,6 +131,38 @@ unsafe extern "system" fn wnd_proc(
if !window_state_ptr.is_null() { if !window_state_ptr.is_null() {
let window_state = &*window_state_ptr; let window_state = &*window_state_ptr;
let result = wnd_proc_inner(hwnd, msg, wparam, lparam, window_state);
// If any of the above event handlers caused tasks to be pushed to the deferred tasks list,
// then we'll try to handle them now
loop {
// NOTE: This is written like this instead of using a `while let` loop to avoid exending
// the borrow of `window_state.deferred_tasks` into the call of
// `window_state.handle_deferred_task()` since that may also generate additional
// messages.
let task = match window_state.deferred_tasks.borrow_mut().pop_front() {
Some(task) => task,
None => break,
};
window_state.handle_deferred_task(task);
}
// The actual custom window proc has been moved to another function so we can always handle
// the deferred tasks regardless of whether the custom window proc returns early or not
if let Some(result) = result {
return result;
}
}
DefWindowProcW(hwnd, msg, wparam, lparam)
}
/// Our custom `wnd_proc` handler. If the result contains a value, then this is returned after
/// handling any deferred tasks. otherwise the default window procedure is invoked.
unsafe fn wnd_proc_inner(
hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM, window_state: &WindowState,
) -> Option<LRESULT> {
match msg { match msg {
WM_MOUSEMOVE => { WM_MOUSEMOVE => {
let mut window = window_state.create_window(hwnd); let mut window = window_state.create_window(hwnd);
@ -150,7 +183,7 @@ unsafe extern "system" fn wnd_proc(
window_state.handler.borrow_mut().on_event(&mut window, event); window_state.handler.borrow_mut().on_event(&mut window, event);
return 0; return Some(0);
} }
WM_MOUSEWHEEL | WM_MOUSEHWHEEL => { WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
let mut window = window_state.create_window(hwnd); let mut window = window_state.create_window(hwnd);
@ -174,7 +207,7 @@ unsafe extern "system" fn wnd_proc(
window_state.handler.borrow_mut().on_event(&mut window, event); window_state.handler.borrow_mut().on_event(&mut window, event);
return 0; return Some(0);
} }
WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDOWN WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDOWN
| WM_RBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP => { | WM_RBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP => {
@ -241,7 +274,7 @@ unsafe extern "system" fn wnd_proc(
if wparam == WIN_FRAME_TIMER { if wparam == WIN_FRAME_TIMER {
window_state.handler.borrow_mut().on_frame(&mut window); window_state.handler.borrow_mut().on_frame(&mut window);
} }
return 0; return Some(0);
} }
WM_CLOSE => { WM_CLOSE => {
// Make sure to release the borrow before the DefWindowProc call // Make sure to release the borrow before the DefWindowProc call
@ -256,25 +289,23 @@ unsafe extern "system" fn wnd_proc(
} }
// DestroyWindow(hwnd); // DestroyWindow(hwnd);
// return 0; // return Some(0);
return DefWindowProcW(hwnd, msg, wparam, lparam); return Some(DefWindowProcW(hwnd, msg, wparam, lparam));
} }
WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP
| WM_INPUTLANGCHANGE => { | WM_INPUTLANGCHANGE => {
let mut window = window_state.create_window(hwnd); let mut window = window_state.create_window(hwnd);
let mut window = crate::Window::new(&mut window); let mut window = crate::Window::new(&mut window);
let opt_event = window_state let opt_event =
.keyboard_state window_state.keyboard_state.borrow_mut().process_message(hwnd, msg, wparam, lparam);
.borrow_mut()
.process_message(hwnd, msg, wparam, lparam);
if let Some(event) = opt_event { if let Some(event) = opt_event {
window_state.handler.borrow_mut().on_event(&mut window, Event::Keyboard(event)); window_state.handler.borrow_mut().on_event(&mut window, Event::Keyboard(event));
} }
if msg != WM_SYSKEYDOWN { if msg != WM_SYSKEYDOWN {
return 0; return Some(0);
} }
} }
WM_SIZE => { WM_SIZE => {
@ -286,12 +317,10 @@ unsafe extern "system" fn wnd_proc(
let window_info = { let window_info = {
let mut window_info = window_state.window_info.borrow_mut(); let mut window_info = window_state.window_info.borrow_mut();
*window_info = WindowInfo::from_physical_size( *window_info =
PhySize { width, height }, WindowInfo::from_physical_size(PhySize { width, height }, window_info.scale());
window_info.scale(),
);
window_info.clone() *window_info
}; };
window_state window_state
@ -349,13 +378,12 @@ unsafe extern "system" fn wnd_proc(
_ => { _ => {
if msg == BV_WINDOW_MUST_CLOSE { if msg == BV_WINDOW_MUST_CLOSE {
DestroyWindow(hwnd); DestroyWindow(hwnd);
return 0; return Some(0);
}
} }
} }
} }
DefWindowProcW(hwnd, msg, wparam, lparam) None
} }
unsafe fn register_wnd_class() -> ATOM { unsafe fn register_wnd_class() -> ATOM {
@ -390,6 +418,7 @@ unsafe fn unregister_wnd_class(wnd_class: ATOM) {
/// `handler` from indirectly triggering other events that would also need to be handled using /// `handler` from indirectly triggering other events that would also need to be handled using
/// `handler`. /// `handler`.
struct WindowState { struct WindowState {
hwnd: HWND,
window_class: ATOM, window_class: ATOM,
window_info: RefCell<WindowInfo>, window_info: RefCell<WindowInfo>,
_parent_handle: Option<ParentHandle>, _parent_handle: Option<ParentHandle>,
@ -399,6 +428,13 @@ struct WindowState {
scale_policy: WindowScalePolicy, scale_policy: WindowScalePolicy,
dw_style: u32, dw_style: u32,
/// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably
/// borrowing the fields from `WindowState` more than once. For instance, when the window
/// handler requests a resize in response to a keyboard event, the window state will already be
/// borrowed in `wnd_proc`. So the `resize()` function below cannot also mutably borrow that
/// window state at the same time.
deferred_tasks: Rc<RefCell<VecDeque<WindowTask>>>,
#[cfg(feature = "opengl")] #[cfg(feature = "opengl")]
gl_context: Rc<Option<GlContext>>, gl_context: Rc<Option<GlContext>>,
} }
@ -406,18 +442,64 @@ struct WindowState {
impl WindowState { impl WindowState {
#[cfg(not(feature = "opengl"))] #[cfg(not(feature = "opengl"))]
fn create_window(&self, hwnd: HWND) -> Window { fn create_window(&self, hwnd: HWND) -> Window {
Window { hwnd } Window { hwnd, deferred_tasks: self.deferred_tasks.clone() }
} }
#[cfg(feature = "opengl")] #[cfg(feature = "opengl")]
fn create_window(&self, hwnd: HWND) -> Window { fn create_window(&self, hwnd: HWND) -> Window {
Window { hwnd, gl_context: self.gl_context.clone() } Window {
hwnd,
deferred_tasks: self.deferred_tasks.clone(),
gl_context: self.gl_context.clone(),
} }
} }
/// Handle a deferred task as described in [`Self::deferred_tasks
pub(self) fn handle_deferred_task(&self, task: WindowTask) {
match task {
WindowTask::Resize(size) => {
let window_info = {
let mut window_info = self.window_info.borrow_mut();
let scaling = window_info.scale();
*window_info = WindowInfo::from_logical_size(size, scaling);
*window_info
};
unsafe {
SetWindowPos(
self.hwnd,
self.hwnd,
0,
0,
window_info.physical_size().width as i32,
window_info.physical_size().height as i32,
SWP_NOZORDER | SWP_NOMOVE,
)
};
}
}
}
}
/// Tasks that must be deferred until the end of [`wnd_proc()`] to avoid reentrant `WindowState`
/// borrows. See the docstring on [`WindowState::deferred_tasks`] for more information.
#[derive(Debug, Clone)]
enum WindowTask {
/// Resize the window to the given size. The size is in logical pixels. DPI scaling is applied
/// automatically.
Resize(Size),
}
pub struct Window { pub struct Window {
/// The HWND belonging to this window. The window's actual state is stored in the `WindowState`
/// struct associated with this HWND through `unsafe { GetWindowLongPtrW(self.hwnd,
/// GWLP_USERDATA) } as *const WindowState`.
hwnd: HWND, hwnd: HWND,
/// See [`WindowState::deferred_tasks`].
deferred_tasks: Rc<RefCell<VecDeque<WindowTask>>>,
#[cfg(feature = "opengl")] #[cfg(feature = "opengl")]
gl_context: Rc<Option<GlContext>>, gl_context: Rc<Option<GlContext>>,
} }
@ -546,11 +628,20 @@ impl Window {
GlContext::create(&handle, gl_config).expect("Could not create OpenGL context") GlContext::create(&handle, gl_config).expect("Could not create OpenGL context")
})); }));
// The build closure shouldn't be enqueueing deferred tasks yet, but we'll try handling
// them just in case to avoid losing them
let deferred_tasks: Rc<RefCell<VecDeque<WindowTask>>> =
Rc::new(RefCell::new(VecDeque::new()));
#[cfg(not(feature = "opengl"))] #[cfg(not(feature = "opengl"))]
let handler = build(&mut crate::Window::new(&mut Window { hwnd })); let handler = build(&mut crate::Window::new(&mut Window {
hwnd,
deferred_tasks: deferred_tasks.clone(),
}));
#[cfg(feature = "opengl")] #[cfg(feature = "opengl")]
let handler = build(&mut crate::Window::new(&mut Window { let handler = build(&mut crate::Window::new(&mut Window {
hwnd, hwnd,
deferred_tasks: deferred_tasks.clone(),
gl_context: gl_context.clone(), gl_context: gl_context.clone(),
})); }));
let handler = RefCell::new(Box::new(handler)); let handler = RefCell::new(Box::new(handler));
@ -559,6 +650,7 @@ impl Window {
let parent_handle = if parented { Some(parent_handle) } else { None }; let parent_handle = if parented { Some(parent_handle) } else { None };
let window_state = Box::new(WindowState { let window_state = Box::new(WindowState {
hwnd,
window_class, window_class,
window_info: RefCell::new(window_info), window_info: RefCell::new(window_info),
_parent_handle: parent_handle, _parent_handle: parent_handle,
@ -568,10 +660,18 @@ impl Window {
scale_policy: options.scale, scale_policy: options.scale,
dw_style: flags, dw_style: flags,
deferred_tasks: Rc::new(RefCell::new(VecDeque::with_capacity(4))),
#[cfg(feature = "opengl")] #[cfg(feature = "opengl")]
gl_context, gl_context,
}); });
// If the plugin did queue up tasks as part of the build handler, then we'll process
// them now
for task in deferred_tasks.borrow_mut().drain(..) {
window_state.handle_deferred_task(task);
}
// Only works on Windows 10 unfortunately. // Only works on Windows 10 unfortunately.
SetProcessDpiAwarenessContext( SetProcessDpiAwarenessContext(
winapi::shared::windef::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, winapi::shared::windef::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
@ -633,6 +733,13 @@ impl Window {
} }
} }
pub fn resize(&mut self, size: Size) {
// To avoid reentrant event handler calls we'll defer the actual resizing until after the
// event has been handled
let task = WindowTask::Resize(size);
self.deferred_tasks.borrow_mut().push_back(task);
}
#[cfg(feature = "opengl")] #[cfg(feature = "opengl")]
pub fn gl_context(&self) -> Option<&GlContext> { pub fn gl_context(&self) -> Option<&GlContext> {
self.gl_context.as_ref().as_ref() self.gl_context.as_ref().as_ref()

View file

@ -99,12 +99,9 @@ impl<'a> Window<'a> {
self.window.close(); self.window.close();
} }
/// Resize the window to the given size. /// Resize the window to the given size. The size is always in logical pixels. DPI scaling will
/// /// automatically be accounted for.
/// # TODO #[cfg(any(target_os = "linux", target_os = "windows"))]
///
/// This is currently only supported on Linux.
#[cfg(target_os = "linux")]
pub fn resize(&mut self, size: Size) { pub fn resize(&mut self, size: Size) {
self.window.resize(size); self.window.resize(size);
} }