mirror of
https://github.com/bevyengine/bevy
synced 2024-11-27 23:20:20 +00:00
Add window drag move and drag resize without decoration example. (#15814)
# Objective Add an example for the new drag move and drag resize introduced by PR #15674 and fix #15734. ## Solution I created an example that allows the user to exercise drag move and drag resize separately. The user can also choose what direction the resize works in. ![Screenshot 2024-10-10 at 4 06 43 AM](https://github.com/user-attachments/assets/1da558ab-a80f-49af-8b7d-bb635b0f038f) ### Name The example is called `window_drag_move`. Happy to have that bikeshedded. ### Contentious Refactor? This PR removed the `ResizeDirection` enumeration in favor of using `CompassOctant` which had the same variants. Perhaps this is contentious. ### Unsafe? In PR #15674 I mentioned that `start_drag_move()` and `start_drag_resize()`'s requirement to only be called in the presence of a left-click looks like a compiler-unenforceable contract that can cause intermittent panics when not observed, so perhaps the functions should be marked them unsafe. **I have not made that change** here since I didn't see a clear consensus on that. ## Testing I exercised this on x86 macOS. However, winit for macOS does not support drag resize. It reports a good error when `start_drag_resize()` is called. I'd like to see it tested on Windows and Linux. --- ## Showcase Example window_drag_move shows how to drag or resize a window without decoration. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
a09104b62c
commit
5157fef84b
5 changed files with 172 additions and 43 deletions
11
Cargo.toml
11
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"
|
||||
|
|
|
@ -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<ResizeDirection>,
|
||||
drag_resize_request: Option<CompassOctant>,
|
||||
/// Unscaled cursor position.
|
||||
physical_cursor_position: Option<DVec2>,
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
145
examples/window/window_drag_move.rs
Normal file
145
examples/window/window_drag_move.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue