diff --git a/Cargo.toml b/Cargo.toml index 2c0113a0e6..efc4ea636f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3286,6 +3286,17 @@ description = "Demonstrates customizing default window settings" category = "Window" wasm = true +[[example]] +name = "window_drag_move" +path = "examples/window/window_drag_move.rs" +doc-scrape-examples = true + +[package.metadata.example.window_drag_move] +name = "Window Drag Move" +description = "Demonstrates drag move and drag resize without window decoration" +category = "Window" +wasm = false + [[example]] name = "ambiguity_detection" path = "tests/ecs/ambiguity_detection.rs" diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index cc21e6ab8c..7b62b013f2 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -4,7 +4,7 @@ use bevy_ecs::{ entity::{Entity, VisitEntities, VisitEntitiesMut}, prelude::{Component, ReflectComponent}, }; -use bevy_math::{DVec2, IVec2, UVec2, Vec2}; +use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(feature = "serialize")] @@ -460,7 +460,7 @@ impl 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) { + pub fn start_drag_resize(&mut self, direction: CompassOctant) { self.internal.drag_resize_request = Some(direction); } @@ -994,32 +994,6 @@ 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( @@ -1036,7 +1010,7 @@ pub struct InternalWindowState { /// 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, + drag_resize_request: Option, /// Unscaled cursor position. physical_cursor_position: Option, } @@ -1058,7 +1032,7 @@ impl InternalWindowState { } /// Consumes the current resize request, if it exists. This should only be called by window backends. - pub fn take_resize_request(&mut self) -> Option { + pub fn take_resize_request(&mut self) -> Option { self.drag_resize_request.take() } } diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index fc5ff5c29a..9a9c33ef36 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -5,10 +5,10 @@ use bevy_input::{ touch::{ForceTouch, TouchInput, TouchPhase}, ButtonState, }; -use bevy_math::Vec2; +use bevy_math::{CompassOctant, Vec2}; #[cfg(feature = "custom_cursor")] use bevy_window::SystemCursorIcon; -use bevy_window::{EnabledButtons, ResizeDirection, WindowLevel, WindowTheme}; +use bevy_window::{EnabledButtons, WindowLevel, WindowTheme}; use winit::keyboard::{Key, NamedKey, NativeKey}; pub fn convert_keyboard_input( @@ -707,17 +707,15 @@ pub fn convert_enabled_buttons(enabled_buttons: EnabledButtons) -> winit::window window_buttons } -pub fn convert_resize_direction( - resize_direction: ResizeDirection, -) -> winit::window::ResizeDirection { +pub fn convert_resize_direction(resize_direction: CompassOctant) -> 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, + CompassOctant::West => winit::window::ResizeDirection::West, + CompassOctant::North => winit::window::ResizeDirection::North, + CompassOctant::East => winit::window::ResizeDirection::East, + CompassOctant::South => winit::window::ResizeDirection::South, + CompassOctant::NorthWest => winit::window::ResizeDirection::NorthWest, + CompassOctant::NorthEast => winit::window::ResizeDirection::NorthEast, + CompassOctant::SouthWest => winit::window::ResizeDirection::SouthWest, + CompassOctant::SouthEast => winit::window::ResizeDirection::SouthEast, } } diff --git a/examples/README.md b/examples/README.md index 1ef19bcbbc..7fdd705ac6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -538,6 +538,7 @@ Example | Description [Scale Factor Override](../examples/window/scale_factor_override.rs) | Illustrates how to customize the default window settings [Screenshot](../examples/window/screenshot.rs) | Shows how to save screenshots to disk [Transparent Window](../examples/window/transparent_window.rs) | Illustrates making the window transparent and hiding the window decoration +[Window Drag Move](../examples/window/window_drag_move.rs) | Demonstrates drag move and drag resize without window decoration [Window Resizing](../examples/window/window_resizing.rs) | Demonstrates resizing and responding to resizing a window [Window Settings](../examples/window/window_settings.rs) | Demonstrates customizing default window settings diff --git a/examples/window/window_drag_move.rs b/examples/window/window_drag_move.rs new file mode 100644 index 0000000000..54f904707c --- /dev/null +++ b/examples/window/window_drag_move.rs @@ -0,0 +1,145 @@ +//! This example illustrates drag move and drag resize without window +//! decorations. +//! +//! When window decorations are not present, the user cannot drag a window by +//! its titlebar to change its position. The `start_drag_move()` function +//! permits a users to drag a window by left clicking anywhere in the window; +//! left click must be pressed and other constraints can be imposed. For +//! instance an application could require a user to hold down alt and left click +//! to drag a window. +//! +//! The `start_drag_resize()` function behaves similarly but permits a window to +//! be resized. +use bevy::{math::CompassOctant, prelude::*}; + +/// Determine what do on left click. +#[derive(Resource, Debug)] +enum LeftClickAction { + /// Do nothing. + Nothing, + /// Move the window on left click. + Move, + /// Resize the window on left click. + Resize, +} + +/// What direction index should the window resize toward. +#[derive(Resource)] +struct ResizeDir(usize); + +/// Directions that the drag resizes the window toward. +const DIRECTIONS: [CompassOctant; 8] = [ + CompassOctant::North, + CompassOctant::NorthEast, + CompassOctant::East, + CompassOctant::SouthEast, + CompassOctant::South, + CompassOctant::SouthWest, + CompassOctant::West, + CompassOctant::NorthWest, +]; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + decorations: false, + ..default() + }), + ..default() + })) + .insert_resource(ResizeDir(7)) + .insert_resource(LeftClickAction::Move) + .add_systems(Startup, setup) + .add_systems(Update, (handle_input, move_or_resize_windows)) + .run(); +} + +fn setup(mut commands: Commands) { + // Camera + commands.spawn(Camera3d::default()); + + // UI + commands + .spawn(( + NodeBundle { + style: Style { + position_type: PositionType::Absolute, + padding: UiRect::all(Val::Px(5.0)), + ..default() + }, + background_color: Color::BLACK.with_alpha(0.75).into(), + ..default() + }, + GlobalZIndex(i32::MAX), + )) + .with_children(|p| { + p.spawn(Text::default()).with_children(|p| { + p.spawn(TextSpan::new( + "Demonstrate drag move and drag resize without window decorations.\n\n", + )); + p.spawn(TextSpan::new("Controls:\n")); + p.spawn(TextSpan::new("A - change left click action [")); + p.spawn(TextSpan::new("Move")); + p.spawn(TextSpan::new("]\n")); + p.spawn(TextSpan::new("S / D - change resize direction [")); + p.spawn(TextSpan::new("NorthWest")); + p.spawn(TextSpan::new("]\n")); + }); + }); +} + +fn handle_input( + input: Res>, + mut action: ResMut, + mut dir: ResMut, + example_text: Query>, + mut writer: TextUiWriter, +) { + use LeftClickAction::*; + if input.just_pressed(KeyCode::KeyA) { + *action = match *action { + Move => Resize, + Resize => Nothing, + Nothing => Move, + }; + *writer.text(example_text.single(), 4) = format!("{:?}", *action); + } + + if input.just_pressed(KeyCode::KeyS) { + dir.0 = dir + .0 + .checked_sub(1) + .unwrap_or(DIRECTIONS.len().saturating_sub(1)); + *writer.text(example_text.single(), 7) = format!("{:?}", DIRECTIONS[dir.0]); + } + + if input.just_pressed(KeyCode::KeyD) { + dir.0 = (dir.0 + 1) % DIRECTIONS.len(); + *writer.text(example_text.single(), 7) = format!("{:?}", DIRECTIONS[dir.0]); + } +} + +fn move_or_resize_windows( + mut windows: Query<&mut Window>, + action: Res, + input: Res>, + dir: Res, +) { + // Both `start_drag_move()` and `start_drag_resize()` must be called after a + // left mouse button press as done here. + // + // winit 0.30.5 may panic when initiated without a left mouse button press. + if input.just_pressed(MouseButton::Left) { + for mut window in windows.iter_mut() { + match *action { + LeftClickAction::Nothing => (), + LeftClickAction::Move => window.start_drag_move(), + LeftClickAction::Resize => { + let d = DIRECTIONS[dir.0]; + window.start_drag_resize(d); + } + } + } + } +}