Windows: CursorState improvements (#526)
* Windows: CursorState improvements Fixes #523 Prior to changing the cursor state, we now check the current grab state, since it can be invalidated by alt-tabbing and other things. `CursorState::Hide` is also implemented now. The cursor name is now wrapped in a `Cursor` struct to allow multithreaded access. `Window::set_cursor_state` has been reworked to use `execute_in_thread`. Two unneeded `transmute` calls were also removed. The `WM_SETCURSOR` handler is much more readable now. `MonitorId::get_adapter_name` has been removed, since it's dead code and appears to be a relic from 4 years ago. * Windows: CursorState::Grab no longer hides cursor `MouseCursor::NoneCursor` has been implemented to allow for equivalent behavior to the older implementation. Windows and X11 now have consistent cursor grabbing behavior. macOS still needs to be updated. * Windows: Grabbing auto-hides again (for now) This API needs more work, so let's stick to a bug fix and some refactoring. However, it now hides using a different technique than it did originally, which applies instantly instead of after mouse movement.
This commit is contained in:
parent
fddfb2e2d6
commit
f6d26df64d
6 changed files with 125 additions and 83 deletions
|
|
@ -8,9 +8,9 @@ use std::os::windows::ffi::OsStrExt;
|
|||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, UINT};
|
||||
use winapi::shared::windef::{HDC, HWND, POINT, RECT};
|
||||
use winapi::um::{combaseapi, dwmapi, libloaderapi, processthreadsapi, winuser};
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE, UINT};
|
||||
use winapi::shared::windef::{HDC, HWND, LPPOINT, POINT, RECT};
|
||||
use winapi::um::{combaseapi, dwmapi, libloaderapi, winuser};
|
||||
use winapi::um::objbase::{COINIT_MULTITHREADED};
|
||||
use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
|
||||
use winapi::um::winnt::{HRESULT, LONG, LPCWSTR};
|
||||
|
|
@ -22,10 +22,11 @@ use MonitorId as RootMonitorId;
|
|||
use MouseCursor;
|
||||
use WindowAttributes;
|
||||
|
||||
use platform::platform::{EventsLoop, PlatformSpecificWindowBuilderAttributes, WindowId};
|
||||
use platform::platform::{Cursor, EventsLoop, PlatformSpecificWindowBuilderAttributes, WindowId};
|
||||
use platform::platform::events_loop::{self, DESTROY_MSG_ID};
|
||||
use platform::platform::icon::{self, IconType, WinIcon};
|
||||
use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input;
|
||||
use platform::platform::util;
|
||||
|
||||
/// The Win32 implementation of the main `Window` object.
|
||||
pub struct Window {
|
||||
|
|
@ -275,70 +276,108 @@ impl Window {
|
|||
MouseCursor::Wait => winuser::IDC_WAIT,
|
||||
MouseCursor::Progress => winuser::IDC_APPSTARTING,
|
||||
MouseCursor::Help => winuser::IDC_HELP,
|
||||
MouseCursor::NoneCursor => ptr::null(),
|
||||
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
|
||||
};
|
||||
|
||||
let mut cur = self.window_state.lock().unwrap();
|
||||
cur.cursor = cursor_id;
|
||||
cur.cursor = Cursor(cursor_id);
|
||||
}
|
||||
|
||||
// TODO: it should be possible to rework this function by using the `execute_in_thread` method
|
||||
// of the events loop.
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
let mut current_state = self.window_state.lock().unwrap();
|
||||
unsafe fn cursor_is_grabbed(&self) -> Result<bool, String> {
|
||||
let mut client_rect: RECT = mem::uninitialized();
|
||||
let mut clip_rect: RECT = mem::uninitialized();
|
||||
if winuser::GetClientRect(self.window.0, &mut client_rect) == 0 {
|
||||
return Err("`GetClientRect` failed".to_owned());
|
||||
}
|
||||
// A `POINT` is two `LONG`s (x, y), and the `RECT` field after `left` is `top`.
|
||||
if winuser::ClientToScreen(self.window.0, &mut client_rect.left as *mut _ as LPPOINT) == 0 {
|
||||
return Err("`ClientToScreen` (left, top) failed".to_owned());
|
||||
}
|
||||
if winuser::ClientToScreen(self.window.0, &mut client_rect.right as *mut _ as LPPOINT) == 0 {
|
||||
return Err("`ClientToScreen` (right, bottom) failed".to_owned());
|
||||
}
|
||||
if winuser::GetClipCursor(&mut clip_rect) == 0 {
|
||||
return Err("`GetClipCursor` failed".to_owned());
|
||||
}
|
||||
Ok(util::rect_eq(&client_rect, &clip_rect))
|
||||
}
|
||||
|
||||
let foreground_thread_id = unsafe { winuser::GetWindowThreadProcessId(self.window.0, ptr::null_mut()) };
|
||||
let current_thread_id = unsafe { processthreadsapi::GetCurrentThreadId() };
|
||||
fn change_cursor_state(
|
||||
window: &WindowWrapper,
|
||||
current_state: CursorState,
|
||||
state: CursorState,
|
||||
) -> Result<CursorState, String> {
|
||||
match (current_state, state) {
|
||||
(CursorState::Normal, CursorState::Normal)
|
||||
| (CursorState::Hide, CursorState::Hide)
|
||||
| (CursorState::Grab, CursorState::Grab) => (), // no-op
|
||||
|
||||
unsafe { winuser::AttachThreadInput(foreground_thread_id, current_thread_id, 1) };
|
||||
|
||||
let res = match (state, current_state.cursor_state) {
|
||||
(CursorState::Normal, CursorState::Normal) => Ok(()),
|
||||
(CursorState::Hide, CursorState::Hide) => Ok(()),
|
||||
(CursorState::Grab, CursorState::Grab) => Ok(()),
|
||||
|
||||
(CursorState::Hide, CursorState::Normal) => {
|
||||
current_state.cursor_state = CursorState::Hide;
|
||||
Ok(())
|
||||
(CursorState::Normal, CursorState::Hide) => unsafe {
|
||||
winuser::ShowCursor(FALSE);
|
||||
},
|
||||
|
||||
(CursorState::Normal, CursorState::Hide) => {
|
||||
current_state.cursor_state = CursorState::Normal;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(CursorState::Grab, CursorState::Normal) | (CursorState::Grab, CursorState::Hide) => {
|
||||
unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
if winuser::GetClientRect(self.window.0, &mut rect) == 0 {
|
||||
return Err(format!("GetWindowRect failed"));
|
||||
}
|
||||
winuser::ClientToScreen(self.window.0, mem::transmute(&mut rect.left));
|
||||
winuser::ClientToScreen(self.window.0, mem::transmute(&mut rect.right));
|
||||
if winuser::ClipCursor(&rect) == 0 {
|
||||
return Err(format!("ClipCursor failed"));
|
||||
}
|
||||
current_state.cursor_state = CursorState::Grab;
|
||||
Ok(())
|
||||
(CursorState::Grab, CursorState::Hide) => unsafe {
|
||||
if winuser::ClipCursor(ptr::null()) == 0 {
|
||||
return Err("`ClipCursor` failed".to_owned());
|
||||
}
|
||||
},
|
||||
|
||||
(CursorState::Normal, CursorState::Grab) => {
|
||||
unsafe {
|
||||
if winuser::ClipCursor(ptr::null()) == 0 {
|
||||
return Err(format!("ClipCursor failed"));
|
||||
}
|
||||
current_state.cursor_state = CursorState::Normal;
|
||||
Ok(())
|
||||
(CursorState::Hide, CursorState::Normal) => unsafe {
|
||||
winuser::ShowCursor(TRUE);
|
||||
},
|
||||
|
||||
(CursorState::Normal, CursorState::Grab)
|
||||
| (CursorState::Hide, CursorState::Grab) => unsafe {
|
||||
let mut rect = mem::uninitialized();
|
||||
if winuser::GetClientRect(window.0, &mut rect) == 0 {
|
||||
return Err("`GetClientRect` failed".to_owned());
|
||||
}
|
||||
if winuser::ClientToScreen(window.0, &mut rect.left as *mut _ as LPPOINT) == 0 {
|
||||
return Err("`ClientToScreen` (left, top) failed".to_owned());
|
||||
}
|
||||
if winuser::ClientToScreen(window.0, &mut rect.right as *mut _ as LPPOINT) == 0 {
|
||||
return Err("`ClientToScreen` (right, bottom) failed".to_owned());
|
||||
}
|
||||
if winuser::ClipCursor(&rect) == 0 {
|
||||
return Err("`ClipCursor` failed".to_owned());
|
||||
}
|
||||
if current_state != CursorState::Hide {
|
||||
winuser::ShowCursor(FALSE);
|
||||
}
|
||||
},
|
||||
|
||||
_ => unimplemented!(),
|
||||
(CursorState::Grab, CursorState::Normal) => unsafe {
|
||||
if winuser::ClipCursor(ptr::null()) == 0 {
|
||||
return Err("`ClipCursor` failed".to_owned());
|
||||
}
|
||||
winuser::ShowCursor(TRUE);
|
||||
},
|
||||
};
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
unsafe { winuser::AttachThreadInput(foreground_thread_id, current_thread_id, 0) };
|
||||
|
||||
res
|
||||
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
|
||||
let is_grabbed = unsafe { self.cursor_is_grabbed() }?;
|
||||
let (tx, rx) = channel();
|
||||
let window = self.window.clone();
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
self.events_loop_proxy.execute_in_thread(move |_| {
|
||||
let mut window_state_lock = window_state.lock().unwrap();
|
||||
// We should probably also check if the cursor is hidden,
|
||||
// but `GetCursorInfo` isn't in winapi-rs yet, and it doesn't seem to matter as much.
|
||||
let current_state = match window_state_lock.cursor_state {
|
||||
CursorState::Normal if is_grabbed => CursorState::Grab,
|
||||
CursorState::Grab if !is_grabbed => CursorState::Normal,
|
||||
current_state => current_state,
|
||||
};
|
||||
let result = Self::change_cursor_state(&window, current_state, state)
|
||||
.map(|_| {
|
||||
window_state_lock.cursor_state = state;
|
||||
});
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -813,7 +852,7 @@ unsafe fn init(
|
|||
|
||||
// Creating a mutex to track the current window state
|
||||
let window_state = Arc::new(Mutex::new(events_loop::WindowState {
|
||||
cursor: winuser::IDC_ARROW, // use arrow by default
|
||||
cursor: Cursor(winuser::IDC_ARROW), // use arrow by default
|
||||
cursor_state: CursorState::Normal,
|
||||
attributes: window,
|
||||
mouse_in_window: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue