grab_cursor and hide_cursor (#571)

* Windows: Use new cursor state API

* X11: Use new cursor state API

* macOS: Use new cursor state API

* Android+iOS: Stubbed new cursor state API

* Emscripten: Use new cursor state API

* Prevent multiple inc/dec of display count on Windows

* Fixed missing imports (no idea where those went)

* Remove NoneCursor

* Improved documentation

* Fix Emscripten build

* Windows: Re-grab before and after fullscreen
This commit is contained in:
Francesca Frangipane 2018-06-18 12:32:18 -04:00 committed by GitHub
parent 042f5fe4b3
commit fb7528c239
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 326 additions and 313 deletions

View file

@ -16,6 +16,7 @@
- `AvailableMonitorsIter` now implements `Debug`.
- Fixed quirk on macOS where certain keys would generate characters at twice the normal rate when held down.
- On X11, all event loops now share the same `XConnection`.
- **Breaking:** `Window::set_cursor_state` and `CursorState` enum removed in favor of the more composable `Window::grab_cursor` and `Window::hide_cursor`. As a result, grabbing the cursor no longer automatically hides it; you must call both methods to retain the old behavior on Windows and macOS. `Cursor::NoneCursor` has been removed, as it's no longer useful.
# Version 0.15.1 (2018-06-13)

View file

@ -8,7 +8,7 @@ fn main() {
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
window.set_title("A fantastic window!");
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::NoneCursor, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
let cursors = [MouseCursor::Default, MouseCursor::Crosshair, MouseCursor::Hand, MouseCursor::Arrow, MouseCursor::Move, MouseCursor::Text, MouseCursor::Wait, MouseCursor::Help, MouseCursor::Progress, MouseCursor::NotAllowed, MouseCursor::ContextMenu, MouseCursor::Cell, MouseCursor::VerticalText, MouseCursor::Alias, MouseCursor::Copy, MouseCursor::NoDrop, MouseCursor::Grab, MouseCursor::Grabbing, MouseCursor::AllScroll, MouseCursor::ZoomIn, MouseCursor::ZoomOut, MouseCursor::EResize, MouseCursor::NResize, MouseCursor::NeResize, MouseCursor::NwResize, MouseCursor::SResize, MouseCursor::SeResize, MouseCursor::SwResize, MouseCursor::WResize, MouseCursor::EwResize, MouseCursor::NsResize, MouseCursor::NeswResize, MouseCursor::NwseResize, MouseCursor::ColResize, MouseCursor::RowResize];
let mut cursor_idx = 0;
events_loop.run_forever(|event| {

38
examples/cursor_grab.rs Normal file
View file

@ -0,0 +1,38 @@
extern crate winit;
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new()
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
.build(&events_loop)
.unwrap();
events_loop.run_forever(|event| {
if let winit::Event::WindowEvent { event, .. } = event {
use winit::WindowEvent::*;
match event {
CloseRequested => return winit::ControlFlow::Break,
KeyboardInput {
input: winit::KeyboardInput {
state: winit::ElementState::Released,
virtual_keycode: Some(key),
modifiers,
..
},
..
} => {
use winit::VirtualKeyCode::*;
match key {
Escape => return winit::ControlFlow::Break,
G => window.grab_cursor(!modifiers.shift).unwrap(),
H => window.hide_cursor(!modifiers.shift),
_ => (),
}
}
_ => (),
}
}
winit::ControlFlow::Continue
});
}

View file

@ -1,45 +0,0 @@
extern crate winit;
use winit::{ControlFlow, WindowEvent, ElementState, KeyboardInput};
fn main() {
let mut events_loop = winit::EventsLoop::new();
let window = winit::WindowBuilder::new().build(&events_loop).unwrap();
window.set_title("winit - Cursor grabbing test");
let mut grabbed = false;
events_loop.run_forever(|event| {
println!("{:?}", event);
match event {
winit::Event::WindowEvent { event, .. } => {
match event {
WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
if grabbed {
grabbed = false;
window.set_cursor_state(winit::CursorState::Normal)
.ok().expect("could not ungrab mouse cursor");
} else {
grabbed = true;
window.set_cursor_state(winit::CursorState::Grab)
.ok().expect("could not grab mouse cursor");
}
},
WindowEvent::CloseRequested => return ControlFlow::Break,
a @ WindowEvent::CursorMoved { .. } => {
println!("{:?}", a);
},
_ => (),
}
}
_ => {}
}
ControlFlow::Continue
});
}

View file

@ -355,7 +355,6 @@ pub enum MouseCursor {
/// Cursor showing that something cannot be done.
NotAllowed,
ContextMenu,
NoneCursor,
Cell,
VerticalText,
Alias,
@ -391,29 +390,6 @@ impl Default for MouseCursor {
}
}
/// Describes how winit handles the cursor.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum CursorState {
/// Normal cursor behavior.
Normal,
/// The cursor will be invisible when over the window.
Hide,
/// Grabs the mouse cursor. The cursor's motion will be confined to this
/// window and the window has exclusive access to further events regarding
/// the cursor.
///
/// This is useful for first-person cameras for example.
Grab,
}
impl Default for CursorState {
fn default() -> Self {
CursorState::Normal
}
}
/// Attributes to use when creating a window.
#[derive(Debug, Clone)]
pub struct WindowAttributes {

View file

@ -12,7 +12,6 @@ use std::sync::mpsc::{Receiver, channel};
use {
CreationError,
CursorState,
Event,
LogicalPosition,
LogicalSize,
@ -337,9 +336,13 @@ impl Window {
}
#[inline]
pub fn set_cursor_state(&self, _state: CursorState) -> Result<(), String> {
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on Android.".to_owned())
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
Ok(())
}
#[inline]

View file

@ -150,7 +150,8 @@ impl EventsLoop {
pub struct WindowId(usize);
pub struct Window2 {
cursor_state: Mutex<::CursorState>,
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
is_fullscreen: bool,
events: Box<Mutex<VecDeque<::Event>>>,
}
@ -374,7 +375,8 @@ impl Window {
}
let w = Window2 {
cursor_state: Default::default(),
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
events: Default::default(),
is_fullscreen: attribs.fullscreen.is_some(),
};
@ -498,48 +500,57 @@ impl Window {
}
#[inline]
pub fn show(&self) {}
#[inline]
pub fn hide(&self) {}
pub fn show(&self) {
// N/A
}
#[inline]
pub fn set_cursor(&self, _cursor: ::MouseCursor) {}
pub fn hide(&self) {
// N/A
}
#[inline]
pub fn set_cursor_state(&self, state: ::CursorState) -> Result<(), String> {
pub fn set_cursor(&self, _cursor: ::MouseCursor) {
// N/A
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
use ::CursorState::*;
let mut old_state = self.window.cursor_state.lock().unwrap();
if state == *old_state {
return Ok(());
if grab {
em_try(ffi::emscripten_set_pointerlockchange_callback(
ptr::null(),
0 as *mut c_void,
ffi::EM_FALSE,
Some(pointerlockchange_callback),
))?;
em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?;
} else {
em_try(ffi::emscripten_set_pointerlockchange_callback(
ptr::null(),
0 as *mut c_void,
ffi::EM_FALSE,
None,
))?;
em_try(ffi::emscripten_exit_pointerlock())?;
}
// Set or unset grab callback
match state {
Hide | Normal => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, None))?,
Grab => em_try(ffi::emscripten_set_pointerlockchange_callback(ptr::null(), 0 as *mut c_void, ffi::EM_FALSE, Some(pointerlockchange_callback)))?,
}
// Go back to normal cursor state
match *old_state {
Hide => show_mouse(),
Grab => em_try(ffi::emscripten_exit_pointerlock())?,
Normal => (),
}
// Set cursor from normal cursor state
match state {
Hide => ffi::emscripten_hide_mouse(),
Grab => em_try(ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE))?,
Normal => (),
}
// Update
*old_state = state;
Ok(())
}
*grabbed_lock = grab;
Ok(())
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.window.cursor_hidden.lock().unwrap();
if hide == *hidden_lock { return; }
if hide {
unsafe { ffi::emscripten_hide_mouse() };
} else {
show_mouse();
}
*hidden_lock = hide;
}
#[inline]
@ -610,7 +621,8 @@ impl Drop for Window {
unsafe {
// Return back to normal cursor state
let _ = self.set_cursor_state(::CursorState::Normal);
self.hide_cursor(false);
self.grab_cursor(false);
// Exit fullscreen if on
if self.window.is_fullscreen {

View file

@ -69,7 +69,6 @@ use objc::runtime::{BOOL, Class, Object, Sel, YES};
use {
CreationError,
CursorState,
Event,
LogicalPosition,
LogicalSize,
@ -394,9 +393,13 @@ impl Window {
}
#[inline]
pub fn set_cursor_state(&self, _cursor_state: CursorState) -> Result<(), String> {
pub fn grab_cursor(&self, _grab: bool) -> Result<(), String> {
Err("Cursor grabbing is not possible on iOS.".to_owned())
}
#[inline]
pub fn hide_cursor(&self, _hide: bool) {
// N/A
Ok(())
}
#[inline]

View file

@ -11,17 +11,13 @@ use sctk::reexports::client::ConnectError;
use {
CreationError,
CursorState,
EventsLoopClosed,
Icon,
LogicalPosition,
LogicalSize,
MouseCursor,
PhysicalPosition,
PhysicalSize,
ControlFlow,
WindowAttributes,
};
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
use window::MonitorId as RootMonitorId;
use self::x11::{XConnection, XError};
use self::x11::ffi::XVisualInfo;
@ -252,10 +248,18 @@ impl Window {
}
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
match self {
&Window::X(ref w) => w.set_cursor_state(state),
&Window::Wayland(ref w) => w.set_cursor_state(state)
&Window::X(ref window) => window.grab_cursor(grab),
&Window::Wayland(ref _window) => Err("Cursor grabbing is not yet possible on Wayland.".to_owned()),
}
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
match self {
&Window::X(ref window) => window.hide_cursor(hide),
&Window::Wayland(ref _window) => unimplemented!(),
}
}

View file

@ -1,7 +1,8 @@
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, Weak};
use {CreationError, CursorState, MouseCursor, WindowAttributes, LogicalPosition, LogicalSize};
use {CreationError, MouseCursor, WindowAttributes};
use dpi::{LogicalPosition, LogicalSize};
use platform::MonitorId as PlatformMonitorId;
use window::MonitorId as RootMonitorId;
@ -239,17 +240,6 @@ impl Window {
// TODO
}
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
use CursorState::{Grab, Hide, Normal};
// TODO : not yet possible on wayland to grab cursor
match state {
Grab => Err("Cursor cannot be grabbed on wayland yet.".to_string()),
Hide => Err("Cursor cannot be hidden on wayland yet.".to_string()),
Normal => Ok(()),
}
}
#[inline]
pub fn hidpi_factor(&self) -> i32 {
self.monitors.lock().unwrap().compute_hidpi_factor()

View file

@ -7,8 +7,9 @@ use std::sync::Arc;
use libc;
use parking_lot::Mutex;
use {CursorState, Icon, LogicalPosition, LogicalSize, MouseCursor, WindowAttributes};
use {Icon, MouseCursor, WindowAttributes};
use CreationError::{self, OsError};
use dpi::{LogicalPosition, LogicalSize};
use platform::MonitorId as PlatformMonitorId;
use platform::PlatformSpecificWindowBuilderAttributes;
use platform::x11::MonitorId as X11MonitorId;
@ -61,7 +62,8 @@ pub struct UnownedWindow {
root: ffi::Window, // never changes
screen_id: i32, // never changes
cursor: Mutex<MouseCursor>,
cursor_state: Mutex<CursorState>,
cursor_grabbed: Mutex<bool>,
cursor_hidden: Mutex<bool>,
ime_sender: Mutex<ImeSender>,
pub multitouch: bool, // never changes
pub shared_state: Mutex<SharedState>,
@ -160,7 +162,8 @@ impl UnownedWindow {
root,
screen_id,
cursor: Default::default(),
cursor_state: Default::default(),
cursor_grabbed: Default::default(),
cursor_hidden: Default::default(),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
multitouch: window_attrs.multitouch,
shared_state: SharedState::new(),
@ -976,9 +979,6 @@ impl UnownedWindow {
MouseCursor::ZoomIn => load(b"zoom-in\0"),
MouseCursor::ZoomOut => load(b"zoom-out\0"),
MouseCursor::NoneCursor => self.create_empty_cursor()
.expect("Failed to create empty cursor"),
}
}
@ -995,7 +995,7 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
*self.cursor.lock() = cursor;
if *self.cursor_state.lock() != CursorState::Hide {
if !*self.cursor_hidden.lock() {
self.update_cursor(self.get_cursor(cursor));
}
}
@ -1039,67 +1039,73 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
use CursorState::*;
let mut cursor_state_lock = self.cursor_state.lock();
match (state, *cursor_state_lock) {
(Normal, Normal) | (Hide, Hide) | (Grab, Grab) => return Ok(()),
_ => {},
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let mut grabbed_lock = self.cursor_grabbed.lock();
if grab == *grabbed_lock { return Ok(()); }
unsafe {
// We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
// Therefore, this is common to both codepaths.
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
let result = if grab {
let result = unsafe {
(self.xconn.xlib.XGrabPointer)(
self.xconn.display,
self.xwindow,
ffi::True,
(
ffi::ButtonPressMask
| ffi::ButtonReleaseMask
| ffi::EnterWindowMask
| ffi::LeaveWindowMask
| ffi::PointerMotionMask
| ffi::PointerMotionHintMask
| ffi::Button1MotionMask
| ffi::Button2MotionMask
| ffi::Button3MotionMask
| ffi::Button4MotionMask
| ffi::Button5MotionMask
| ffi::ButtonMotionMask
| ffi::KeymapStateMask
) as c_uint,
ffi::GrabModeAsync,
ffi::GrabModeAsync,
self.xwindow,
0,
ffi::CurrentTime,
)
};
match *cursor_state_lock {
Grab => {
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
self.xconn.flush_requests().expect("Failed to call XUngrabPointer");
}
},
Normal => {},
Hide => self.update_cursor(self.get_cursor(*self.cursor.lock())),
match result {
ffi::GrabSuccess => Ok(()),
ffi::AlreadyGrabbed => Err("Cursor could not be grabbed: already grabbed by another client"),
ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"),
ffi::GrabNotViewable => Err("Cursor could not be grabbed: grab location not viewable"),
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
_ => unreachable!(),
}.map_err(|err| err.to_owned())
} else {
self.xconn.flush_requests()
.map_err(|err| format!("Failed to call `XUngrabPointer`: {:?}", err))
};
if result.is_ok() {
*grabbed_lock = grab;
}
result
}
match state {
Normal => {
*cursor_state_lock = state;
Ok(())
},
Hide => {
*cursor_state_lock = state;
self.update_cursor(
self.create_empty_cursor().expect("Failed to create empty cursor")
);
Ok(())
},
Grab => {
unsafe {
// Ungrab before grabbing to prevent passive grabs
// from causing AlreadyGrabbed
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
match (self.xconn.xlib.XGrabPointer)(
self.xconn.display, self.xwindow, ffi::True,
(ffi::ButtonPressMask | ffi::ButtonReleaseMask | ffi::EnterWindowMask |
ffi::LeaveWindowMask | ffi::PointerMotionMask | ffi::PointerMotionHintMask |
ffi::Button1MotionMask | ffi::Button2MotionMask | ffi::Button3MotionMask |
ffi::Button4MotionMask | ffi::Button5MotionMask | ffi::ButtonMotionMask |
ffi::KeymapStateMask) as c_uint,
ffi::GrabModeAsync, ffi::GrabModeAsync,
self.xwindow, 0, ffi::CurrentTime
) {
ffi::GrabSuccess => {
*cursor_state_lock = state;
Ok(())
},
ffi::AlreadyGrabbed | ffi::GrabInvalidTime |
ffi::GrabNotViewable | ffi::GrabFrozen
=> Err("cursor could not be grabbed".to_string()),
_ => unreachable!(),
}
}
},
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let mut hidden_lock = self.cursor_hidden.lock();
if hide == *hidden_lock {return; }
let cursor = if hide {
self.create_empty_cursor().expect("Failed to create empty cursor")
} else {
self.get_cursor(*self.cursor.lock())
};
*hidden_lock = hide;
drop(hidden_lock);
self.update_cursor(cursor);
}
#[inline]

View file

@ -27,7 +27,6 @@ use objc::declare::ClassDecl;
use {
CreationError,
CursorState,
Event,
LogicalPosition,
LogicalSize,
@ -527,6 +526,7 @@ pub struct Window2 {
pub window: IdRef,
pub delegate: WindowDelegate,
pub input_context: IdRef,
cursor_hidden: Cell<bool>,
}
unsafe impl Send for Window2 {}
@ -674,6 +674,7 @@ impl Window2 {
window: window,
delegate: WindowDelegate::new(delegate_state),
input_context,
cursor_hidden: Default::default(),
};
// Set fullscreen mode after we setup everything
@ -980,7 +981,7 @@ impl Window2 {
MouseCursor::SeResize | MouseCursor::SwResize |
MouseCursor::NwseResize | MouseCursor::NeswResize |
MouseCursor::Cell | MouseCursor::NoneCursor |
MouseCursor::Cell |
MouseCursor::Wait | MouseCursor::Progress | MouseCursor::Help |
MouseCursor::Move | MouseCursor::AllScroll | MouseCursor::ZoomIn |
MouseCursor::ZoomOut => "arrowCursor",
@ -994,25 +995,25 @@ impl Window2 {
}
}
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
let cls = Class::get("NSCursor").unwrap();
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
CGDisplay::associate_mouse_and_mouse_cursor_position(!grab)
.map_err(|status| format!("Failed to grab cursor: `CGError` {:?}", status))
}
// TODO: Check for errors.
match state {
CursorState::Normal => {
let _: () = unsafe { msg_send![cls, unhide] };
let _ = CGDisplay::associate_mouse_and_mouse_cursor_position(true);
Ok(())
},
CursorState::Hide => {
let _: () = unsafe { msg_send![cls, hide] };
Ok(())
},
CursorState::Grab => {
let _: () = unsafe { msg_send![cls, hide] };
let _ = CGDisplay::associate_mouse_and_mouse_cursor_position(false);
Ok(())
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let cursor_class = Class::get("NSCursor").unwrap();
// macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once.
// (otherwise, `hide_cursor(false)` would need to be called n times!)
if hide != self.cursor_hidden.get() {
if hide {
let _: () = unsafe { msg_send![cursor_class, hide] };
} else {
let _: () = unsafe { msg_send![cursor_class, unhide] };
}
self.cursor_hidden.replace(hide);
}
}

View file

@ -40,7 +40,6 @@ use winapi::um::winnt::{LONG, LPCSTR, SHORT};
use {
ControlFlow,
CursorState,
Event,
EventsLoopClosed,
KeyboardInput,
@ -83,8 +82,8 @@ pub struct SavedWindowInfo {
pub struct WindowState {
/// Cursor to set at the next `WM_SETCURSOR` event received.
pub cursor: Cursor,
/// Cursor state to set at the next `WM_SETCURSOR` event received.
pub cursor_state: CursorState,
pub cursor_grabbed: bool,
pub cursor_hidden: bool,
/// Used by `WM_GETMINMAXINFO`.
pub max_size: Option<PhysicalSize>,
pub min_size: Option<PhysicalSize>,

View file

@ -17,7 +17,6 @@ use winapi::um::winnt::{LONG, LPCWSTR};
use {
CreationError,
CursorState,
Icon,
LogicalPosition,
LogicalSize,
@ -26,9 +25,9 @@ use {
PhysicalSize,
WindowAttributes,
};
use platform::platform::{Cursor, EventsLoop, PlatformSpecificWindowBuilderAttributes, WindowId};
use platform::platform::{Cursor, PlatformSpecificWindowBuilderAttributes, WindowId};
use platform::platform::dpi::{BASE_DPI, dpi_to_scale_factor, get_window_dpi, get_window_scale_factor};
use platform::platform::events_loop::{self, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID};
use platform::platform::events_loop::{self, DESTROY_MSG_ID, EventsLoop, INITIAL_DPI_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;
@ -336,7 +335,6 @@ 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.
};
@ -363,83 +361,78 @@ impl Window {
Ok(util::rect_eq(&client_rect, &clip_rect))
}
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
(CursorState::Normal, CursorState::Hide) => unsafe {
winuser::ShowCursor(FALSE);
},
(CursorState::Grab, CursorState::Hide) => unsafe {
if winuser::ClipCursor(ptr::null()) == 0 {
return Err("`ClipCursor` failed".to_owned());
}
},
(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);
}
},
(CursorState::Grab, CursorState::Normal) => unsafe {
if winuser::ClipCursor(ptr::null()) == 0 {
return Err("`ClipCursor` failed".to_owned());
}
winuser::ShowCursor(TRUE);
},
};
Ok(state)
pub(crate) unsafe fn grab_cursor_inner(window: &WindowWrapper, grab: bool) -> Result<(), String> {
if grab {
let mut rect = mem::uninitialized();
if winuser::GetClientRect(window.0, &mut 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(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());
}
} else {
if winuser::ClipCursor(ptr::null()) == 0 {
return Err("`ClipCursor` failed".to_owned());
}
}
Ok(())
}
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();
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let currently_grabbed = unsafe { self.cursor_is_grabbed() }?;
let window_state = Arc::clone(&self.window_state);
{
let window_state_lock = window_state.lock().unwrap();
if currently_grabbed == grab
&& grab == window_state_lock.cursor_grabbed {
return Ok(());
}
}
let window = self.window.clone();
let (tx, rx) = channel();
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 result = unsafe { Self::grab_cursor_inner(&window, grab) };
if result.is_ok() {
window_state.lock().unwrap().cursor_grabbed = grab;
}
let _ = tx.send(result);
});
rx.recv().unwrap()
}
pub(crate) unsafe fn hide_cursor_inner(hide: bool) {
if hide {
winuser::ShowCursor(FALSE);
} else {
winuser::ShowCursor(TRUE);
}
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let window_state = Arc::clone(&self.window_state);
{
let window_state_lock = window_state.lock().unwrap();
// We don't want to increment/decrement the display count more than once!
if hide == window_state_lock.cursor_hidden { return; }
}
let (tx, rx) = channel();
self.events_loop_proxy.execute_in_thread(move |_| {
unsafe { Self::hide_cursor_inner(hide) };
window_state.lock().unwrap().cursor_hidden = hide;
let _ = tx.send(());
});
rx.recv().unwrap()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
get_window_scale_factor(self.window.0, self.window.1)
@ -518,26 +511,28 @@ impl Window {
}
unsafe fn restore_saved_window(&self) {
let mut window_state = self.window_state.lock().unwrap();
let (rect, mut style, ex_style) = {
let mut window_state_lock = self.window_state.lock().unwrap();
// 'saved_window_info' can be None if the window has never been
// in fullscreen mode before this method gets called.
if window_state.saved_window_info.is_none() {
return;
}
// 'saved_window_info' can be None if the window has never been
// in fullscreen mode before this method gets called.
if window_state_lock.saved_window_info.is_none() {
return;
}
// Reset original window style and size. The multiple window size/moves
// here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be
// repainted. Better-looking methods welcome.
{
let saved_window_info = window_state.saved_window_info.as_mut().unwrap();
let saved_window_info = window_state_lock.saved_window_info.as_mut().unwrap();
// Reset original window style and size. The multiple window size/moves
// here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be
// repainted. Better-looking methods welcome.
saved_window_info.is_fullscreen = false;
}
let saved_window_info = window_state.saved_window_info.as_ref().unwrap();
let rect = saved_window_info.rect.clone();
let rect = saved_window_info.rect.clone();
let (style, ex_style) = (saved_window_info.style, saved_window_info.ex_style);
(rect, style, ex_style)
};
let window = self.window.clone();
let (mut style, ex_style) = (saved_window_info.style, saved_window_info.ex_style);
let window_state = Arc::clone(&self.window_state);
let maximized = self.maximized.get();
let resizable = self.resizable.get();
@ -545,6 +540,8 @@ impl Window {
// We're restoring the window to its size and position from before being fullscreened.
// `ShowWindow` resizes the window, so it must be called from the main thread.
self.events_loop_proxy.execute_in_thread(move |_| {
let _ = Self::grab_cursor_inner(&window, false);
if resizable {
style |= winuser::WS_SIZEBOX as LONG;
} else {
@ -577,6 +574,9 @@ impl Window {
);
mark_fullscreen(window.0, false);
let window_state_lock = window_state.lock().unwrap();
let _ = Self::grab_cursor_inner(&window, window_state_lock.cursor_grabbed);
});
}
@ -588,10 +588,13 @@ impl Window {
let (x, y): (i32, i32) = inner.get_position().into();
let (width, height): (u32, u32) = inner.get_dimensions().into();
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (style, ex_style) = self.set_fullscreen_style();
self.events_loop_proxy.execute_in_thread(move |_| {
let _ = Self::grab_cursor_inner(&window, false);
winuser::SetWindowLongW(
window.0,
winuser::GWL_STYLE,
@ -622,6 +625,9 @@ impl Window {
);
mark_fullscreen(window.0, true);
let window_state_lock = window_state.lock().unwrap();
let _ = Self::grab_cursor_inner(&window, window_state_lock.cursor_grabbed);
});
}
&None => {
@ -980,7 +986,8 @@ unsafe fn init(
.map(|logical_size| PhysicalSize::from_logical(logical_size, dpi_factor));
let mut window_state = events_loop::WindowState {
cursor: Cursor(winuser::IDC_ARROW), // use arrow by default
cursor_state: CursorState::Normal,
cursor_grabbed: false,
cursor_hidden: false,
max_size,
min_size,
mouse_in_window: false,

View file

@ -2,7 +2,6 @@ use std::collections::vec_deque::IntoIter as VecDequeIter;
use {
CreationError,
CursorState,
EventsLoop,
Icon,
LogicalPosition,
@ -327,12 +326,31 @@ impl Window {
self.window.set_cursor_position(position)
}
/// Sets how winit handles the cursor. See the documentation of `CursorState` for details.
/// Grabs the cursor, preventing it from leaving the window.
///
/// Has no effect on Android.
/// ## Platform-specific
///
/// On macOS, this presently merely locks the cursor in a fixed location, which looks visually awkward.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
self.window.set_cursor_state(state)
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
self.window.grab_cursor(grab)
}
/// Hides the cursor, making it invisible but still usable.
///
/// ## Platform-specific
///
/// On Windows and X11, the cursor is only hidden within the confines of the window.
///
/// On macOS, the cursor is hidden as long as the window has input focus, even if the cursor is outside of the
/// window.
///
/// This has no effect on Android or iOS.
#[inline]
pub fn hide_cursor(&self, hide: bool) {
self.window.hide_cursor(hide)
}
/// Sets the window to maximized or back