From b673028148cb54e4735640ab55b405499f00d2bb Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 18 Aug 2023 21:39:55 +0200 Subject: [PATCH] feat(winit): client-side resize drag support --- src/window/settings.rs | 5 + winit/src/application.rs | 18 ++++ winit/src/application/drag_resize.rs | 132 +++++++++++++++++++++++++++ winit/src/settings.rs | 5 + 4 files changed, 160 insertions(+) create mode 100644 winit/src/application/drag_resize.rs diff --git a/src/window/settings.rs b/src/window/settings.rs index 458b9232c1..3627e9af05 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -8,6 +8,9 @@ pub struct Settings { /// The initial size of the window. pub size: (u32, u32), + /// The border area for the drag resize handle. + pub resize_border: u32, + /// The initial position of the window. pub position: Position, @@ -43,6 +46,7 @@ impl Default for Settings { fn default() -> Settings { Settings { size: (1024, 768), + resize_border: 8, position: Position::default(), min_size: None, max_size: None, @@ -61,6 +65,7 @@ impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { size: settings.size, + resize_border: settings.resize_border, position: iced_winit::Position::from(settings.position), min_size: settings.min_size, max_size: settings.max_size, diff --git a/winit/src/application.rs b/winit/src/application.rs index d1689452b0..c94e3cd04f 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,4 +1,5 @@ //! Create interactive, native cross-platform applications. +mod drag_resize; #[cfg(feature = "trace")] mod profiler; mod state; @@ -125,6 +126,8 @@ where let mut debug = Debug::new(); debug.startup_started(); + let resize_border = settings.window.resize_border; + #[cfg(feature = "trace")] let _ = info_span!("Application", "RUN").entered(); @@ -212,6 +215,7 @@ where window, should_be_visible, settings.exit_on_close_request, + resize_border, ); #[cfg(feature = "trace")] @@ -279,6 +283,7 @@ async fn run_instance( window: winit::window::Window, should_be_visible: bool, exit_on_close_request: bool, + resize_border: u32, ) where A: Application + 'static, E: Executor + 'static, @@ -331,6 +336,12 @@ async fn run_instance( &mut debug, )); + // Creates closure for handling the window drag resize state with winit. + let mut drag_resize_window_func = drag_resize::event_func( + &window, + resize_border as f64 * window.scale_factor(), + ); + let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); @@ -562,6 +573,13 @@ async fn run_instance( event: window_event, .. } => { + // Initiates a drag resize window state when found. + if let Some(func) = drag_resize_window_func.as_mut() { + if func(&window, &window_event) { + continue; + } + } + if requests_exit(&window_event, state.modifiers()) && exit_on_close_request { diff --git a/winit/src/application/drag_resize.rs b/winit/src/application/drag_resize.rs new file mode 100644 index 0000000000..379d7c9ed8 --- /dev/null +++ b/winit/src/application/drag_resize.rs @@ -0,0 +1,132 @@ +use winit::window::{CursorIcon, ResizeDirection}; + +/// If supported by winit, returns a closure that implements cursor resize support. +pub fn event_func( + window: &winit::window::Window, + border_size: f64, +) -> Option< + impl FnMut(&winit::window::Window, &winit::event::WindowEvent<'_>) -> bool, +> { + if window.drag_resize_window(ResizeDirection::East).is_ok() { + // Keep track of cursor when it is within a resizeable border. + let mut cursor_prev_resize_direction = None; + + Some( + move |window: &winit::window::Window, + window_event: &winit::event::WindowEvent<'_>| + -> bool { + // Keep track of border resize state and set cursor icon when in range + match window_event { + winit::event::WindowEvent::CursorMoved { + position, .. + } => { + if !window.is_decorated() { + let location = cursor_resize_direction( + window.inner_size(), + *position, + border_size, + ); + if location != cursor_prev_resize_direction { + window.set_cursor_icon( + resize_direction_cursor_icon(location), + ); + cursor_prev_resize_direction = location; + return true; + } + } + } + winit::event::WindowEvent::MouseInput { + state: winit::event::ElementState::Pressed, + button: winit::event::MouseButton::Left, + .. + } => { + if let Some(direction) = cursor_prev_resize_direction { + let _res = window.drag_resize_window(direction); + return true; + } + } + _ => (), + } + + false + }, + ) + } else { + None + } +} + +/// Get the cursor icon that corresponds to the resize direction. +fn resize_direction_cursor_icon( + resize_direction: Option, +) -> 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, + } +} + +/// Identifies resize direction based on cursor position and window dimensions. +#[allow(clippy::similar_names)] +fn cursor_resize_direction( + win_size: winit::dpi::PhysicalSize, + position: winit::dpi::PhysicalPosition, + border_size: f64, +) -> Option { + 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, + }, + }) +} diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 8d3e1b47fd..42a32a4f0d 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -65,6 +65,9 @@ pub struct Window { /// The size of the window. pub size: (u32, u32), + /// The border area for the drag resize handle. + pub resize_border: u32, + /// The position of the window. pub position: Position, @@ -100,6 +103,7 @@ impl fmt::Debug for Window { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Window") .field("size", &self.size) + .field("resize_border", &self.resize_border) .field("position", &self.position) .field("min_size", &self.min_size) .field("max_size", &self.max_size) @@ -226,6 +230,7 @@ impl Default for Window { fn default() -> Window { Window { size: (1024, 768), + resize_border: 8, position: Position::default(), min_size: None, max_size: None,