use bevy::{
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
render::camera::OrthographicProjection,
};
#[derive(Default)]
pub struct PanCamPlugin;
impl Plugin for PanCamPlugin {
fn build(&self, app: &mut App) {
app.add_system(camera_movement).add_system(camera_zoom);
}
}
fn camera_zoom(
mut query: Query<(&PanCam, &mut OrthographicProjection, &mut Transform)>,
mut scroll_events: EventReader<MouseWheel>,
windows: Res<Windows>,
#[cfg(feature = "bevy_egui")] egui_ctx: Option<ResMut<bevy_egui::EguiContext>>,
) {
#[cfg(feature = "bevy_egui")]
if let Some(mut egui_ctx) = egui_ctx {
if egui_ctx.ctx_mut().wants_pointer_input() || egui_ctx.ctx_mut().wants_keyboard_input() {
return;
}
}
let pixels_per_line = 100.; let scroll = scroll_events
.iter()
.map(|ev| match ev.unit {
MouseScrollUnit::Pixel => ev.y,
MouseScrollUnit::Line => ev.y * pixels_per_line,
})
.sum::<f32>();
if scroll == 0. {
return;
}
let window = windows.get_primary().unwrap();
let window_size = Vec2::new(window.width(), window.height());
let mouse_normalized_screen_pos = window
.cursor_position()
.map(|cursor_pos| (cursor_pos / window_size) * 2. - Vec2::ONE);
for (cam, mut proj, mut pos) in &mut query {
if cam.enabled {
let old_scale = proj.scale;
proj.scale = (proj.scale * (1. + -scroll * 0.001)).max(cam.min_scale);
if let Some(max_scale) = cam.max_scale {
proj.scale = proj.scale.min(max_scale);
}
if let (Some(mouse_normalized_screen_pos), true) =
(mouse_normalized_screen_pos, cam.zoom_to_cursor)
{
let proj_size = Vec2::new(proj.right, proj.top);
let mouse_world_pos = pos.translation.truncate()
+ mouse_normalized_screen_pos * proj_size * old_scale;
pos.translation = (mouse_world_pos
- mouse_normalized_screen_pos * proj_size * proj.scale)
.extend(pos.translation.z);
}
}
}
}
fn camera_movement(
windows: Res<Windows>,
mouse_buttons: Res<Input<MouseButton>>,
mut query: Query<(&PanCam, &mut Transform, &OrthographicProjection)>,
mut last_pos: Local<Option<Vec2>>,
#[cfg(feature = "bevy_egui")] egui_ctx: Option<ResMut<bevy_egui::EguiContext>>,
) {
#[cfg(feature = "bevy_egui")]
if let Some(mut egui_ctx) = egui_ctx {
if egui_ctx.ctx_mut().wants_pointer_input() || egui_ctx.ctx_mut().wants_keyboard_input() {
*last_pos = None;
return;
}
}
let window = windows.get_primary().unwrap();
let current_pos = match window.cursor_position() {
Some(current_pos) => current_pos,
None => return,
};
let delta = current_pos - last_pos.unwrap_or(current_pos);
for (cam, mut transform, projection) in &mut query {
if cam.enabled
&& cam
.grab_buttons
.iter()
.any(|btn| mouse_buttons.pressed(*btn))
{
let scaling = Vec2::new(
window.width() / (projection.right - projection.left),
window.height() / (projection.top - projection.bottom),
) * projection.scale;
transform.translation -= (delta * scaling).extend(0.);
}
}
*last_pos = Some(current_pos);
}
#[derive(Component)]
pub struct PanCam {
pub grab_buttons: Vec<MouseButton>,
pub enabled: bool,
pub zoom_to_cursor: bool,
pub min_scale: f32,
pub max_scale: Option<f32>,
}
impl Default for PanCam {
fn default() -> Self {
Self {
grab_buttons: vec![MouseButton::Left, MouseButton::Right, MouseButton::Middle],
enabled: true,
zoom_to_cursor: true,
min_scale: 0.00001,
max_scale: None,
}
}
}