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 window drag move and drag resize without decoration example. #15814

Merged
merged 11 commits into from
Oct 15, 2024
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
34 changes: 4 additions & 30 deletions crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea of using the existing CompassOctant 😄

self.internal.drag_resize_request = Some(direction);
}

Expand Down Expand Up @@ -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(
Expand All @@ -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<ResizeDirection>,
drag_resize_request: Option<CompassOctant>,
/// Unscaled cursor position.
physical_cursor_position: Option<DVec2>,
}
Expand All @@ -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<ResizeDirection> {
pub fn take_resize_request(&mut self) -> Option<CompassOctant> {
self.drag_resize_request.take()
}
}
Expand Down
24 changes: 11 additions & 13 deletions crates/bevy_winit/src/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
}
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
145 changes: 145 additions & 0 deletions examples/window/window_drag_move.rs
Original file line number Diff line number Diff line change
@@ -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<ButtonInput<KeyCode>>,
mut action: ResMut<LeftClickAction>,
mut dir: ResMut<ResizeDir>,
example_text: Query<Entity, With<Text>>,
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<LeftClickAction>,
input: Res<ButtonInput<MouseButton>>,
dir: Res<ResizeDir>,
) {
// 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);
}
}
}
}
}