Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add drag-resizing with cursor feature #2003

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- On Wayland, bump `smithay-client-toolkit` to 0.15.
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
- On Windows, added `drag_resize_window` method

# 0.25.0 (2021-05-15)

Expand Down
31 changes: 16 additions & 15 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,21 +193,22 @@ Legend:
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**|

### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android |iOS |WASM |
|----------------------- | ----- | ---- | ------- | ---------- |----- |----- | ------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A** |**N/A** |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A** |**N/A** |**N/A** |
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A** |**N/A** |❓ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |✔️ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A** |**N/A** |❓ |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A** |**N/A** |**N/A** |
|Resize window with cursor |✔️ |❌ |❌ |❌ |**N/A** |**N/A** |**N/A** |

### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
Expand Down
156 changes: 156 additions & 0 deletions examples/borderless.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use simple_logger::SimpleLogger;
use winit::{
dpi::LogicalSize,
event::{
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, ResizeDirection, WindowBuilder},
};

#[derive(PartialEq, Eq, Clone, Copy)]
enum CursorLocation {
Caption,
Top,
Bottom,
Right,
Left,
TopRight,
TopLeft,
BottomRight,
BottomLeft,
Default,
}

impl CursorLocation {
// Conversion to appropriate cursor Icon
fn get_cursor_icon(&self) -> CursorIcon {
match self {
CursorLocation::Top | CursorLocation::Bottom => CursorIcon::NsResize,
CursorLocation::Right | CursorLocation::Left => CursorIcon::EwResize,
CursorLocation::TopRight | CursorLocation::BottomLeft => CursorIcon::NeswResize,
CursorLocation::TopLeft | CursorLocation::BottomRight => CursorIcon::NwseResize,
_ => CursorIcon::Arrow,
}
}

// Conversion to a resize direction
fn to_resize_direction(&self) -> Option<ResizeDirection> {
match self {
CursorLocation::Top => Some(ResizeDirection::Top),
CursorLocation::Bottom => Some(ResizeDirection::Bottom),
CursorLocation::Right => Some(ResizeDirection::Right),
CursorLocation::Left => Some(ResizeDirection::Left),
CursorLocation::TopRight => Some(ResizeDirection::TopRight),
CursorLocation::TopLeft => Some(ResizeDirection::TopLeft),
CursorLocation::BottomRight => Some(ResizeDirection::BottomRight),
CursorLocation::BottomLeft => Some(ResizeDirection::BottomLeft),
_ => None,
}
}

// Intersects X locations and Y locations
// Assumes that the x_location will only be Left, Right or Default
// Assumes that the y_location will only be Top, Caption, Bottom or Default
fn intersect(x_location: Self, y_location: Self) -> Self {
match (x_location, y_location) {
(CursorLocation::Default, _) => y_location,
(_, CursorLocation::Default) => x_location,
(CursorLocation::Left, CursorLocation::Top) => CursorLocation::TopLeft,
(CursorLocation::Left, CursorLocation::Bottom) => CursorLocation::BottomLeft,
(CursorLocation::Right, CursorLocation::Top) => CursorLocation::TopRight,
(CursorLocation::Right, CursorLocation::Bottom) => CursorLocation::BottomRight,
_ => CursorLocation::Default,
}
}
}

const BORDER: f64 = 5.;
const CAPTIONHEIGHT: f64 = 20.; // Titlebar
const INIT_WIDTH: f64 = 400.;
const INIT_HEIGHT: f64 = 200.;

fn main() {
SimpleLogger::new().init().unwrap();
let event_loop = EventLoop::new();

let window = WindowBuilder::new().build(&event_loop).unwrap();
window.set_min_inner_size(Some(LogicalSize::new(INIT_WIDTH, INIT_HEIGHT)));

let mut border = false;
window.set_decorations(border);

let mut cursor_location = CursorLocation::Caption;
let mut x_border = INIT_WIDTH - BORDER;
let mut y_border = INIT_HEIGHT - BORDER;

event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(StartCause::Init) => {
eprintln!(
"Press 'B' to toggle borderless \nThe top of the screen functions as a titlebar"
)
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::CursorMoved { position, .. } => {
// Test for X
let x_location = if position.x < BORDER {
CursorLocation::Left
} else if position.x > x_border {
CursorLocation::Right
} else {
CursorLocation::Default
};

// Test for Y
let y_location = if position.y < BORDER {
CursorLocation::Top
} else if position.y < CAPTIONHEIGHT {
CursorLocation::Caption
} else if position.y > y_border {
CursorLocation::Bottom
} else {
CursorLocation::Default
};

let new_location = CursorLocation::intersect(x_location, y_location);

if new_location != cursor_location {
cursor_location = new_location;
window.set_cursor_icon(cursor_location.get_cursor_icon())
}
}

WindowEvent::Resized(new_size) => {
x_border = new_size.width as f64 - BORDER;
y_border = new_size.height as f64 - BORDER;
}

WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
if let Some(dir) = cursor_location.to_resize_direction() {
window.drag_resize_window(dir).unwrap()
} else if cursor_location == CursorLocation::Caption {
window.drag_window().unwrap()
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(VirtualKeyCode::B),
..
},
..
} => {
border = !border;
window.set_decorations(border);
}
_ => (),
},
_ => (),
});
}
9 changes: 9 additions & 0 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,15 @@ impl Window {
))
}

pub fn drag_resize_window(
&self,
_direction: window::ResizeDirection,
) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))
}

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() {
unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ }
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/ios/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use crate::{
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
CursorIcon, Fullscreen, ResizeDirection, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
};

Expand Down Expand Up @@ -186,6 +187,10 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

pub fn set_minimized(&self, _minimized: bool) {
warn!("`Window::set_minimized` is ignored on iOS")
}
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
window::{CursorIcon, Fullscreen, ResizeDirection, UserAttentionType, WindowAttributes},
};

pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
Expand Down Expand Up @@ -363,6 +363,11 @@ impl 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]
pub fn scale_factor(&self) -> f64 {
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes};
use crate::window::{CursorIcon, Fullscreen, ResizeDirection, UserAttentionType, WindowAttributes};

use super::env::WindowingFeatures;
use super::event_loop::WinitState;
Expand Down Expand Up @@ -447,6 +447,11 @@ impl Window {
Ok(())
}

#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

#[inline]
pub fn set_ime_position(&self, position: Position) {
let scale_factor = self.scale_factor() as f64;
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
},
window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
window::{CursorIcon, Fullscreen, Icon, ResizeDirection, UserAttentionType, WindowAttributes},
};

use super::{
Expand Down Expand Up @@ -1377,6 +1377,11 @@ impl UnownedWindow {
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
}

#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) {
let _ = self
.ime_sender
Expand Down
8 changes: 7 additions & 1 deletion src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ use crate::{
OsError,
},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
CursorIcon, Fullscreen, ResizeDirection, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
};
use cocoa::{
Expand Down Expand Up @@ -613,6 +614,11 @@ impl UnownedWindow {
Ok(())
}

#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

pub(crate) fn is_zoomed(&self) -> bool {
// because `isZoomed` doesn't work if the window's borderless,
// we make it resizable temporalily.
Expand Down
8 changes: 7 additions & 1 deletion src/platform_impl/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use crate::event;
use crate::icon::Icon;
use crate::monitor::MonitorHandle as RootMH;
use crate::window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI,
CursorIcon, Fullscreen, ResizeDirection, UserAttentionType, WindowAttributes,
WindowId as RootWI,
};

use raw_window_handle::web::WebHandle;
Expand Down Expand Up @@ -227,6 +228,11 @@ impl Window {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

#[inline]
pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

#[inline]
pub fn set_minimized(&self, _minimized: bool) {
// Intentionally a no-op, as canvases cannot be 'minimized'
Expand Down
Loading