diff --git a/docs/reference/src/language/builtins/elements.md b/docs/reference/src/language/builtins/elements.md index bbbea333f13..6feaf1564bc 100644 --- a/docs/reference/src/language/builtins/elements.md +++ b/docs/reference/src/language/builtins/elements.md @@ -864,4 +864,5 @@ or smaller. The initial width can be controlled with the `preferred-width` prope - **`default-font-weight`** (_in_ _int_): The font weight to use as default in text elements inside this window, that don't have their `font-weight` property set. The values range from 100 (lightest) to 900 (thickest). 400 is the normal weight. - **`icon`** (_in_ _image_): The window icon shown in the title bar or the task bar on window managers supporting it. - **`no-frame`** (_in_ _bool_): Whether the window should be borderless/frameless or not. +- **`resize-border`** (_in_ _length_): Size of the resize border in borderless/frameless windows (winit only for now). - **`title`** (_in_ _string_): The window title that is shown in the title bar. diff --git a/internal/backends/winit/drag_resize_window.rs b/internal/backends/winit/drag_resize_window.rs new file mode 100644 index 00000000000..dc6cd9b846e --- /dev/null +++ b/internal/backends/winit/drag_resize_window.rs @@ -0,0 +1,91 @@ +use winit::window::{CursorIcon, ResizeDirection}; + +pub fn handle_cursor_move_for_resize( + window: &winit::window::Window, + position: winit::dpi::PhysicalPosition, + current_direction: Option, + border_size: f64, +) -> Option { + if !window.is_decorated() && window.is_resizable() { + let location = get_resize_direction(window.inner_size(), position, border_size); + + if current_direction != location { + window.set_cursor_icon(resize_direction_cursor_icon(location)); + } + + return location; + } + + None +} + +pub fn handle_resize(window: &winit::window::Window, direction: Option) { + if let Some(dir) = direction { + let _ = window.drag_resize_window(dir); + } +} + +/// 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, + } +} + +fn get_resize_direction( + win_size: winit::dpi::PhysicalSize, + position: winit::dpi::PhysicalPosition, + border_size: f64, +) -> Option { + enum X { + West, + East, + Default, + } + + enum Y { + North, + South, + Default, + } + + let xdir = if position.x < border_size { + X::West + } else if position.x > (win_size.width as f64 - border_size) { + X::East + } else { + X::Default + }; + + let ydir = if position.y < border_size { + Y::North + } else if position.y > (win_size.height as f64 - border_size) { + Y::South + } else { + Y::Default + }; + + Some(match (xdir, ydir) { + (X::West, Y::North) => ResizeDirection::NorthWest, + (X::West, Y::South) => ResizeDirection::SouthWest, + (X::West, Y::Default) => ResizeDirection::West, + + (X::East, Y::North) => ResizeDirection::NorthEast, + (X::East, Y::South) => ResizeDirection::SouthEast, + (X::East, Y::Default) => ResizeDirection::East, + + (X::Default, Y::North) => ResizeDirection::North, + (X::Default, Y::South) => ResizeDirection::South, + (X::Default, Y::Default) => return None, + }) +} diff --git a/internal/backends/winit/event_loop.rs b/internal/backends/winit/event_loop.rs index 7309a660649..d9acb7aa221 100644 --- a/internal/backends/winit/event_loop.rs +++ b/internal/backends/winit/event_loop.rs @@ -7,6 +7,7 @@ [WindowAdapter] trait used by the generated code and the run-time to change aspects of windows on the screen. */ +use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize}; use crate::winitwindowadapter::WinitWindowAdapter; use crate::SlintUserEvent; #[cfg(not(target_arch = "wasm32"))] @@ -24,6 +25,7 @@ use std::cell::{RefCell, RefMut}; use std::rc::{Rc, Weak}; use winit::event::{Event, WindowEvent}; use winit::event_loop::EventLoopWindowTarget; +use winit::window::ResizeDirection; #[cfg(not(target_arch = "wasm32"))] /// The Default, and the selection clippoard @@ -247,6 +249,7 @@ pub struct EventLoopState { pressed: bool, loop_error: Option, + current_resize_direction: Option, } impl EventLoopState { @@ -342,6 +345,14 @@ impl EventLoopState { runtime_window.process_key_input(event); } WindowEvent::CursorMoved { position, .. } => { + self.current_resize_direction = handle_cursor_move_for_resize( + window.winit_window().as_ref(), + position, + self.current_resize_direction, + runtime_window + .window_item() + .map_or(0_f64, |w| w.as_pin_ref().resize_border().0.into()), + ); let position = position.to_logical(runtime_window.scale_factor() as f64); self.cursor_pos = euclid::point2(position.x, position.y); runtime_window.process_mouse_input(MouseEvent::Moved { position: self.cursor_pos }); @@ -378,6 +389,16 @@ impl EventLoopState { }; let ev = match state { winit::event::ElementState::Pressed => { + if button == PointerEventButton::Left + && self.current_resize_direction.is_some() + { + handle_resize( + window.winit_window().as_ref(), + self.current_resize_direction, + ); + return; + } + self.pressed = true; MouseEvent::Pressed { position: self.cursor_pos, button, click_count: 0 } } diff --git a/internal/backends/winit/lib.rs b/internal/backends/winit/lib.rs index b8a9173f8a5..67b0b3021fb 100644 --- a/internal/backends/winit/lib.rs +++ b/internal/backends/winit/lib.rs @@ -12,7 +12,9 @@ use i_slint_core::window::WindowAdapter; use renderer::WinitCompatibleRenderer; use std::rc::Rc; +mod drag_resize_window; mod winitwindowadapter; + use i_slint_core::platform::PlatformError; use winitwindowadapter::*; pub(crate) mod event_loop; diff --git a/internal/compiler/builtins.slint b/internal/compiler/builtins.slint index 2912c4adc3f..f17ba9be829 100644 --- a/internal/compiler/builtins.slint +++ b/internal/compiler/builtins.slint @@ -149,6 +149,7 @@ component WindowItem { in property color <=> background; in property title: "Slint Window"; in property no-frame; + in property resize-border; in property always-on-top; in property default-font-family; in-out property default-font-size; // <=> StyleMetrics.default-font-size set in apply_default_properties_from_style diff --git a/internal/core/items.rs b/internal/core/items.rs index 9c1276b0de0..10af80b69d0 100644 --- a/internal/core/items.rs +++ b/internal/core/items.rs @@ -1254,6 +1254,7 @@ pub struct WindowItem { pub background: Property, pub title: Property, pub no_frame: Property, + pub resize_border: Property, pub always_on_top: Property, pub icon: Property, pub default_font_family: Property,