use bevy::camera::visibility::RenderLayers; use bevy::input::mouse::{AccumulatedMouseMotion, MouseWheel}; use bevy::prelude::*; use bevy::window::{CursorOptions, PrimaryWindow}; use bevy_egui::{EguiContexts, EguiGlobalSettings, PrimaryEguiContext}; use transform_gizmo_bevy::prelude::GizmoCamera; use crate::infra::EditorOnly; use crate::state::scene_tools_active; use crate::ui::{egui_captures_keyboard, UiState}; use settings::ProjectSettings; #[derive(Component)] pub struct EditorCamera { pub fly_speed: f32, pub look_sensitivity: f32, pub pan_speed: f32, } impl Default for EditorCamera { fn default() -> Self { Self { fly_speed: 14.0, look_sensitivity: 0.003, pan_speed: 0.015, } } } pub struct EditorCameraPlugin; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CameraNavigationMode { Look, Pan, } #[derive(Resource, Default)] struct EditorCameraInputState { mode: Option, cursor_hidden_for_look: bool, cursor_visible_before_look: bool, } impl Plugin for EditorCameraPlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_systems(Startup, spawn_editor_camera) .add_systems( Update, (sync_editor_camera_from_settings, update_editor_camera) .chain() .run_if(scene_tools_active), ); } } fn sync_editor_camera_from_settings( settings: Res, mut cameras: Query<&mut EditorCamera>, ) { if !settings.is_changed() { return; } let input = &settings.input; for mut camera in &mut cameras { camera.fly_speed = input.editor_fly_speed; camera.look_sensitivity = input.editor_look_sensitivity; camera.pan_speed = input.editor_pan_speed; } } fn spawn_editor_camera( mut commands: Commands, mut egui_global_settings: ResMut, ) { // Own the egui primary context explicitly. If bevy_egui auto-attaches it to // a 3D camera, `set_camera_viewport` then crops egui's own screen rect to the // shrinking viewport region, and egui_dock eventually lays out into a NaN // rect (panic: "rect is nan in advance_cursor_after_rect"). egui_global_settings.auto_create_primary_context = false; commands.spawn(( Name::new("Editor Camera"), EditorOnly, EditorCamera::default(), RenderLayers::default(), GizmoCamera, Camera3d::default(), Camera { clear_color: ClearColorConfig::Custom(Color::srgba(0.0, 0.0, 0.0, 1.0)), ..default() }, Transform::from_xyz(10.0, 8.0, 18.0).looking_at(Vec3::ZERO, Vec3::Y), )); // Dedicated full-window 2D camera that renders only egui on top of the 3D // scene. Keeping the egui context off the 3D cameras avoids the // viewport-cropping feedback loop described above. commands.spawn(( Name::new("Editor Egui Camera"), EditorOnly, Camera2d, PrimaryEguiContext, RenderLayers::none(), Camera { order: 10, clear_color: ClearColorConfig::None, ..default() }, )); } #[allow(clippy::too_many_arguments)] fn update_editor_camera( time: Res