use bevy::ecs::component::ComponentId;
use bevy::ecs::reflect::{AppTypeRegistry, ReflectComponent};
use bevy::prelude::*;
use jackdaw_api::prelude::*;
use super::component_display::revert_component_to_baseline;
use super::physics_display::{DisablePhysics, enable_physics};
use crate::commands::{AddComponent, CommandHistory, EditorCommand};
use crate::selection::Selection;
pub(crate) fn add_to_extension(ctx: &mut ExtensionContext) {
ctx.register_operator::<ComponentAddOp>()
.register_operator::<ComponentRemoveOp>()
.register_operator::<ComponentRevertBaselineOp>()
.register_operator::<PhysicsEnableOp>()
.register_operator::<PhysicsDisableOp>()
.register_operator::<AnimationToggleKeyframeOp>()
.register_operator::<super::brush_display::BrushFaceClearMaterialOp>()
.register_operator::<super::brush_display::BrushFaceApplyTextureToAllOp>()
.register_operator::<super::brush_display::BrushFaceSetUvScalePresetOp>()
.register_operator::<super::brush_display::BrushClearAllMaterialsOp>();
}
fn has_primary_selection(selection: Res<Selection>) -> bool {
selection.primary().is_some()
}
fn component_id_for_path(
world: &mut World,
type_path: &str,
) -> Option<(ComponentId, std::any::TypeId)> {
let registry = world.resource::<AppTypeRegistry>().clone();
let type_id = {
let registry_read = registry.read();
let registration = registry_read.get_with_type_path(type_path)?;
registration.type_id()
};
if let Some(component_id) = world.components().get_id(type_id) {
return Some((component_id, type_id));
}
let (reflect_component, default_value) = {
let registry_read = registry.read();
let reflect_component = registry_read
.get_with_type_path(type_path)?
.data::<ReflectComponent>()?
.clone();
let default_value =
crate::reflect_default::build_reflective_default(type_id, ®istry_read)?;
(reflect_component, default_value)
};
let temp = world.spawn_empty().id();
{
let registry_read = registry.read();
reflect_component.insert(
&mut world.entity_mut(temp),
default_value.as_partial_reflect(),
®istry_read,
);
}
let component_id = world.components().get_id(type_id);
world.despawn(temp);
component_id.map(|id| (id, type_id))
}
#[operator(
id = "component.add",
label = "Add Component",
description = "Add a component to the selected entity.",
is_available = has_primary_selection,
params(
entity(Entity, doc = "Entity that receives the component."),
type_path(String, doc = "Fully-qualified Bevy reflected type path of the component to add."),
),
)]
pub(crate) fn component_add(
params: In<OperatorParameters>,
mut commands: Commands,
) -> OperatorResult {
let Some(entity) = params.as_entity("entity") else {
return OperatorResult::Cancelled;
};
let Some(type_path) = params.as_str("type_path").map(str::to_string) else {
return OperatorResult::Cancelled;
};
commands.queue(move |world: &mut World| {
let Some((component_id, type_id)) = component_id_for_path(world, &type_path) else {
warn!(
"component.add: no registration for type_path '{type_path}'. \
Make sure your plugin calls `register_type::<T>()` and that `T` \
derives `Reflect, Default` with `#[reflect(Component, Default)]`."
);
return;
};
let mut cmd: Box<dyn EditorCommand> =
Box::new(AddComponent::new(entity, type_id, component_id, type_path));
cmd.execute(world);
world.resource_mut::<CommandHistory>().push_executed(cmd);
if let Ok(mut ec) = world.get_entity_mut(entity) {
ec.insert(super::InspectorDirty);
}
});
OperatorResult::Finished
}
#[operator(
id = "component.remove",
label = "Remove Component",
description = "Remove a component from the selected entity.",
is_available = has_primary_selection,
params(
entity(Entity, doc = "Entity that loses the component."),
type_path(String, doc = "Fully-qualified Bevy reflected type path of the component to remove."),
),
)]
pub(crate) fn component_remove(
params: In<OperatorParameters>,
mut commands: Commands,
) -> OperatorResult {
let Some(entity) = params.as_entity("entity") else {
return OperatorResult::Cancelled;
};
let Some(type_path) = params.as_str("type_path").map(str::to_string) else {
return OperatorResult::Cancelled;
};
commands.queue(move |world: &mut World| {
let Some((component_id, _)) = component_id_for_path(world, &type_path) else {
return;
};
if let Ok(mut ec) = world.get_entity_mut(entity) {
ec.remove_by_id(component_id);
ec.insert(super::InspectorDirty);
}
});
OperatorResult::Finished
}
#[operator(
id = "component.revert_baseline",
label = "Revert To Prefab",
description = "Restore the component to the value it had in the source prefab.",
is_available = has_primary_selection,
params(
entity(Entity, doc = "Prefab instance entity to revert."),
type_path(String, doc = "Fully-qualified Bevy reflected type path of the component to revert."),
),
)]
pub(crate) fn component_revert_baseline(
params: In<OperatorParameters>,
mut commands: Commands,
) -> OperatorResult {
let Some(entity) = params.as_entity("entity") else {
return OperatorResult::Cancelled;
};
let Some(type_path) = params.as_str("type_path").map(str::to_string) else {
return OperatorResult::Cancelled;
};
commands.queue(move |world: &mut World| {
let Some((component_id, _)) = component_id_for_path(world, &type_path) else {
return;
};
if let Err(err) =
world.run_system_cached_with(revert_component_to_baseline, (entity, component_id))
{
error!("revert_component_to_baseline failed: {err}");
}
});
OperatorResult::Finished
}
#[operator(
id = "physics.enable",
label = "Enable Physics",
description = "Make the selected entity participate in the physics simulation.",
is_available = has_primary_selection,
params(entity(Entity, doc = "Entity to make physical.")),
)]
pub(crate) fn physics_enable(
params: In<OperatorParameters>,
mut commands: Commands,
) -> OperatorResult {
let Some(entity) = params.as_entity("entity") else {
return OperatorResult::Cancelled;
};
commands.queue(move |world: &mut World| {
enable_physics(world, entity);
if let Ok(mut ec) = world.get_entity_mut(entity) {
ec.insert(super::InspectorDirty);
}
});
OperatorResult::Finished
}
#[operator(
id = "physics.disable",
label = "Disable Physics",
description = "Stop the selected entity from participating in the physics simulation.",
is_available = has_primary_selection,
params(entity(Entity, doc = "Entity to make non-physical.")),
)]
pub(crate) fn physics_disable(
params: In<OperatorParameters>,
mut commands: Commands,
) -> OperatorResult {
let Some(entity) = params.as_entity("entity") else {
return OperatorResult::Cancelled;
};
commands.queue(move |world: &mut World| {
let mut cmd: Box<dyn EditorCommand> = Box::new(DisablePhysics::from_world(world, entity));
cmd.execute(world);
world.resource_mut::<CommandHistory>().push_executed(cmd);
if let Ok(mut ec) = world.get_entity_mut(entity) {
ec.insert(super::InspectorDirty);
}
});
OperatorResult::Finished
}
#[operator(
id = "animation.toggle_keyframe",
label = "Toggle Keyframe",
description = "Add or replace a keyframe for this property at the current timeline cursor.",
is_available = has_primary_selection,
params(
entity(Entity, doc = "Source entity whose property is being animated."),
component_type_path(String, doc = "Fully-qualified Bevy reflected type path of the component that owns the property."),
field_path(String, doc = "Dotted path to the field within the component (e.g. \"translation\")."),
),
)]
pub(crate) fn animation_toggle_keyframe(
params: In<OperatorParameters>,
mut commands: Commands,
) -> OperatorResult {
let Some(entity) = params.as_entity("entity") else {
return OperatorResult::Cancelled;
};
let Some(type_path) = params.as_str("component_type_path").map(str::to_string) else {
return OperatorResult::Cancelled;
};
let Some(field_path) = params.as_str("field_path").map(str::to_string) else {
return OperatorResult::Cancelled;
};
commands.queue(move |world: &mut World| {
world
.run_system_cached_with(
super::anim_diamond::toggle_keyframe,
(entity, type_path, field_path),
)
.ok();
});
OperatorResult::Finished
}