use bevy::{prelude::*, render::camera::Projection, window::PrimaryWindow}; use bevy_egui_next::{egui, EguiContexts, EguiPlugin}; #[derive(Default, Resource)] struct OccupiedScreenSpace { left: f32, top: f32, right: f32, bottom: f32, } const CAMERA_TARGET: Vec3 = Vec3::ZERO; #[derive(Resource, Deref, DerefMut)] struct OriginalCameraTransform(Transform); fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(EguiPlugin) .init_resource::() .add_systems(Startup, setup_system) .add_systems(Update, ui_example_system) .add_systems(Update, update_camera_transform_system) .run(); } fn ui_example_system( mut contexts: EguiContexts, mut occupied_screen_space: ResMut, ) { let ctx = contexts.ctx_mut(); occupied_screen_space.left = egui::SidePanel::left("left_panel") .resizable(true) .show(ctx, |ui| { ui.label("Left resizeable panel"); ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response .rect .width(); occupied_screen_space.right = egui::SidePanel::right("right_panel") .resizable(true) .show(ctx, |ui| { ui.label("Right resizeable panel"); ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response .rect .width(); occupied_screen_space.top = egui::TopBottomPanel::top("top_panel") .resizable(true) .show(ctx, |ui| { ui.label("Top resizeable panel"); ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response .rect .height(); occupied_screen_space.bottom = egui::TopBottomPanel::bottom("bottom_panel") .resizable(true) .show(ctx, |ui| { ui.label("Bottom resizeable panel"); ui.allocate_rect(ui.available_rect_before_wrap(), egui::Sense::hover()); }) .response .rect .height(); } fn setup_system( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { commands.spawn(PbrBundle { mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0, subdivisions: 0, })), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..Default::default() }); commands.spawn(PbrBundle { mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..Default::default() }); commands.spawn(PointLightBundle { point_light: PointLight { intensity: 1500.0, shadows_enabled: true, ..Default::default() }, transform: Transform::from_xyz(4.0, 8.0, 4.0), ..Default::default() }); let camera_pos = Vec3::new(-2.0, 2.5, 5.0); let camera_transform = Transform::from_translation(camera_pos).looking_at(CAMERA_TARGET, Vec3::Y); commands.insert_resource(OriginalCameraTransform(camera_transform)); commands.spawn(Camera3dBundle { transform: camera_transform, ..Default::default() }); } fn update_camera_transform_system( occupied_screen_space: Res, original_camera_transform: Res, windows: Query<&Window, With>, mut camera_query: Query<(&Projection, &mut Transform)>, ) { let (camera_projection, mut transform) = match camera_query.get_single_mut() { Ok((Projection::Perspective(projection), transform)) => (projection, transform), _ => unreachable!(), }; let distance_to_target = (CAMERA_TARGET - original_camera_transform.translation).length(); let frustum_height = 2.0 * distance_to_target * (camera_projection.fov * 0.5).tan(); let frustum_width = frustum_height * camera_projection.aspect_ratio; let window = windows.single(); let left_taken = occupied_screen_space.left / window.width(); let right_taken = occupied_screen_space.right / window.width(); let top_taken = occupied_screen_space.top / window.height(); let bottom_taken = occupied_screen_space.bottom / window.height(); transform.translation = original_camera_transform.translation + transform.rotation.mul_vec3(Vec3::new( (right_taken - left_taken) * frustum_width * 0.5, (top_taken - bottom_taken) * frustum_height * 0.5, 0.0, )); }