feat(x11): Add Window::drag_resize_window (#2515)

This commit is contained in:
Michael Murphy 2023-01-11 18:07:09 +01:00 committed by GitHub
parent 08ce3af3e1
commit 9225b2812e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 280 additions and 15 deletions

View file

@ -52,6 +52,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Wayland, `RedrawRequested` not emitted during resize. - On Wayland, `RedrawRequested` not emitted during resize.
- **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface. - **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface.
- Added Orbital support for Redox OS - Added Orbital support for Redox OS
- On X11, added `drag_resize_window` method.
# 0.27.5 # 0.27.5

View file

@ -215,6 +215,7 @@ Legend:
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** | |Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** | |Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** | |Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |❌ |❌ |✔️ |❌ |**N/A**|**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.

View file

@ -0,0 +1,141 @@
//! Demonstrates capability to create in-app draggable regions for client-side decoration support.
use simple_logger::SimpleLogger;
use winit::{
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, ResizeDirection, WindowBuilder},
};
const BORDER: f64 = 8.0;
fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(600.0, 400.0))
.with_min_inner_size(winit::dpi::LogicalSize::new(400.0, 200.0))
.with_decorations(false)
.build(&event_loop)
.unwrap();
let mut border = false;
let mut cursor_location = None;
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!("Press 'B' to toggle borderless")
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CursorMoved { position, .. } => {
if !window.is_decorated() {
let new_location =
cursor_resize_direction(window.inner_size(), position, BORDER);
if new_location != cursor_location {
cursor_location = new_location;
window.set_cursor_icon(cursor_direction_icon(cursor_location))
}
}
}
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
if let Some(dir) = cursor_location {
let _res = window.drag_resize_window(dir);
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::B),
..
},
..
} => {
border = !border;
window.set_decorations(border);
}
_ => (),
},
_ => (),
});
}
fn cursor_direction_icon(resize_direction: Option<ResizeDirection>) -> CursorIcon {
match resize_direction {
Some(resize_direction) => match resize_direction {
ResizeDirection::East => CursorIcon::EResize,
ResizeDirection::North => CursorIcon::NResize,
ResizeDirection::NorthEast => CursorIcon::NeResize,
ResizeDirection::NorthWest => CursorIcon::NwResize,
ResizeDirection::South => CursorIcon::SResize,
ResizeDirection::SouthEast => CursorIcon::SeResize,
ResizeDirection::SouthWest => CursorIcon::SwResize,
ResizeDirection::West => CursorIcon::WResize,
},
None => CursorIcon::Default,
}
}
fn cursor_resize_direction(
win_size: winit::dpi::PhysicalSize<u32>,
position: winit::dpi::PhysicalPosition<f64>,
border_size: f64,
) -> Option<ResizeDirection> {
enum XDirection {
West,
East,
Default,
}
enum YDirection {
North,
South,
Default,
}
let xdir = if position.x < border_size {
XDirection::West
} else if position.x > (win_size.width as f64 - border_size) {
XDirection::East
} else {
XDirection::Default
};
let ydir = if position.y < border_size {
YDirection::North
} else if position.y > (win_size.height as f64 - border_size) {
YDirection::South
} else {
YDirection::Default
};
Some(match xdir {
XDirection::West => match ydir {
YDirection::North => ResizeDirection::NorthWest,
YDirection::South => ResizeDirection::SouthWest,
YDirection::Default => ResizeDirection::West,
},
XDirection::East => match ydir {
YDirection::North => ResizeDirection::NorthEast,
YDirection::South => ResizeDirection::SouthEast,
YDirection::Default => ResizeDirection::East,
},
XDirection::Default => match ydir {
YDirection::North => ResizeDirection::North,
YDirection::South => ResizeDirection::South,
YDirection::Default => return None,
},
})
}

View file

@ -24,7 +24,7 @@ use crate::{
error, error,
event::{self, StartCause, VirtualKeyCode}, event::{self, StartCause, VirtualKeyCode},
event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW},
window::{self, CursorGrabMode, Theme, WindowButtons, WindowLevel}, window::{self, CursorGrabMode, ResizeDirection, Theme, WindowButtons, WindowLevel},
}; };
fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option<event::VirtualKeyCode> { fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option<event::VirtualKeyCode> {
@ -1021,6 +1021,15 @@ impl Window {
)) ))
} }
pub fn drag_resize_window(
&self,
_direction: ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(), error::NotSupportedError::new(),

View file

@ -26,8 +26,8 @@ use crate::{
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowId as RootWindowId, WindowLevel, WindowButtons, WindowId as RootWindowId, WindowLevel,
}, },
}; };
@ -200,6 +200,10 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }

View file

@ -34,8 +34,8 @@ use crate::{
}, },
icon::Icon, icon::Icon,
window::{ window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowLevel, WindowButtons, WindowLevel,
}, },
}; };
@ -424,6 +424,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 drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest)) x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))

View file

@ -19,7 +19,8 @@ use crate::platform_impl::{
PlatformSpecificWindowBuilderAttributes as PlatformAttributes, PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
}; };
use crate::window::{ use crate::window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons,
}; };
use super::env::WindowingFeatures; use super::env::WindowingFeatures;
@ -560,6 +561,11 @@ impl Window {
Ok(()) Ok(())
} }
#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
self.send_request(WindowRequest::PassthroughMouseInput(!hittest)); self.send_request(WindowRequest::PassthroughMouseInput(!hittest));

View file

@ -4,6 +4,17 @@ use once_cell::sync::Lazy;
use super::*; use super::*;
// https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248
pub const MOVERESIZE_TOPLEFT: isize = 0;
pub const MOVERESIZE_TOP: isize = 1;
pub const MOVERESIZE_TOPRIGHT: isize = 2;
pub const MOVERESIZE_RIGHT: isize = 3;
pub const MOVERESIZE_BOTTOMRIGHT: isize = 4;
pub const MOVERESIZE_BOTTOM: isize = 5;
pub const MOVERESIZE_BOTTOMLEFT: isize = 6;
pub const MOVERESIZE_LEFT: isize = 7;
pub const MOVERESIZE_MOVE: isize = 8;
// This info is global to the window manager. // This info is global to the window manager.
static SUPPORTED_HINTS: Lazy<Mutex<Vec<ffi::Atom>>> = static SUPPORTED_HINTS: Lazy<Mutex<Vec<ffi::Atom>>> =
Lazy::new(|| Mutex::new(Vec::with_capacity(0))); Lazy::new(|| Mutex::new(Vec::with_capacity(0)));

View file

@ -21,8 +21,8 @@ use crate::{
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, Icon, Theme, UserAttentionType, WindowAttributes, CursorGrabMode, CursorIcon, Icon, ResizeDirection, Theme, UserAttentionType,
WindowButtons, WindowLevel, WindowAttributes, WindowButtons, WindowLevel,
}, },
}; };
@ -1433,7 +1433,27 @@ impl UnownedWindow {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
/// Moves the window while it is being dragged.
pub fn drag_window(&self) -> Result<(), ExternalError> { pub fn drag_window(&self) -> Result<(), ExternalError> {
self.drag_initiate(util::MOVERESIZE_MOVE)
}
/// Resizes the window while it is being dragged.
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.drag_initiate(match direction {
ResizeDirection::East => util::MOVERESIZE_RIGHT,
ResizeDirection::North => util::MOVERESIZE_TOP,
ResizeDirection::NorthEast => util::MOVERESIZE_TOPRIGHT,
ResizeDirection::NorthWest => util::MOVERESIZE_TOPLEFT,
ResizeDirection::South => util::MOVERESIZE_BOTTOM,
ResizeDirection::SouthEast => util::MOVERESIZE_BOTTOMRIGHT,
ResizeDirection::SouthWest => util::MOVERESIZE_BOTTOMLEFT,
ResizeDirection::West => util::MOVERESIZE_LEFT,
})
}
/// Initiates a drag operation while the left mouse button is pressed.
fn drag_initiate(&self, action: isize) -> Result<(), ExternalError> {
let pointer = self let pointer = self
.xconn .xconn
.query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
@ -1464,7 +1484,7 @@ impl UnownedWindow {
[ [
(window.x as c_long + pointer.win_x as c_long), (window.x as c_long + pointer.win_x as c_long),
(window.y as c_long + pointer.win_y as c_long), (window.y as c_long + pointer.win_y as c_long),
8, // _NET_WM_MOVERESIZE_MOVE action.try_into().unwrap(),
ffi::Button1 as c_long, ffi::Button1 as c_long,
1, 1,
], ],

View file

@ -32,8 +32,8 @@ use crate::{
Fullscreen, OsError, Fullscreen, OsError,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowId as RootWindowId, WindowLevel, WindowButtons, WindowId as RootWindowId, WindowLevel,
}, },
}; };
use core_graphics::display::{CGDisplay, CGPoint}; use core_graphics::display::{CGDisplay, CGPoint};
@ -784,6 +784,11 @@ impl WinitWindow {
Ok(()) Ok(())
} }
#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
util::set_ignore_mouse_events_sync(self, !hittest); util::set_ignore_mouse_events_sync(self, !hittest);

View file

@ -348,6 +348,16 @@ impl Window {
)) ))
} }
#[inline]
pub fn drag_resize_window(
&self,
_direction: window::ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported( Err(error::ExternalError::NotSupported(

View file

@ -3,8 +3,8 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
use crate::event; use crate::event;
use crate::icon::Icon; use crate::icon::Icon;
use crate::window::{ use crate::window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowId as RootWI, WindowLevel, WindowButtons, WindowId as RootWI, WindowLevel,
}; };
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle};
@ -267,6 +267,11 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))
} }
#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new())) Err(ExternalError::NotSupported(NotSupportedError::new()))

View file

@ -72,8 +72,8 @@ use crate::{
Fullscreen, PlatformSpecificWindowBuilderAttributes, WindowId, Fullscreen, PlatformSpecificWindowBuilderAttributes, WindowId,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowLevel, WindowButtons, WindowLevel,
}, },
}; };
@ -432,6 +432,11 @@ impl Window {
Ok(()) Ok(())
} }
#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
#[inline] #[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
let window = self.window.clone(); let window = self.window.clone();

View file

@ -1159,6 +1159,19 @@ impl Window {
self.window.drag_window() self.window.drag_window()
} }
/// Resizes 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
///
/// Only X11 is supported at this time.
#[inline]
pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
self.window.drag_resize_window(direction)
}
/// Modifies whether the window catches cursor events. /// Modifies whether the window catches cursor events.
/// ///
/// If `true`, the window will catch the cursor events. If `false`, events are passed through /// If `true`, the window will catch the cursor events. If `false`, events are passed through
@ -1353,6 +1366,35 @@ impl Default for CursorIcon {
} }
} }
/// Defines the orientation that a window resize will be performed.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ResizeDirection {
East,
North,
NorthEast,
NorthWest,
South,
SouthEast,
SouthWest,
West,
}
impl From<ResizeDirection> for CursorIcon {
fn from(direction: ResizeDirection) -> Self {
use ResizeDirection::*;
match direction {
East => CursorIcon::EResize,
North => CursorIcon::NResize,
NorthEast => CursorIcon::NeResize,
NorthWest => CursorIcon::NwResize,
South => CursorIcon::SResize,
SouthEast => CursorIcon::SeResize,
SouthWest => CursorIcon::SwResize,
West => CursorIcon::WResize,
}
}
}
/// Fullscreen modes. /// Fullscreen modes.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Fullscreen { pub enum Fullscreen {