Add dragging window with cursor feature (#1840)

* X11 implementation.

* Introduce example.

* Wayland implementation.

* Windows implementation.

* Improve Wayland seat passing.

* MacOS implementation.

* Correct windows implementation per specification.

* Update dependency smithay-client-toolkit from branch to master.

* Fixed blocking thread in windows implementation.

* Add multi-window example.

* Move Wayland to a different PR.

* Fix CHANGELOG.

* Improve example.

Co-authored-by: Markus Røyset <maroider@protonmail.com>

* Rename `set_drag_window` to `begin_drag`.

* Improve example.

* Fix CHANGELOG.

* Fix CHANGELOG.

Co-authored-by: Markus Røyset <maroider@protonmail.com>

* Rename to `drag_window`.

* Fix typo.

* Re-introduce Wayland implementation.

* Fixing Wayland build.

* Fixing Wayland build.

* Move SCTK to 0.12.3.

Co-authored-by: Markus Røyset <maroider@protonmail.com>
This commit is contained in:
daxpedda 2021-03-07 10:43:23 +01:00 committed by GitHub
parent 4192d04a53
commit 98470393d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 233 additions and 5 deletions

View file

@ -13,6 +13,7 @@
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`. - On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme. - On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements. - On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
# 0.24.0 (2020-12-09) # 0.24.0 (2020-12-09)

View file

@ -85,7 +85,7 @@ features = [
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true } wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.12", optional = true } sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true }
mio = { version = "0.6", optional = true } mio = { version = "0.6", optional = true }
mio-extras = { version = "2.0", optional = true } mio-extras = { version = "2.0", optional = true }
x11-dl = { version = "2.18.5", optional = true } x11-dl = { version = "2.18.5", optional = true }

View file

@ -209,6 +209,7 @@ Legend:
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ | |Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ | |Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
### Pending API Reworks ### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms. Changes in the API that have been agreed upon but aren't implemented across all platforms.

73
examples/drag_window.rs Normal file
View file

@ -0,0 +1,73 @@
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder, WindowId},
};
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
let mut switched = false;
let mut entered_id = window_2.id();
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Switch which window is to be dragged by pressing \"x\".")
}
Event::WindowEvent { event, window_id } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
let window = if (window_id == window_1.id() && switched)
|| (window_id == window_2.id() && !switched)
{
&window_2
} else {
&window_1
};
window.drag_window().unwrap()
}
WindowEvent::CursorEntered { .. } => {
entered_id = window_id;
name_windows(entered_id, switched, &window_1, &window_2)
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::X),
..
},
..
} => {
switched = !switched;
name_windows(entered_id, switched, &window_1, &window_2);
println!("Switched!")
}
_ => (),
},
_ => (),
});
}
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
let (drag_target, other) =
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
(&window_2, &window_1)
} else {
(&window_1, &window_2)
};
drag_target.set_title("drag target");
other.set_title("winit window");
}

View file

@ -556,6 +556,12 @@ impl Window {
pub fn set_cursor_visible(&self, _: bool) {} pub fn set_cursor_visible(&self, _: bool) {}
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() { let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() {
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ } unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }

View file

@ -182,6 +182,10 @@ impl Inner {
debug!("`Window::set_cursor_visible` is ignored on iOS") debug!("`Window::set_cursor_visible` is ignored on iOS")
} }
pub fn drag_window(&self) -> 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

@ -358,6 +358,11 @@ impl Window {
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible)) x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
} }
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_window())
}
#[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

@ -4,6 +4,7 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent}; use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent; use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
use sctk::seat::pointer::ThemedPointer; use sctk::seat::pointer::ThemedPointer;
@ -28,6 +29,7 @@ pub(super) fn handle_pointer(
event: PointerEvent, event: PointerEvent,
pointer_data: &Rc<RefCell<PointerData>>, pointer_data: &Rc<RefCell<PointerData>>,
winit_state: &mut WinitState, winit_state: &mut WinitState,
seat: WlSeat,
) { ) {
let event_sink = &mut winit_state.event_sink; let event_sink = &mut winit_state.event_sink;
let mut pointer_data = pointer_data.borrow_mut(); let mut pointer_data = pointer_data.borrow_mut();
@ -59,6 +61,7 @@ pub(super) fn handle_pointer(
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(), pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(), latest_serial: pointer_data.latest_serial.clone(),
seat,
}; };
window_handle.pointer_entered(winit_pointer); window_handle.pointer_entered(winit_pointer);
@ -101,6 +104,7 @@ pub(super) fn handle_pointer(
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(), pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(), latest_serial: pointer_data.latest_serial.clone(),
seat,
}; };
window_handle.pointer_left(winit_pointer); window_handle.pointer_left(winit_pointer);

View file

@ -13,6 +13,7 @@ use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_p
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer}; use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::{ConceptFrame, Window};
use crate::event::ModifiersState; use crate::event::ModifiersState;
use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::event_loop::WinitState;
@ -35,6 +36,9 @@ pub struct WinitPointer {
/// Latest observed serial in pointer events. /// Latest observed serial in pointer events.
latest_serial: Rc<Cell<u32>>, latest_serial: Rc<Cell<u32>>,
/// Seat.
seat: WlSeat,
} }
impl PartialEq for WinitPointer { impl PartialEq for WinitPointer {
@ -144,6 +148,10 @@ impl WinitPointer {
confined_pointer.destroy(); confined_pointer.destroy();
} }
} }
pub fn drag_window(&self, window: &Window<ConceptFrame>) {
window.start_interactive_move(&self.seat, self.latest_serial.get());
}
} }
/// A pointer wrapper for easy releasing and managing pointers. /// A pointer wrapper for easy releasing and managing pointers.
@ -172,11 +180,18 @@ impl Pointers {
pointer_constraints.clone(), pointer_constraints.clone(),
modifiers_state, modifiers_state,
))); )));
let pointer_seat = seat.detach();
let pointer = theme_manager.theme_pointer_with_impl( let pointer = theme_manager.theme_pointer_with_impl(
seat, seat,
move |event, pointer, mut dispatch_data| { move |event, pointer, mut dispatch_data| {
let winit_state = dispatch_data.get::<WinitState>().unwrap(); let winit_state = dispatch_data.get::<WinitState>().unwrap();
handlers::handle_pointer(pointer, event, &pointer_data, winit_state); handlers::handle_pointer(
pointer,
event,
&pointer_data,
winit_state,
pointer_seat.clone(),
);
}, },
); );

View file

@ -586,6 +586,18 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
let drag_window_request = WindowRequest::DragWindow;
self.window_requests
.lock()
.unwrap()
.push(drag_window_request);
self.event_loop_awakener.ping();
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

@ -34,6 +34,9 @@ pub enum WindowRequest {
/// Grab cursor. /// Grab cursor.
GrabCursor(bool), GrabCursor(bool),
/// Drag window.
DragWindow,
/// Maximize the window. /// Maximize the window.
Maximize(bool), Maximize(bool),
@ -268,6 +271,12 @@ impl WindowHandle {
pointer.set_cursor(Some(cursor_icon)); pointer.set_cursor(Some(cursor_icon));
} }
} }
pub fn drag_window(&self) {
for pointer in self.pointers.iter() {
pointer.drag_window(&self.window);
}
}
} }
#[inline] #[inline]
@ -299,6 +308,9 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
WindowRequest::GrabCursor(grab) => { WindowRequest::GrabCursor(grab) => {
window_handle.set_cursor_grab(grab); window_handle.set_cursor_grab(grab);
} }
WindowRequest::DragWindow => {
window_handle.drag_window();
}
WindowRequest::Maximize(maximize) => { WindowRequest::Maximize(maximize) => {
if maximize { if maximize {
window_handle.window.set_maximized(); window_handle.window.set_maximized();

View file

@ -1276,6 +1276,46 @@ impl UnownedWindow {
self.set_cursor_position_physical(x, y) self.set_cursor_position_physical(x, y)
} }
pub fn drag_window(&self) -> Result<(), ExternalError> {
let pointer = self
.xconn
.query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
let window = self.inner_position().map_err(ExternalError::NotSupported)?;
let message = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_MOVERESIZE\0") };
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
// if the cursor isn't currently grabbed
let mut grabbed_lock = self.cursor_grabbed.lock();
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
self.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
*grabbed_lock = false;
// we keep the lock until we are done
self.xconn
.send_client_msg(
self.xwindow,
self.root,
message,
Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask),
[
(window.x as c_long + pointer.win_x as c_long),
(window.y as c_long + pointer.win_y as c_long),
8, // _NET_WM_MOVERESIZE_MOVE
ffi::Button1 as c_long,
1,
],
)
.flush()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
}
pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) { pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
let _ = self let _ = self
.ime_sender .ime_sender

View file

@ -636,6 +636,16 @@ impl UnownedWindow {
Ok(()) Ok(())
} }
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
unsafe {
let event: id = msg_send![NSApp(), currentEvent];
let _: () = msg_send![*self.ns_window, performWindowDragWithEvent: event];
}
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

@ -222,6 +222,11 @@ impl Window {
} }
} }
#[inline]
pub fn drag_window(&self) -> 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

@ -14,8 +14,8 @@ use std::{
use winapi::{ use winapi::{
ctypes::c_int, ctypes::c_int,
shared::{ shared::{
minwindef::{HINSTANCE, UINT}, minwindef::{HINSTANCE, LPARAM, UINT, WPARAM},
windef::{HWND, POINT, RECT}, windef::{HWND, POINT, POINTS, RECT},
}, },
um::{ um::{
combaseapi, dwmapi, combaseapi, dwmapi,
@ -26,7 +26,7 @@ use winapi::{
oleidl::LPDROPTARGET, oleidl::LPDROPTARGET,
shobjidl_core::{CLSID_TaskbarList, ITaskbarList2}, shobjidl_core::{CLSID_TaskbarList, ITaskbarList2},
wingdi::{CreateRectRgn, DeleteObject}, wingdi::{CreateRectRgn, DeleteObject},
winnt::LPCWSTR, winnt::{LPCWSTR, SHORT},
winuser, winuser,
}, },
}; };
@ -357,6 +357,30 @@ impl Window {
Ok(()) Ok(())
} }
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
unsafe {
let points = {
let mut pos = mem::zeroed();
winuser::GetCursorPos(&mut pos);
pos
};
let points = POINTS {
x: points.x as SHORT,
y: points.y as SHORT,
};
winuser::ReleaseCapture();
winuser::PostMessageW(
self.window.0,
winuser::WM_NCLBUTTONDOWN,
winuser::HTCAPTION as WPARAM,
&points as *const _ as LPARAM,
);
}
Ok(())
}
#[inline] #[inline]
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
WindowId(self.window.0) WindowId(self.window.0)

View file

@ -764,6 +764,22 @@ impl Window {
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
self.window.set_cursor_visible(visible) self.window.set_cursor_visible(visible)
} }
/// Moves the window with the left mouse button until the button is released.
///
/// There's no guarantee that this will work unless the left mouse button was pressed
/// immediately before this function is called.
///
/// ## Platform-specific
///
/// - **X11:** Un-grabs the cursor.
/// - **Wayland:** Requires the cursor to be inside the window to be dragged.
/// - **macOS:** May prevent the button release event to be triggered.
/// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
self.window.drag_window()
}
} }
/// Monitor info functions. /// Monitor info functions.