66//! To configure the settings of this controller, modify the fields of the [`PanCamera`] component.
77
88use bevy_app:: { App , Plugin , RunFixedMainLoop , RunFixedMainLoopSystems } ;
9- use bevy_camera:: Camera ;
9+ use bevy_camera:: { Camera , RenderTarget } ;
1010use bevy_ecs:: prelude:: * ;
1111use bevy_input:: keyboard:: KeyCode ;
12- use bevy_input:: mouse:: { AccumulatedMouseScroll , MouseScrollUnit } ;
12+ use bevy_input:: mouse:: { AccumulatedMouseScroll , MouseButton , MouseScrollUnit } ;
1313use bevy_input:: ButtonInput ;
1414use bevy_math:: { Vec2 , Vec3 } ;
15+ use bevy_picking:: events:: { Drag , DragEnd , DragStart , Pointer } ;
1516use bevy_time:: { Real , Time } ;
17+ use bevy_transform:: components:: GlobalTransform ;
1618use bevy_transform:: prelude:: Transform ;
17-
19+ use bevy_window :: { PrimaryWindow , WindowRef } ;
1820use 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+ }
0 commit comments