Skip to content

Commit 0d68d94

Browse files
Sigma-devkfc35alice-i-cecile
authored
Pan camera mouse pan (#22859)
# Objective The current PanCamera only supports keyboard panning, when a common use case is mouse panning. ## Solution Add optional mouse panning with some settings ## Testing I ran the Pan Camera example with the new settings ## Additional notes I added settings for setting the exact mouse button that triggers the panning, and whether or not to grab the cursor when panning. I am unsure about the cursor grab part of this design as it may mess with the app's existing cursor options and the window part may not work for some setups. It may be wiser to just omit grabbing for now, or maybe keep grabbing but don't change visibility idk As always I am open to any and all feedback. --------- Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent ec254de commit 0d68d94

3 files changed

Lines changed: 111 additions & 4 deletions

File tree

crates/bevy_camera_controller/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev", default-featu
2222
bevy_time = { path = "../bevy_time", version = "0.19.0-dev", default-features = false }
2323
bevy_transform = { path = "../bevy_transform", version = "0.19.0-dev", default-features = false }
2424
bevy_window = { path = "../bevy_window", version = "0.19.0-dev", default-features = false }
25+
bevy_picking = { path = "../bevy_picking", version = "0.19.0-dev", default-features = false }
2526

2627
[features]
2728
default = ["bevy_reflect"]

crates/bevy_camera_controller/src/pan_camera.rs

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
//! To configure the settings of this controller, modify the fields of the [`PanCamera`] component.
77
88
use bevy_app::{App, Plugin, RunFixedMainLoop, RunFixedMainLoopSystems};
9-
use bevy_camera::Camera;
9+
use bevy_camera::{Camera, RenderTarget};
1010
use bevy_ecs::prelude::*;
1111
use bevy_input::keyboard::KeyCode;
12-
use bevy_input::mouse::{AccumulatedMouseScroll, MouseScrollUnit};
12+
use bevy_input::mouse::{AccumulatedMouseScroll, MouseButton, MouseScrollUnit};
1313
use bevy_input::ButtonInput;
1414
use bevy_math::{Vec2, Vec3};
15+
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer};
1516
use bevy_time::{Real, Time};
17+
use bevy_transform::components::GlobalTransform;
1618
use bevy_transform::prelude::Transform;
17-
19+
use bevy_window::{PrimaryWindow, WindowRef};
1820
use core::{f32::consts::*, fmt};
1921

2022
/// A plugin that enables 2D camera panning and zooming controls.
@@ -28,7 +30,9 @@ impl Plugin for PanCameraPlugin {
2830
app.add_systems(
2931
RunFixedMainLoop,
3032
run_pancamera_controller.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop),
31-
);
33+
)
34+
.add_observer(add_window_observer)
35+
.add_observer(remove_window_observer);
3236
}
3337
}
3438

@@ -68,6 +72,16 @@ pub struct PanCamera {
6872
pub key_rotate_ccw: Option<KeyCode>,
6973
/// [`KeyCode`] for clockwise rotation.
7074
pub key_rotate_cw: Option<KeyCode>,
75+
/// Mouse pan settings.
76+
pub mouse_pan_settings: MousePanSettings,
77+
}
78+
79+
/// Settings for mouse panning for the [`PanCamera`] controller.
80+
pub struct MousePanSettings {
81+
/// Whether the mouse panning is enabled.
82+
pub enabled: bool,
83+
/// The mouse button to use for panning.
84+
pub button: MouseButton,
7185
}
7286

7387
/// Provides the default values for the `PanCamera` controller.
@@ -105,6 +119,10 @@ impl Default for PanCamera {
105119
rotation_speed: PI,
106120
key_rotate_ccw: Some(KeyCode::KeyQ),
107121
key_rotate_cw: Some(KeyCode::KeyE),
122+
mouse_pan_settings: MousePanSettings {
123+
enabled: true,
124+
button: MouseButton::Left,
125+
},
108126
}
109127
}
110128
}
@@ -235,3 +253,83 @@ fn run_pancamera_controller(
235253

236254
transform.scale = Vec3::splat(controller.zoom_factor);
237255
}
256+
257+
/// A component attached to window entities that holds the id of an
258+
/// active `handle_mouse_pan` observer. It is None if there is no
259+
/// such observer.
260+
#[derive(Component)]
261+
struct HandleMousePanObserver(Option<Entity>);
262+
263+
fn add_window_observer(
264+
drag_start: On<Pointer<DragStart>>,
265+
mut commands: Commands,
266+
render_targets: Query<&RenderTarget, With<PanCamera>>,
267+
primary_window: Single<Entity, With<PrimaryWindow>>,
268+
) {
269+
for render_target in render_targets.iter() {
270+
if let RenderTarget::Window(window) = render_target {
271+
let entity = match window {
272+
WindowRef::Primary => primary_window.entity(),
273+
WindowRef::Entity(entity) => *entity,
274+
};
275+
if entity == drag_start.entity {
276+
let observer_id = commands
277+
.spawn(Observer::new(handle_mouse_pan).with_entity(entity))
278+
.id();
279+
commands
280+
.entity(entity)
281+
.insert(HandleMousePanObserver(Some(observer_id)));
282+
}
283+
}
284+
}
285+
}
286+
287+
fn remove_window_observer(
288+
drag_end: On<Pointer<DragEnd>>,
289+
mut commands: Commands,
290+
render_targets: Query<&RenderTarget, With<PanCamera>>,
291+
mut handle_mouse_pan_observer: Query<&mut HandleMousePanObserver>,
292+
primary_window: Single<Entity, With<PrimaryWindow>>,
293+
) {
294+
for render_target in render_targets.iter() {
295+
if let RenderTarget::Window(window) = render_target {
296+
let entity = match window {
297+
WindowRef::Primary => primary_window.entity(),
298+
WindowRef::Entity(entity) => *entity,
299+
};
300+
if entity == drag_end.entity
301+
&& let Ok(mut observer) = handle_mouse_pan_observer.get_mut(entity)
302+
&& let Some(observer_entity) = observer.0.take()
303+
{
304+
commands.entity(observer_entity).despawn();
305+
}
306+
}
307+
}
308+
}
309+
310+
fn handle_mouse_pan(
311+
drag: On<Pointer<Drag>>,
312+
mut pan_cameras: Query<(&Camera, &GlobalTransform, &mut Transform, &PanCamera)>,
313+
) {
314+
for (camera, global_transform, mut transform, pan_camera_controller) in pan_cameras.iter_mut() {
315+
if !pan_camera_controller.enabled || !pan_camera_controller.mouse_pan_settings.enabled {
316+
return;
317+
}
318+
319+
let Ok(camera_screen_position) =
320+
camera.world_to_viewport(global_transform, transform.translation)
321+
else {
322+
continue;
323+
};
324+
325+
let offset_camera_screen_position = camera_screen_position + drag.delta * -1.; // inverted feels more natural
326+
327+
let Ok(new_camera_position) =
328+
camera.viewport_to_world_2d(global_transform, offset_camera_screen_position)
329+
else {
330+
continue;
331+
};
332+
333+
transform.translation = new_camera_position.extend(transform.translation.z);
334+
}
335+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: "Added mouse panning to `PanCamera` component"
3+
pull_requests: [22859]
4+
---
5+
6+
By default `PanCamera` now includes mouse panning.
7+
8+
To go back to the keyboard only panning, you need to set the `enabled` field of the `PanCamera`'s mouse_pan_settings field to false.

0 commit comments

Comments
 (0)