Skip to content

Commit 5157fef

Browse files
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 <[email protected]>
1 parent a09104b commit 5157fef

File tree

5 files changed

+172
-43
lines changed

5 files changed

+172
-43
lines changed

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -3286,6 +3286,17 @@ description = "Demonstrates customizing default window settings"
32863286
category = "Window"
32873287
wasm = true
32883288

3289+
[[example]]
3290+
name = "window_drag_move"
3291+
path = "examples/window/window_drag_move.rs"
3292+
doc-scrape-examples = true
3293+
3294+
[package.metadata.example.window_drag_move]
3295+
name = "Window Drag Move"
3296+
description = "Demonstrates drag move and drag resize without window decoration"
3297+
category = "Window"
3298+
wasm = false
3299+
32893300
[[example]]
32903301
name = "ambiguity_detection"
32913302
path = "tests/ecs/ambiguity_detection.rs"

crates/bevy_window/src/window.rs

+4-30
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bevy_ecs::{
44
entity::{Entity, VisitEntities, VisitEntitiesMut},
55
prelude::{Component, ReflectComponent},
66
};
7-
use bevy_math::{DVec2, IVec2, UVec2, Vec2};
7+
use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2};
88
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
99

1010
#[cfg(feature = "serialize")]
@@ -460,7 +460,7 @@ impl Window {
460460
///
461461
/// There is no guarantee that this will work unless the left mouse button was
462462
/// pressed immediately before this function was called.
463-
pub fn start_drag_resize(&mut self, direction: ResizeDirection) {
463+
pub fn start_drag_resize(&mut self, direction: CompassOctant) {
464464
self.internal.drag_resize_request = Some(direction);
465465
}
466466

@@ -994,32 +994,6 @@ pub enum CursorGrabMode {
994994
Locked,
995995
}
996996

997-
/// Defines the orientation in which a window resize will be performed.
998-
#[derive(Debug, Copy, Clone, PartialEq, Eq, Reflect)]
999-
#[cfg_attr(
1000-
feature = "serialize",
1001-
derive(serde::Serialize, serde::Deserialize),
1002-
reflect(Serialize, Deserialize)
1003-
)]
1004-
pub enum ResizeDirection {
1005-
/// Resize the window to the west.
1006-
West,
1007-
/// Resize the window to the north.
1008-
North,
1009-
/// Resize the window to the east.
1010-
East,
1011-
/// Resize the window to the south.
1012-
South,
1013-
/// Resize the window to the northwest.
1014-
Northwest,
1015-
/// Resize the window to the northeast.
1016-
Northeast,
1017-
/// Resize the window to the southwest.
1018-
Southwest,
1019-
/// Resize the window to the southeast.
1020-
Southeast,
1021-
}
1022-
1023997
/// Stores internal [`Window`] state that isn't directly accessible.
1024998
#[derive(Default, Debug, Copy, Clone, PartialEq, Reflect)]
1025999
#[cfg_attr(
@@ -1036,7 +1010,7 @@ pub struct InternalWindowState {
10361010
/// If this is true then next frame we will ask to drag-move the window.
10371011
drag_move_request: bool,
10381012
/// If this is `Some` then the next frame we will ask to drag-resize the window.
1039-
drag_resize_request: Option<ResizeDirection>,
1013+
drag_resize_request: Option<CompassOctant>,
10401014
/// Unscaled cursor position.
10411015
physical_cursor_position: Option<DVec2>,
10421016
}
@@ -1058,7 +1032,7 @@ impl InternalWindowState {
10581032
}
10591033

10601034
/// Consumes the current resize request, if it exists. This should only be called by window backends.
1061-
pub fn take_resize_request(&mut self) -> Option<ResizeDirection> {
1035+
pub fn take_resize_request(&mut self) -> Option<CompassOctant> {
10621036
self.drag_resize_request.take()
10631037
}
10641038
}

crates/bevy_winit/src/converters.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use bevy_input::{
55
touch::{ForceTouch, TouchInput, TouchPhase},
66
ButtonState,
77
};
8-
use bevy_math::Vec2;
8+
use bevy_math::{CompassOctant, Vec2};
99
#[cfg(feature = "custom_cursor")]
1010
use bevy_window::SystemCursorIcon;
11-
use bevy_window::{EnabledButtons, ResizeDirection, WindowLevel, WindowTheme};
11+
use bevy_window::{EnabledButtons, WindowLevel, WindowTheme};
1212
use winit::keyboard::{Key, NamedKey, NativeKey};
1313

1414
pub fn convert_keyboard_input(
@@ -707,17 +707,15 @@ pub fn convert_enabled_buttons(enabled_buttons: EnabledButtons) -> winit::window
707707
window_buttons
708708
}
709709

710-
pub fn convert_resize_direction(
711-
resize_direction: ResizeDirection,
712-
) -> winit::window::ResizeDirection {
710+
pub fn convert_resize_direction(resize_direction: CompassOctant) -> winit::window::ResizeDirection {
713711
match resize_direction {
714-
ResizeDirection::West => winit::window::ResizeDirection::West,
715-
ResizeDirection::North => winit::window::ResizeDirection::North,
716-
ResizeDirection::East => winit::window::ResizeDirection::East,
717-
ResizeDirection::South => winit::window::ResizeDirection::South,
718-
ResizeDirection::Northwest => winit::window::ResizeDirection::NorthWest,
719-
ResizeDirection::Northeast => winit::window::ResizeDirection::NorthEast,
720-
ResizeDirection::Southwest => winit::window::ResizeDirection::SouthWest,
721-
ResizeDirection::Southeast => winit::window::ResizeDirection::SouthEast,
712+
CompassOctant::West => winit::window::ResizeDirection::West,
713+
CompassOctant::North => winit::window::ResizeDirection::North,
714+
CompassOctant::East => winit::window::ResizeDirection::East,
715+
CompassOctant::South => winit::window::ResizeDirection::South,
716+
CompassOctant::NorthWest => winit::window::ResizeDirection::NorthWest,
717+
CompassOctant::NorthEast => winit::window::ResizeDirection::NorthEast,
718+
CompassOctant::SouthWest => winit::window::ResizeDirection::SouthWest,
719+
CompassOctant::SouthEast => winit::window::ResizeDirection::SouthEast,
722720
}
723721
}

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ Example | Description
538538
[Scale Factor Override](../examples/window/scale_factor_override.rs) | Illustrates how to customize the default window settings
539539
[Screenshot](../examples/window/screenshot.rs) | Shows how to save screenshots to disk
540540
[Transparent Window](../examples/window/transparent_window.rs) | Illustrates making the window transparent and hiding the window decoration
541+
[Window Drag Move](../examples/window/window_drag_move.rs) | Demonstrates drag move and drag resize without window decoration
541542
[Window Resizing](../examples/window/window_resizing.rs) | Demonstrates resizing and responding to resizing a window
542543
[Window Settings](../examples/window/window_settings.rs) | Demonstrates customizing default window settings
543544

examples/window/window_drag_move.rs

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
//! This example illustrates drag move and drag resize without window
2+
//! decorations.
3+
//!
4+
//! When window decorations are not present, the user cannot drag a window by
5+
//! its titlebar to change its position. The `start_drag_move()` function
6+
//! permits a users to drag a window by left clicking anywhere in the window;
7+
//! left click must be pressed and other constraints can be imposed. For
8+
//! instance an application could require a user to hold down alt and left click
9+
//! to drag a window.
10+
//!
11+
//! The `start_drag_resize()` function behaves similarly but permits a window to
12+
//! be resized.
13+
use bevy::{math::CompassOctant, prelude::*};
14+
15+
/// Determine what do on left click.
16+
#[derive(Resource, Debug)]
17+
enum LeftClickAction {
18+
/// Do nothing.
19+
Nothing,
20+
/// Move the window on left click.
21+
Move,
22+
/// Resize the window on left click.
23+
Resize,
24+
}
25+
26+
/// What direction index should the window resize toward.
27+
#[derive(Resource)]
28+
struct ResizeDir(usize);
29+
30+
/// Directions that the drag resizes the window toward.
31+
const DIRECTIONS: [CompassOctant; 8] = [
32+
CompassOctant::North,
33+
CompassOctant::NorthEast,
34+
CompassOctant::East,
35+
CompassOctant::SouthEast,
36+
CompassOctant::South,
37+
CompassOctant::SouthWest,
38+
CompassOctant::West,
39+
CompassOctant::NorthWest,
40+
];
41+
42+
fn main() {
43+
App::new()
44+
.add_plugins(DefaultPlugins.set(WindowPlugin {
45+
primary_window: Some(Window {
46+
decorations: false,
47+
..default()
48+
}),
49+
..default()
50+
}))
51+
.insert_resource(ResizeDir(7))
52+
.insert_resource(LeftClickAction::Move)
53+
.add_systems(Startup, setup)
54+
.add_systems(Update, (handle_input, move_or_resize_windows))
55+
.run();
56+
}
57+
58+
fn setup(mut commands: Commands) {
59+
// Camera
60+
commands.spawn(Camera3d::default());
61+
62+
// UI
63+
commands
64+
.spawn((
65+
NodeBundle {
66+
style: Style {
67+
position_type: PositionType::Absolute,
68+
padding: UiRect::all(Val::Px(5.0)),
69+
..default()
70+
},
71+
background_color: Color::BLACK.with_alpha(0.75).into(),
72+
..default()
73+
},
74+
GlobalZIndex(i32::MAX),
75+
))
76+
.with_children(|p| {
77+
p.spawn(Text::default()).with_children(|p| {
78+
p.spawn(TextSpan::new(
79+
"Demonstrate drag move and drag resize without window decorations.\n\n",
80+
));
81+
p.spawn(TextSpan::new("Controls:\n"));
82+
p.spawn(TextSpan::new("A - change left click action ["));
83+
p.spawn(TextSpan::new("Move"));
84+
p.spawn(TextSpan::new("]\n"));
85+
p.spawn(TextSpan::new("S / D - change resize direction ["));
86+
p.spawn(TextSpan::new("NorthWest"));
87+
p.spawn(TextSpan::new("]\n"));
88+
});
89+
});
90+
}
91+
92+
fn handle_input(
93+
input: Res<ButtonInput<KeyCode>>,
94+
mut action: ResMut<LeftClickAction>,
95+
mut dir: ResMut<ResizeDir>,
96+
example_text: Query<Entity, With<Text>>,
97+
mut writer: TextUiWriter,
98+
) {
99+
use LeftClickAction::*;
100+
if input.just_pressed(KeyCode::KeyA) {
101+
*action = match *action {
102+
Move => Resize,
103+
Resize => Nothing,
104+
Nothing => Move,
105+
};
106+
*writer.text(example_text.single(), 4) = format!("{:?}", *action);
107+
}
108+
109+
if input.just_pressed(KeyCode::KeyS) {
110+
dir.0 = dir
111+
.0
112+
.checked_sub(1)
113+
.unwrap_or(DIRECTIONS.len().saturating_sub(1));
114+
*writer.text(example_text.single(), 7) = format!("{:?}", DIRECTIONS[dir.0]);
115+
}
116+
117+
if input.just_pressed(KeyCode::KeyD) {
118+
dir.0 = (dir.0 + 1) % DIRECTIONS.len();
119+
*writer.text(example_text.single(), 7) = format!("{:?}", DIRECTIONS[dir.0]);
120+
}
121+
}
122+
123+
fn move_or_resize_windows(
124+
mut windows: Query<&mut Window>,
125+
action: Res<LeftClickAction>,
126+
input: Res<ButtonInput<MouseButton>>,
127+
dir: Res<ResizeDir>,
128+
) {
129+
// Both `start_drag_move()` and `start_drag_resize()` must be called after a
130+
// left mouse button press as done here.
131+
//
132+
// winit 0.30.5 may panic when initiated without a left mouse button press.
133+
if input.just_pressed(MouseButton::Left) {
134+
for mut window in windows.iter_mut() {
135+
match *action {
136+
LeftClickAction::Nothing => (),
137+
LeftClickAction::Move => window.start_drag_move(),
138+
LeftClickAction::Resize => {
139+
let d = DIRECTIONS[dir.0];
140+
window.start_drag_resize(d);
141+
}
142+
}
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)