From 4fa8761776d546ee3b1b0bb1a02a31d72eedfa80 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Mon, 13 Jun 2022 12:26:30 +0200 Subject: [PATCH] feat: `Window::set_ignore_cursor_events`, closes #184 (#421) * feat: `Window::set_ignore_cursor_events`, closes #184 Co-authored-by: Markus Siglreithmaier Co-authored-by: z4122 <412213484@qq.com> Co-authored-by: Kirill Chibisov * linux attempt failed * changefile * linux Co-authored-by: Markus Siglreithmaier Co-authored-by: z4122 <412213484@qq.com> Co-authored-by: Kirill Chibisov --- .changes/window_ignore_cursor_events.md | 5 +++++ src/platform_impl/android/mod.rs | 6 ++++++ src/platform_impl/ios/window.rs | 4 ++++ src/platform_impl/linux/event_loop.rs | 17 +++++++++++++++++ src/platform_impl/linux/window.rs | 12 ++++++++++++ src/platform_impl/macos/util/async.rs | 8 ++++++++ src/platform_impl/macos/window.rs | 9 +++++++++ src/platform_impl/windows/window.rs | 13 +++++++++++++ src/platform_impl/windows/window_state.rs | 5 +++++ src/window.rs | 13 +++++++++++++ 10 files changed, 92 insertions(+) create mode 100644 .changes/window_ignore_cursor_events.md diff --git a/.changes/window_ignore_cursor_events.md b/.changes/window_ignore_cursor_events.md new file mode 100644 index 000000000..edd0680b9 --- /dev/null +++ b/.changes/window_ignore_cursor_events.md @@ -0,0 +1,5 @@ +--- +"tao": patch +--- + +Add `Window::set_ignore_cursor_events` diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 8c2d4dfa9..f6139620e 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -664,6 +664,12 @@ impl Window { )) } + pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) + } + pub fn raw_window_handle(&self) -> RawWindowHandle { // TODO: Use main activity instead? let mut handle = AndroidNdkHandle::empty(); diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 8fd8f28ba..3822334a6 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -196,6 +196,10 @@ impl Inner { Err(ExternalError::NotSupported(NotSupportedError::new())) } + pub fn set_ignore_cursor_events(&self, _ignore: bool) -> Result<(), ExternalError> { + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + pub fn set_minimized(&self, _minimized: bool) { warn!("`Window::set_minimized` is ignored on iOS") } diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index dfa4d2807..028d9c0aa 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -11,6 +11,7 @@ use std::{ time::Instant, }; +use cairo::{RectangleInt, Region}; use gdk::{Cursor, CursorType, EventKey, EventMask, ScrollDirection, WindowEdge, WindowState}; use gio::{prelude::*, Cancellable}; use glib::{source::Priority, Continue, MainContext}; @@ -310,6 +311,22 @@ impl EventLoop { } } } + WindowRequest::CursorIgnoreEvents(ignore) => { + if ignore { + let empty_region = Region::create_rectangle(&RectangleInt { + x: 0, + y: 0, + width: 1, + height: 1, + }); + window + .window() + .unwrap() + .input_shape_combine_region(&empty_region, 0, 0); + } else { + window.input_shape_combine_region(None) + }; + } WindowRequest::WireUpEvents => { window.add_events( EventMask::POINTER_MOTION_MASK diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index 5924bb68f..6aeb42d01 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -559,6 +559,17 @@ impl Window { Ok(()) } + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + if let Err(e) = self + .window_requests_tx + .send((self.window_id, WindowRequest::CursorIgnoreEvents(ignore))) + { + log::warn!("Fail to send cursor position request: {}", e); + } + + Ok(()) + } + pub fn set_cursor_visible(&self, visible: bool) { let cursor = if visible { Some(CursorIcon::Default) @@ -664,6 +675,7 @@ pub enum WindowRequest { SetSkipTaskbar(bool), CursorIcon(Option), CursorPosition((i32, i32)), + CursorIgnoreEvents(bool), WireUpEvents, Redraw, Menu((Option, Option)), diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index fe43c9f5f..dc9884a6a 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -247,3 +247,11 @@ pub unsafe fn close_async(ns_window: IdRef) { }); }); } + +// `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 }); + }); +} diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 7fb969c3e..44f3672b1 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -735,6 +735,15 @@ impl UnownedWindow { Ok(()) } + #[inline] + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + unsafe { + util::set_ignore_mouse_events(*self.ns_window, ignore); + } + + Ok(()) + } + pub(crate) fn is_zoomed(&self) -> bool { // because `isZoomed` doesn't work if the window's borderless, // we make it resizable temporalily. diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index c6683d468..6a5e16982 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -405,6 +405,19 @@ impl Window { Ok(()) } + #[inline] + pub fn set_ignore_cursor_events(&self, ignore: 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, ignore) + }); + }); + + Ok(()) + } + #[inline] pub fn id(&self) -> WindowId { WindowId(self.window.0 .0) diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 6146e38c4..8111a24b9 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -90,6 +90,8 @@ bitflags! { const MINIMIZED = 1 << 12; + const IGNORE_CURSOR_EVENT = 1 << 15; + const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; } @@ -225,6 +227,9 @@ impl WindowFlags { if self.contains(WindowFlags::MAXIMIZED) { style |= WS_MAXIMIZE; } + if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) { + style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED; + } if self.intersects( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, ) { diff --git a/src/window.rs b/src/window.rs index 56b46b06b..37e4a9564 100644 --- a/src/window.rs +++ b/src/window.rs @@ -898,6 +898,19 @@ impl Window { pub fn drag_window(&self) -> Result<(), ExternalError> { self.window.drag_window() } + + /// Modifies whether the window catches cursor events. + /// + /// If `true`, the events are passed through the window such that any other window behind it receives them. + /// If `false` the window will catch the cursor events. By default cursor events are not ignored. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`] + #[inline] + pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { + self.window.set_ignore_cursor_events(ignore) + } } /// Monitor info functions.