Add cursor hittest window functionality (#2232)

Co-authored-by: z4122 <412213484@qq.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
This commit is contained in:
Markus Siglreithmaier 2022-04-12 19:10:46 +02:00 committed by GitHub
parent 142d55ff24
commit bf366cb99d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 120 additions and 6 deletions

View file

@ -8,10 +8,11 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased # Unreleased
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`. - On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`. - On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
- On Windows, remove internally unique DC per window. - On Windows, remove internally unique DC per window.
- macOS: Remove the need to call `set_ime_position` after moving the window. - On macOS, remove the need to call `set_ime_position` after moving the window.
- Added `Window::is_visible`. - Added `Window::is_visible`.
- Added `Window::is_resizable`. - Added `Window::is_resizable`.
- Added `Window::is_decorated`. - Added `Window::is_decorated`.

View file

@ -102,6 +102,7 @@ If your PR makes notable changes to Winit's features, please update this section
- **Mouse set location**: Forcibly changing the location of the pointer. - **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window. - **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor. - **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events. - **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied. - **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture. - **Multitouch**: Multi-touch events, including cancellation of a gesture.
@ -199,6 +200,7 @@ Legend:
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**| |Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|✔️ | |Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|✔️ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | |Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | |Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |

View file

@ -774,6 +774,12 @@ impl Window {
)) ))
} }
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn raw_window_handle(&self) -> RawWindowHandle { pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = AndroidNdkHandle::empty(); let mut handle = AndroidNdkHandle::empty();
if let Some(native_window) = ndk_glue::native_window().as_ref() { if let Some(native_window) = ndk_glue::native_window().as_ref() {

View file

@ -196,6 +196,10 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_minimized(&self, _minimized: bool) { pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS") warn!("`Window::set_minimized` is ignored on iOS")
} }

View file

@ -396,6 +396,11 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.drag_window()) x11_or_wayland!(match self; Window(window) => window.drag_window())
} }
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
}
#[inline] #[inline]
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64) x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)

View file

@ -482,6 +482,13 @@ impl Window {
Ok(()) Ok(())
} }
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
self.send_request(WindowRequest::PassthroughMouseInput(!hittest));
Ok(())
}
#[inline] #[inline]
pub fn set_ime_position(&self, position: Position) { pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64; let scale_factor = self.scale_factor() as f64;

View file

@ -1,6 +1,7 @@
use std::cell::Cell; use std::cell::Cell;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
use sctk::reexports::client::protocol::wl_output::WlOutput; use sctk::reexports::client::protocol::wl_output::WlOutput;
use sctk::reexports::client::Attached; use sctk::reexports::client::Attached;
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1; use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
@ -75,6 +76,9 @@ pub enum WindowRequest {
/// `None` unsets the attention request. /// `None` unsets the attention request.
Attention(Option<UserAttentionType>), Attention(Option<UserAttentionType>),
/// Passthrough mouse input to underlying windows.
PassthroughMouseInput(bool),
/// Redraw was requested. /// Redraw was requested.
Redraw, Redraw,
@ -167,6 +171,9 @@ pub struct WindowHandle {
/// Indicator whether user attention is requested. /// Indicator whether user attention is requested.
attention_requested: Cell<bool>, attention_requested: Cell<bool>,
/// Compositor
compositor: Attached<WlCompositor>,
} }
impl WindowHandle { impl WindowHandle {
@ -177,6 +184,9 @@ impl WindowHandle {
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>, pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
) -> Self { ) -> Self {
let xdg_activation = env.get_global::<XdgActivationV1>(); let xdg_activation = env.get_global::<XdgActivationV1>();
// Unwrap is safe, since we can't create window without compositor anyway and won't be
// here.
let compositor = env.get_global::<WlCompositor>().unwrap();
Self { Self {
window, window,
@ -189,6 +199,7 @@ impl WindowHandle {
text_inputs: Vec::new(), text_inputs: Vec::new(),
xdg_activation, xdg_activation,
attention_requested: Cell::new(false), attention_requested: Cell::new(false),
compositor,
} }
} }
@ -304,6 +315,20 @@ impl WindowHandle {
} }
} }
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
if passthrough_mouse_input {
let region = self.compositor.create_region();
region.add(0, 0, 0, 0);
self.window
.surface()
.set_input_region(Some(&region.detach()));
region.destroy();
} else {
// Using `None` results in the entire window being clickable.
self.window.surface().set_input_region(None);
}
}
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
self.cursor_visible.replace(visible); self.cursor_visible.replace(visible);
let cursor_icon = match visible { let cursor_icon = match visible {
@ -425,6 +450,12 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
let window_update = window_updates.get_mut(window_id).unwrap(); let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true; window_update.refresh_frame = true;
} }
WindowRequest::PassthroughMouseInput(passthrough) => {
window_handle.passthrough_mouse_input(passthrough);
let window_update = window_updates.get_mut(window_id).unwrap();
window_update.refresh_frame = true;
}
WindowRequest::Attention(request_type) => { WindowRequest::Attention(request_type) => {
window_handle.set_user_attention(request_type); window_handle.set_user_attention(request_type);
} }

View file

@ -1358,6 +1358,11 @@ impl UnownedWindow {
self.set_cursor_position_physical(x, y) self.set_cursor_position_physical(x, y)
} }
#[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn drag_window(&self) -> Result<(), ExternalError> { pub fn drag_window(&self) -> Result<(), ExternalError> {
let pointer = self let pointer = self
.xconn .xconn

View file

@ -10,7 +10,7 @@ use cocoa::{
}; };
use dispatch::Queue; use dispatch::Queue;
use objc::rc::autoreleasepool; use objc::rc::autoreleasepool;
use objc::runtime::{BOOL, NO}; use objc::runtime::{BOOL, NO, YES};
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
@ -93,6 +93,14 @@ pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
}); });
} }
// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently.
pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) {
let ns_window = MainThreadSafe(ns_window);
Queue::main().exec_async(move || {
ns_window.setIgnoresMouseEvents_(if ignore { YES } else { NO });
});
}
// `toggleFullScreen` is thread-safe, but our additional logic to account for // `toggleFullScreen` is thread-safe, but our additional logic to account for
// window styles isn't. // window styles isn't.
pub unsafe fn toggle_full_screen_async( pub unsafe fn toggle_full_screen_async(

View file

@ -678,6 +678,15 @@ impl UnownedWindow {
Ok(()) Ok(())
} }
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
unsafe {
util::set_ignore_mouse_events(*self.ns_window, !hittest);
}
Ok(())
}
pub(crate) fn is_zoomed(&self) -> bool { pub(crate) fn is_zoomed(&self) -> bool {
// because `isZoomed` doesn't work if the window's borderless, // because `isZoomed` doesn't work if the window's borderless,
// we make it resizable temporalily. // we make it resizable temporalily.

View file

@ -239,6 +239,11 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
#[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline] #[inline]
pub fn set_minimized(&self, _minimized: bool) { pub fn set_minimized(&self, _minimized: bool) {
// Intentionally a no-op, as canvases cannot be 'minimized' // Intentionally a no-op, as canvases cannot be 'minimized'

View file

@ -361,6 +361,19 @@ impl Window {
Ok(()) Ok(())
} }
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest)
});
});
Ok(())
}
#[inline] #[inline]
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId(self.hwnd()) WindowId(self.hwnd())

View file

@ -15,10 +15,10 @@ use windows_sys::Win32::{
HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE, HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE,
SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE,
SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD,
WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LEFT, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED,
WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_EX_LEFT, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE,
WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPED, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPED,
WS_SYSMENU, WS_VISIBLE, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE,
}, },
}; };
@ -93,6 +93,8 @@ bitflags! {
const MINIMIZED = 1 << 12; const MINIMIZED = 1 << 12;
const IGNORE_CURSOR_EVENT = 1 << 14;
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
@ -228,6 +230,9 @@ impl WindowFlags {
if self.contains(WindowFlags::MAXIMIZED) { if self.contains(WindowFlags::MAXIMIZED) {
style |= WS_MAXIMIZE; style |= WS_MAXIMIZE;
} }
if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) {
style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED;
}
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU; style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
style_ex |= WS_EX_ACCEPTFILES; style_ex |= WS_EX_ACCEPTFILES;

View file

@ -949,6 +949,19 @@ impl Window {
pub fn drag_window(&self) -> Result<(), ExternalError> { pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window.drag_window() self.window.drag_window()
} }
/// Modifies whether the window catches cursor events.
///
/// If `true`, the window will catch the cursor events. If `false`, events are passed through
/// the window such that any other window behind it receives them. By default hittest is enabled.
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / X11:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
self.window.set_cursor_hittest(hittest)
}
} }
/// Monitor info functions. /// Monitor info functions.