Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ description = "In-App editor tools for bevy apps"
readme = "README.md"

[workspace.dependencies]
bevy_editor_pls = { version = "0.8.0", path = "crates/bevy_editor_pls" }
bevy_editor_pls_core = { version = "0.8.0", path = "crates/bevy_editor_pls_core" }
bevy_editor_pls_default_windows = { version = "0.8.0", path = "crates/bevy_editor_pls_default_windows" }
bevy_editor_pls = { version = "0.8.1", path = "crates/bevy_editor_pls" }
bevy_editor_pls_core = { version = "0.8.1", path = "crates/bevy_editor_pls_core" }
bevy_editor_pls_default_windows = { version = "0.8.1", path = "crates/bevy_editor_pls_default_windows" }

bevy-inspector-egui = "0.23.0"
egui = "0.26"
egui_dock = "0.11"
egui-gizmo = "0.16"
bevy-inspector-egui = "0.24.0"
egui = "0.27"
egui_dock = "0.12"
# used to be egui-gizmo 0.16
transform-gizmo-bevy = "0.2"

[profile.dev.package."*"]
opt-level = 2
2 changes: 1 addition & 1 deletion crates/bevy_editor_pls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bevy_editor_pls_core.workspace = true
bevy_editor_pls_default_windows = { workspace = true, optional = true }
bevy = { version = "0.13", default-features = false, features = ["x11"] }
egui.workspace = true
egui-gizmo.workspace = true
transform-gizmo-bevy.workspace = true
# bevy_framepace = { version = "0.12", default-features = false }

[dev-dependencies]
Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_editor_pls/src/controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ pub enum Action {
PauseUnpauseTime,
FocusSelected,

// maybe investigate [GizmoOptions].hotkeys
// https://docs.rs/transform-gizmo-bevy/latest/transform_gizmo_bevy/struct.GizmoHotkeys.html
#[cfg(feature = "default_windows")]
SetGizmoModeTranslate,
#[cfg(feature = "default_windows")]
Expand Down Expand Up @@ -240,7 +242,7 @@ pub fn editor_controls_system(
editor
.window_state_mut::<bevy_editor_pls_default_windows::gizmos::GizmoWindow>()
.unwrap()
.gizmo_mode = egui_gizmo::GizmoMode::Translate;
.gizmo_modes = transform_gizmo_bevy::GizmoMode::all_translate();
}
if controls.just_pressed(
Action::SetGizmoModeRotate,
Expand All @@ -251,7 +253,7 @@ pub fn editor_controls_system(
editor
.window_state_mut::<bevy_editor_pls_default_windows::gizmos::GizmoWindow>()
.unwrap()
.gizmo_mode = egui_gizmo::GizmoMode::Rotate;
.gizmo_modes = transform_gizmo_bevy::GizmoMode::all_rotate();
}
if controls.just_pressed(
Action::SetGizmoModeScale,
Expand All @@ -262,7 +264,7 @@ pub fn editor_controls_system(
editor
.window_state_mut::<bevy_editor_pls_default_windows::gizmos::GizmoWindow>()
.unwrap()
.gizmo_mode = egui_gizmo::GizmoMode::Scale;
.gizmo_modes = transform_gizmo_bevy::GizmoMode::all_scale();
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_editor_pls/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ impl Plugin for EditorPlugin {

app.add_plugins(bevy::pbr::wireframe::WireframePlugin);

// required for the GizmoWindow
if !app.is_plugin_added::<transform_gizmo_bevy::TransformGizmoPlugin>() {
app.add_plugins(transform_gizmo_bevy::TransformGizmoPlugin);
}

app.insert_resource(controls::EditorControls::default_bindings())
.add_systems(Update, controls::editor_controls_system);

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_editor_pls_core/src/editor_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub trait EditorWindow: 'static {
const DEFAULT_SIZE: (f32, f32) = (0.0, 0.0);

fn ui(world: &mut World, cx: EditorWindowContext, ui: &mut egui::Ui);

/// Ui shown in the `Open Window` menu item. By default opens the window as a floating window.
fn menu_ui(world: &mut World, mut cx: EditorWindowContext, ui: &mut egui::Ui) {
let _ = world;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_editor_pls_default_windows/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ indexmap = "2"
pretty-type-name = "1.0"
bevy_mod_debugdump = "0.10"
opener = "0.6.0"
egui-gizmo.workspace = true
transform-gizmo-bevy.workspace = true
10 changes: 7 additions & 3 deletions crates/bevy_editor_pls_default_windows/src/cameras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use bevy_editor_pls_core::{
Editor, EditorEvent,
};
use bevy_inspector_egui::egui;
use transform_gizmo_bevy::GizmoCamera;
// use bevy_mod_picking::prelude::PickRaycastSource;

use crate::hierarchy::{HideInEditor, HierarchyWindow};
Expand Down Expand Up @@ -232,6 +233,7 @@ fn spawn_editor_cameras(mut commands: Commands, editor: Res<Editor>) {
HideInEditor,
Name::new("Editor Camera 3D Free"),
NotInScene,
GizmoCamera,
render_layers,
));

Expand All @@ -254,6 +256,7 @@ fn spawn_editor_cameras(mut commands: Commands, editor: Res<Editor>) {
HideInEditor,
Name::new("Editor Camera 3D Pan/Orbit"),
NotInScene,
GizmoCamera,
render_layers,
));

Expand All @@ -275,6 +278,7 @@ fn spawn_editor_cameras(mut commands: Commands, editor: Res<Editor>) {
HideInEditor,
Name::new("Editor Camera 2D Pan/Zoom"),
NotInScene,
GizmoCamera,
render_layers,
));
}
Expand Down Expand Up @@ -600,7 +604,7 @@ fn set_main_pass_viewport(
}
});

cameras.iter_mut().for_each(|mut cam| {
cam.viewport = viewport.clone();
});
cameras
.iter_mut()
.for_each(|mut cam| cam.viewport.clone_from(&viewport));
}
149 changes: 84 additions & 65 deletions crates/bevy_editor_pls_default_windows/src/gizmos.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
use bevy::{
ecs::query::QueryFilter,
ecs::{query::QueryFilter, system::RunSystemOnce},
prelude::*,
render::{camera::CameraProjection, view::RenderLayers},
render::view::RenderLayers,
};

use bevy_editor_pls_core::editor_window::{EditorWindow, EditorWindowContext};
use bevy_inspector_egui::{bevy_inspector::hierarchy::SelectedEntities, egui};
use egui_gizmo::GizmoMode;
use bevy_inspector_egui::egui;
use transform_gizmo_bevy::GizmoTarget;
use transform_gizmo_bevy::{EnumSet, GizmoMode};

use crate::{
cameras::{ActiveEditorCamera, CameraWindow, EditorCamera, EDITOR_RENDER_LAYER},
cameras::{EditorCamera, EDITOR_RENDER_LAYER},
hierarchy::HierarchyWindow,
};

pub struct GizmoState {
/// If [false], doesn't show any gizmos
pub camera_gizmo_active: bool,
pub gizmo_mode: GizmoMode,
/// Synced with the [transform_gizmo_bevy::GizmoOptions] resource
pub gizmo_modes: EnumSet<GizmoMode>,
Copy link
Contributor Author

@ActuallyHappening ActuallyHappening Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only breaking change, at least that I can see

}

impl Default for GizmoState {
fn default() -> Self {
Self {
camera_gizmo_active: true,
gizmo_mode: GizmoMode::Translate,
gizmo_modes: GizmoMode::all_translate(),
}
}
}
Expand All @@ -36,16 +39,79 @@ impl EditorWindow for GizmoWindow {

fn ui(_world: &mut World, _cx: EditorWindowContext, ui: &mut egui::Ui) {
ui.label("Gizmos can currently not be configured");
// could definitely change some settings here in the future
}

fn viewport_toolbar_ui(world: &mut World, cx: EditorWindowContext, ui: &mut egui::Ui) {
/// Called every frame (hopefully), could this invariant (namely being called every frame) be documented,
/// ideally in the [EditorWindow] trait?
fn viewport_toolbar_ui(world: &mut World, cx: EditorWindowContext, _ui: &mut egui::Ui) {
let gizmo_state = cx.state::<GizmoWindow>().unwrap();

// syncs the [GizmoOptions] resource with the current state of the gizmo window
let mut gizmo_options = world.resource_mut::<transform_gizmo_bevy::GizmoOptions>();
gizmo_options.gizmo_modes = gizmo_state.gizmo_modes;

if gizmo_state.camera_gizmo_active {
if let (Some(hierarchy_state), Some(_camera_state)) =
(cx.state::<HierarchyWindow>(), cx.state::<CameraWindow>())
{
draw_gizmo(ui, world, &hierarchy_state.selected, gizmo_state.gizmo_mode);
/// Before [hydrate_gizmos] and [deconstruct_gizmos] are run, this system resets the state of all entities that have a [EntityShouldShowGizmo] component.
/// Then, according to selection logic some entities are marked as focussed, and [hydrate_gizmos] and [deconstruct_gizmos] is run to sync the gizmo state with the selection state.
fn reset_gizmos_selected_state(
mut commands: Commands,
entities: Query<Entity, With<EntityShouldShowGizmo>>,
) {
for entity in entities.iter() {
commands.entity(entity).remove::<EntityShouldShowGizmo>();
}
}

/// Takes all entities marked with [EntityShouldShowGizmo] and adds the [GizmoTarget] component to them.
fn hydrate_gizmos(
mut commands: Commands,
entities: Query<Entity, (With<EntityShouldShowGizmo>, Without<GizmoTarget>)>,
) {
for entity in entities.iter() {
if let Some(mut entity) = commands.get_entity(entity) {
trace!(
"Hydrating a gizmo on entity {:?} because it is selected",
entity.id()
);
// implicitly assumes it is the only gizmo target in the world,
// otherwise setting [GizmoTarget].is_focussed may be necessary
entity.insert(GizmoTarget::default());
}
}
}

/// Takes all entities that should have their [GizmoTarget] removed because they are no longer selected.
fn deconstruct_gizmos(
mut commands: Commands,
entities: Query<Entity, (With<GizmoTarget>, Without<EntityShouldShowGizmo>)>,
) {
for entity in entities.iter() {
if let Some(mut entity) = commands.get_entity(entity) {
entity.remove::<GizmoTarget>();
debug!(
"Removing GizmoTarget from entity {:?} because it has lost focus",
entity.id()
);
}
}
}

if let Some(hierarchy_state) = cx.state::<HierarchyWindow>() {
// here should assign the `EntityShouldShowGizmo` component, which is later synced
// with the actual gizmo ui system

world.run_system_once(reset_gizmos_selected_state);

let selected_entities = hierarchy_state.selected.iter();
for entity in selected_entities {
if let Some(mut entity) = world.get_entity_mut(entity) {
entity.insert(EntityShouldShowGizmo);
}
}

world.run_system_once(hydrate_gizmos);
world.run_system_once(deconstruct_gizmos);
}
}
}
Expand Down Expand Up @@ -93,9 +159,15 @@ struct GizmoMarkerConfig {
camera_material: Handle<StandardMaterial>,
}

/// can somebody document what this does? is it a duplicate of [EntityShouldShowGizmo]?
#[derive(Component)]
struct HasGizmoMarker;

/// When on an entity, this entity should be controllable using some sort of user gizmo.
/// Currently uses [transform_gizmo_bevy], and puts the [GizmoTarget] on the entity.
#[derive(Component)]
struct EntityShouldShowGizmo;

type GizmoMarkerQuery<'w, 's, T, F = ()> =
Query<'w, 's, Entity, (With<T>, Without<HasGizmoMarker>, F)>;

Expand Down Expand Up @@ -165,56 +237,3 @@ fn add_gizmo_markers(
});
}
}

fn draw_gizmo(
ui: &mut egui::Ui,
world: &mut World,
selected_entities: &SelectedEntities,
gizmo_mode: GizmoMode,
) {
let Ok((cam_transform, projection)) = world
.query_filtered::<(&GlobalTransform, &Projection), With<ActiveEditorCamera>>()
.get_single(world)
else {
return;
};
let view_matrix = Mat4::from(cam_transform.affine().inverse());
let projection_matrix = projection.get_projection_matrix();

if selected_entities.len() != 1 {
return;
}

for selected in selected_entities.iter() {
let Some(global_transform) = world.get::<GlobalTransform>(selected) else {
continue;
};
let model_matrix = global_transform.compute_matrix();

let Some(result) = egui_gizmo::Gizmo::new(selected)
.model_matrix(model_matrix.into())
.view_matrix(view_matrix.into())
.projection_matrix(projection_matrix.into())
.orientation(egui_gizmo::GizmoOrientation::Local)
.mode(gizmo_mode)
.interact(ui)
else {
continue;
};

let global_affine = global_transform.affine();

let mut transform = world.get_mut::<Transform>(selected).unwrap();

let parent_affine = global_affine * transform.compute_affine().inverse();
let inverse_parent_transform = GlobalTransform::from(parent_affine.inverse());

let global_transform = Transform {
translation: result.translation.into(),
rotation: result.rotation.into(),
scale: result.scale.into(),
};

*transform = (inverse_parent_transform * global_transform).into();
}
}