Add the functions start_drag_move and start_drag_resize to Window (#15674)

# Objective
Expose the `winit` functions
[drag_window](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.drag_window)
and
[resize_window](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.drag_resize_window).

Which allows implementing move & resize for windows without decorations.

## Solution

Add the functions `start_drag_move` and `start_drag_resize` to
`bevy_window::Window`, which are then assigned to fields in
`InternalWindowState`, and propagated to `winit` in the
`changed_windows` system.

## Testing

I've tested that both functions works on x11 and wayland. Not sure if
someone needs to test on windows/mac?

---

## Showcase

[Screencast from 2024-10-06 11-49-58
(trimmed).webm](https://github.com/user-attachments/assets/1cdee7b1-22bd-41d3-8a0a-6872a6ebf62c)

(The flickering in the video is some issue with resizing without
decorations on x11)

<details>
  <summary>Click to view showcase</summary>

Not the same code used in the video, but simple way to test moving a
window without decorations.
```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                decorations: false,
                ..default()
            }),
            ..default()
        }))
        .add_systems(Update, move_windows)
        .run();
}

fn move_windows(mut windows: Query<&mut Window>, input: Res<ButtonInput<MouseButton>>) {
    if input.pressed(MouseButton::Left) {
        for mut window in windows.iter_mut() {
            window.start_drag_move();
        }
    }
}
```

</details>

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Isse 2024-10-08 18:32:07 +02:00 committed by GitHub
parent 99b9a2fcd7
commit 82aa2e3161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 2 deletions

View file

@ -367,6 +367,22 @@ impl Window {
self.internal.minimize_request = Some(minimized);
}
/// Calling this will attempt to start a drag-move of the window.
///
/// There is no guarantee that this will work unless the left mouse button was
/// pressed immediately before this function was called.
pub fn start_drag_move(&mut self) {
self.internal.drag_move_request = true;
}
/// Calling this will attempt to start a drag-resize of the window.
///
/// There is no guarantee that this will work unless the left mouse button was
/// pressed immediately before this function was called.
pub fn start_drag_resize(&mut self, direction: ResizeDirection) {
self.internal.drag_resize_request = Some(direction);
}
/// The window's client area width in logical pixels.
///
/// See [`WindowResolution`] for an explanation about logical/physical sizes.
@ -897,6 +913,32 @@ pub enum CursorGrabMode {
Locked,
}
/// Defines the orientation in which a window resize will be performed.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum ResizeDirection {
/// Resize the window to the west.
West,
/// Resize the window to the north.
North,
/// Resize the window to the east.
East,
/// Resize the window to the south.
South,
/// Resize the window to the northwest.
Northwest,
/// Resize the window to the northeast.
Northeast,
/// Resize the window to the southwest.
Southwest,
/// Resize the window to the southeast.
Southeast,
}
/// Stores internal [`Window`] state that isn't directly accessible.
#[derive(Default, Debug, Copy, Clone, PartialEq, Reflect)]
#[cfg_attr(
@ -910,6 +952,10 @@ pub struct InternalWindowState {
minimize_request: Option<bool>,
/// If this is true then next frame we will ask to maximize/un-maximize the window depending on `maximized`.
maximize_request: Option<bool>,
/// If this is true then next frame we will ask to drag-move the window.
drag_move_request: bool,
/// If this is `Some` then the next frame we will ask to drag-resize the window.
drag_resize_request: Option<ResizeDirection>,
/// Unscaled cursor position.
physical_cursor_position: Option<DVec2>,
}
@ -924,6 +970,16 @@ impl InternalWindowState {
pub fn take_minimize_request(&mut self) -> Option<bool> {
self.minimize_request.take()
}
/// Consumes the current move request, if it exists. This should only be called by window backends.
pub fn take_move_request(&mut self) -> bool {
core::mem::take(&mut self.drag_move_request)
}
/// Consumes the current resize request, if it exists. This should only be called by window backends.
pub fn take_resize_request(&mut self) -> Option<ResizeDirection> {
self.drag_resize_request.take()
}
}
/// References a screen monitor.

View file

@ -8,7 +8,7 @@ use bevy_input::{
use bevy_math::Vec2;
#[cfg(feature = "custom_cursor")]
use bevy_window::SystemCursorIcon;
use bevy_window::{EnabledButtons, WindowLevel, WindowTheme};
use bevy_window::{EnabledButtons, ResizeDirection, WindowLevel, WindowTheme};
use winit::keyboard::{Key, NamedKey, NativeKey};
pub fn convert_keyboard_input(
@ -706,3 +706,18 @@ pub fn convert_enabled_buttons(enabled_buttons: EnabledButtons) -> winit::window
}
window_buttons
}
pub fn convert_resize_direction(
resize_direction: ResizeDirection,
) -> winit::window::ResizeDirection {
match resize_direction {
ResizeDirection::West => winit::window::ResizeDirection::West,
ResizeDirection::North => winit::window::ResizeDirection::North,
ResizeDirection::East => winit::window::ResizeDirection::East,
ResizeDirection::South => winit::window::ResizeDirection::South,
ResizeDirection::Northwest => winit::window::ResizeDirection::NorthWest,
ResizeDirection::Northeast => winit::window::ResizeDirection::NorthEast,
ResizeDirection::Southwest => winit::window::ResizeDirection::SouthWest,
ResizeDirection::Southeast => winit::window::ResizeDirection::SouthEast,
}
}

View file

@ -28,7 +28,8 @@ use winit::platform::web::WindowExtWebSys;
use crate::{
converters::{
convert_enabled_buttons, convert_window_level, convert_window_theme, convert_winit_theme,
convert_enabled_buttons, convert_resize_direction, convert_window_level,
convert_window_theme, convert_winit_theme,
},
get_best_videomode, get_fitting_videomode, select_monitor,
state::react_to_resize,
@ -462,6 +463,20 @@ pub(crate) fn changed_windows(
winit_window.set_minimized(minimized);
}
if window.internal.take_move_request() {
if let Err(e) = winit_window.drag_window() {
warn!("Winit returned an error while attempting to drag the window: {e}");
}
}
if let Some(resize_direction) = window.internal.take_resize_request() {
if let Err(e) =
winit_window.drag_resize_window(convert_resize_direction(resize_direction))
{
warn!("Winit returned an error while attempting to drag resize the window: {e}");
}
}
if window.focused != cache.window.focused && window.focused {
winit_window.focus_window();
}