From 969407d710291454bef576c9e04237cc6596bab9 Mon Sep 17 00:00:00 2001 From: mbremner Date: Sun, 2 Aug 2026 23:32:07 -0700 Subject: [PATCH 1/5] Added a ZoomTarget enum to the PanCamera struct, with two variants: Center and Cursor. Center is set as default and does not change the behavior of PanCamera. Cursor causes the zoom to be focused on the position of the cursor if the cursor is over the window, or the center of the window if the cursor is not over the window. --- .../bevy_camera_controller/src/pan_camera.rs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/bevy_camera_controller/src/pan_camera.rs b/crates/bevy_camera_controller/src/pan_camera.rs index 8d62e0ac48c88..f91da24d3373e 100644 --- a/crates/bevy_camera_controller/src/pan_camera.rs +++ b/crates/bevy_camera_controller/src/pan_camera.rs @@ -19,7 +19,7 @@ use bevy_picking::{ use bevy_time::{Real, Time}; use bevy_transform::components::GlobalTransform; use bevy_transform::prelude::Transform; -use bevy_window::{PrimaryWindow, WindowRef}; +use bevy_window::{PrimaryWindow, Window, WindowRef}; use core::{f32::consts::*, fmt}; /// A plugin that enables 2D camera panning and zooming controls. @@ -55,6 +55,8 @@ pub struct PanCamera { pub max_zoom: f32, /// Translation speed for panning movement. pub zoom_speed: f32, + /// The focal point for zooming + pub zoom_target: ZoomTarget, /// [`KeyCode`] to zoom in. pub key_zoom_in: Option, /// [`KeyCode`] to zoom out. @@ -87,6 +89,14 @@ pub struct MousePanSettings { pub button: PointerButton, } +/// Target focal point for zooming using the ['PanCamera'] controller +pub enum ZoomTarget{ + /// Zoom to / from the center of the window + Center, + /// Zoom to / from the position of the cursor if the cursor is over the window + Cursor, +} + /// Provides the default values for the `PanCamera` controller. /// /// The default settings are: @@ -112,6 +122,7 @@ impl Default for PanCamera { min_zoom: 0.1, max_zoom: 5.0, zoom_speed: 0.1, + zoom_target: ZoomTarget::Center, key_zoom_in: Some(KeyCode::Equal), key_zoom_out: Some(KeyCode::Minus), pan_speed: 500.0, @@ -171,11 +182,12 @@ fn run_pancamera_controller( key_input: Res>, accumulated_mouse_scroll: Res, mouse_scroll_conversion: Res, - mut query: Query<(&mut Transform, &mut PanCamera), With>, + window: Single<&Window>, + mut query: Query<(&Camera, &GlobalTransform, &mut Transform, &mut PanCamera)> ) { let dt = time.delta_secs(); - let Ok((mut transform, mut controller)) = query.single_mut() else { + let Ok((camera, global, mut transform, mut controller)) = query.single_mut() else { return; }; @@ -250,10 +262,24 @@ fn run_pancamera_controller( .y; zoom_amount += mouse_scroll * controller.zoom_speed; + let prev_zoom = controller.zoom_factor; + controller.zoom_factor = (controller.zoom_factor - zoom_amount).clamp(controller.min_zoom, controller.max_zoom); + // If Zoom target is Cursor, then also translate the camera so that the world coordinates of + // the cursor maintain the same viewport coordinates. + if let ZoomTarget::Cursor = controller.zoom_target { + if let Some(cursor_pos) = window.cursor_position() + && let Ok(world_pos) = camera.viewport_to_world_2d(global, cursor_pos) { + let cursor_vec = world_pos - transform.translation.truncate() ; + let delta_cursor_vec = (1. - controller.zoom_factor / prev_zoom ) * cursor_vec; + transform.translation += delta_cursor_vec.extend(0.) + } + } + transform.scale = Vec3::splat(controller.zoom_factor); + } /// A component attached to window entities that holds the id of an From f6625d87b67b6ef79547baa19232405d3eb2a9a4 Mon Sep 17 00:00:00 2001 From: mbremner Date: Mon, 3 Aug 2026 12:12:55 -0700 Subject: [PATCH 2/5] Fixed some format issues identified by the CI job check --- crates/bevy_camera_controller/src/pan_camera.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bevy_camera_controller/src/pan_camera.rs b/crates/bevy_camera_controller/src/pan_camera.rs index f91da24d3373e..b1699a398e0e3 100644 --- a/crates/bevy_camera_controller/src/pan_camera.rs +++ b/crates/bevy_camera_controller/src/pan_camera.rs @@ -90,7 +90,7 @@ pub struct MousePanSettings { } /// Target focal point for zooming using the ['PanCamera'] controller -pub enum ZoomTarget{ +pub enum ZoomTarget { /// Zoom to / from the center of the window Center, /// Zoom to / from the position of the cursor if the cursor is over the window @@ -183,7 +183,7 @@ fn run_pancamera_controller( accumulated_mouse_scroll: Res, mouse_scroll_conversion: Res, window: Single<&Window>, - mut query: Query<(&Camera, &GlobalTransform, &mut Transform, &mut PanCamera)> + mut query: Query<(&Camera, &GlobalTransform, &mut Transform, &mut PanCamera)>, ) { let dt = time.delta_secs(); @@ -271,7 +271,8 @@ fn run_pancamera_controller( // the cursor maintain the same viewport coordinates. if let ZoomTarget::Cursor = controller.zoom_target { if let Some(cursor_pos) = window.cursor_position() - && let Ok(world_pos) = camera.viewport_to_world_2d(global, cursor_pos) { + && let Ok(world_pos) = camera.viewport_to_world_2d(global, cursor_pos) + { let cursor_vec = world_pos - transform.translation.truncate() ; let delta_cursor_vec = (1. - controller.zoom_factor / prev_zoom ) * cursor_vec; transform.translation += delta_cursor_vec.extend(0.) From 2890a592710d24c0eb05e8fe01b1760db3d30444 Mon Sep 17 00:00:00 2001 From: mbremner Date: Mon, 3 Aug 2026 12:15:19 -0700 Subject: [PATCH 3/5] Fixed some format issues identified by the CI job check --- crates/bevy_camera_controller/src/pan_camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_camera_controller/src/pan_camera.rs b/crates/bevy_camera_controller/src/pan_camera.rs index b1699a398e0e3..394907847f862 100644 --- a/crates/bevy_camera_controller/src/pan_camera.rs +++ b/crates/bevy_camera_controller/src/pan_camera.rs @@ -273,7 +273,7 @@ fn run_pancamera_controller( if let Some(cursor_pos) = window.cursor_position() && let Ok(world_pos) = camera.viewport_to_world_2d(global, cursor_pos) { - let cursor_vec = world_pos - transform.translation.truncate() ; + let cursor_vec = world_pos - transform.translation.truncate(); let delta_cursor_vec = (1. - controller.zoom_factor / prev_zoom ) * cursor_vec; transform.translation += delta_cursor_vec.extend(0.) } From fbffd14ffa28ad435f7f0aa39f88b690dff1b626 Mon Sep 17 00:00:00 2001 From: mbremner Date: Mon, 3 Aug 2026 12:18:07 -0700 Subject: [PATCH 4/5] Fixed some format issues identified by the CI job check --- crates/bevy_camera_controller/src/pan_camera.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_camera_controller/src/pan_camera.rs b/crates/bevy_camera_controller/src/pan_camera.rs index 394907847f862..ae4ec35821745 100644 --- a/crates/bevy_camera_controller/src/pan_camera.rs +++ b/crates/bevy_camera_controller/src/pan_camera.rs @@ -274,13 +274,12 @@ fn run_pancamera_controller( && let Ok(world_pos) = camera.viewport_to_world_2d(global, cursor_pos) { let cursor_vec = world_pos - transform.translation.truncate(); - let delta_cursor_vec = (1. - controller.zoom_factor / prev_zoom ) * cursor_vec; + let delta_cursor_vec = (1. - controller.zoom_factor / prev_zoom) * cursor_vec; transform.translation += delta_cursor_vec.extend(0.) } } transform.scale = Vec3::splat(controller.zoom_factor); - } /// A component attached to window entities that holds the id of an From ce3c29008b42e1586cb027e1c0ef0e08746968d0 Mon Sep 17 00:00:00 2001 From: mbremner Date: Mon, 3 Aug 2026 12:29:07 -0700 Subject: [PATCH 5/5] Fixed some format issues identified by the CI job check --- crates/bevy_camera_controller/src/pan_camera.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_camera_controller/src/pan_camera.rs b/crates/bevy_camera_controller/src/pan_camera.rs index ae4ec35821745..ac121bb502ed4 100644 --- a/crates/bevy_camera_controller/src/pan_camera.rs +++ b/crates/bevy_camera_controller/src/pan_camera.rs @@ -89,7 +89,7 @@ pub struct MousePanSettings { pub button: PointerButton, } -/// Target focal point for zooming using the ['PanCamera'] controller +/// Target focal point for zooming using the [`PanCamera`] controller pub enum ZoomTarget { /// Zoom to / from the center of the window Center, @@ -275,7 +275,7 @@ fn run_pancamera_controller( { let cursor_vec = world_pos - transform.translation.truncate(); let delta_cursor_vec = (1. - controller.zoom_factor / prev_zoom) * cursor_vec; - transform.translation += delta_cursor_vec.extend(0.) + transform.translation += delta_cursor_vec.extend(0.); } }