mod brush_display;
pub(crate) mod component_display;
mod component_picker;
mod custom_props_display;
mod material_display;
pub(crate) mod physics_display;
pub(crate) mod reflect_fields;
use crate::EditorEntity;
use bevy::prelude::*;
const MAX_REFLECT_DEPTH: usize = 4;
fn extract_module_group(module_path: Option<&str>) -> String {
let Some(path) = module_path else {
return "Other".to_string();
};
let first = path.split("::").next().unwrap_or(path);
let name = first.strip_prefix("bevy_").unwrap_or(first);
match name {
"pbr" | "core_pipeline" => "Render".to_string(),
"render" => "Render".to_string(),
"transform" => "Transform".to_string(),
"ecs" => "ECS".to_string(),
"hierarchy" => "Hierarchy".to_string(),
"window" | "winit" => "Window".to_string(),
"input" | "picking" => "Input".to_string(),
"asset" => "Asset".to_string(),
"scene" => "Scene".to_string(),
"gltf" => "GLTF".to_string(),
"ui" => "UI".to_string(),
"text" => "Text".to_string(),
"audio" => "Audio".to_string(),
"animation" => "Animation".to_string(),
"sprite" => "Sprite".to_string(),
_ => {
let mut chars = name.chars();
match chars.next() {
None => "Other".to_string(),
Some(c) => c.to_uppercase().to_string() + chars.as_str(),
}
}
}
}
pub trait EditorMeta {
fn description() -> &'static str {
""
}
fn category() -> &'static str {
""
}
}
#[derive(Clone)]
pub struct ReflectEditorMeta {
pub description: &'static str,
pub category: &'static str,
}
impl<T: EditorMeta> bevy::reflect::FromType<T> for ReflectEditorMeta {
fn from_type() -> Self {
ReflectEditorMeta {
description: T::description(),
category: T::category(),
}
}
}
#[reflect_trait]
pub trait Displayable {
fn display(&self, entity: &mut EntityCommands, source: Entity);
}
#[derive(Component)]
struct NameFieldInput(Entity);
impl Displayable for Name {
fn display(&self, entity: &mut EntityCommands, source: Entity) {
entity
.insert(jackdaw_feathers::text_edit::text_edit(
jackdaw_feathers::text_edit::TextEditProps::default()
.with_placeholder("Name...")
.with_default_value(self.to_string())
.allow_empty(),
))
.insert(NameFieldInput(source));
}
}
#[derive(Component)]
#[require(EditorEntity)]
pub struct Inspector;
pub struct InspectorPlugin;
impl Plugin for InspectorPlugin {
fn build(&self, app: &mut App) {
app.register_type_data::<Name, ReflectDisplayable>()
.add_observer(component_display::remove_component_displays)
.add_observer(component_display::add_component_displays)
.add_observer(component_display::on_inspector_dirty)
.add_observer(component_picker::on_add_component_button_click)
.add_observer(reflect_fields::on_checkbox_commit)
.add_observer(reflect_fields::on_text_edit_commit)
.add_observer(physics_display::on_physics_enable_toggle)
.add_observer(custom_props_display::on_custom_property_checkbox_commit)
.add_observer(custom_props_display::on_custom_property_text_commit)
.add_observer(brush_display::handle_clear_texture)
.add_observer(brush_display::handle_clear_material)
.add_observer(brush_display::handle_clear_material_from_brush)
.add_observer(brush_display::handle_apply_texture_to_all)
.add_observer(brush_display::handle_uv_scale_preset)
.add_observer(brush_display::on_brush_face_text_commit)
.add_observer(on_name_field_commit)
.add_observer(material_display::on_material_text_commit)
.add_systems(
Update,
(
reflect_fields::refresh_inspector_fields,
reflect_fields::refresh_enum_variants,
component_picker::filter_component_picker,
component_picker::close_picker_on_escape,
brush_display::update_brush_face_properties,
component_display::filter_inspector_components,
)
.run_if(in_state(crate::AppState::Editor)),
);
}
}
fn on_name_field_commit(
event: On<jackdaw_feathers::text_edit::TextEditCommitEvent>,
name_inputs: Query<&NameFieldInput>,
child_of_query: Query<&ChildOf>,
mut names: Query<&mut Name>,
) {
let mut current = event.entity;
let mut source = None;
for _ in 0..4 {
let Ok(child_of) = child_of_query.get(current) else {
break;
};
if let Ok(name_input) = name_inputs.get(child_of.parent()) {
source = Some(name_input.0);
break;
}
current = child_of.parent();
}
let Some(source_entity) = source else {
return;
};
if let Ok(mut name) = names.get_mut(source_entity) {
*name = Name::new(event.text.clone());
}
}
#[derive(Component)]
pub struct ComponentDisplay;
#[derive(Component)]
pub(super) struct ComponentDisplayBody;
#[derive(Component)]
pub struct AddComponentButton;
#[derive(Component)]
pub(super) struct ComponentPicker;
#[derive(Component)]
pub(super) struct ComponentPickerSearch;
#[derive(Component)]
pub(super) struct ComponentPickerEntry {
pub(super) short_name: String,
pub(super) module_path: String,
pub(super) category: String,
pub(super) description: String,
}
#[derive(Component)]
pub(super) struct ComponentPickerSectionHeader {
pub(super) group: String,
}
#[derive(Component)]
pub(super) struct InspectorSearch;
#[derive(Component)]
pub(super) struct ComponentName(pub(super) String);
#[derive(Component)]
pub(super) struct InspectorGroupSection;
#[derive(Component)]
pub(super) struct FieldBinding {
pub(super) source_entity: Entity,
pub(super) type_path: String,
pub(super) field_path: String,
}
#[derive(Component)]
pub(super) struct EnumVariantHost {
pub(super) source_entity: Entity,
pub(super) type_path: String,
pub(super) field_path: String,
pub(super) depth: usize,
pub(super) current_variant: String,
}
#[derive(Component)]
pub(super) struct BrushFacePropsContainer;
#[derive(Component)]
#[allow(dead_code)]
pub(super) struct BrushFaceFieldBinding {
pub(super) field: BrushFaceField,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum BrushFaceField {
UvOffsetX,
UvOffsetY,
UvScaleX,
UvScaleY,
UvRotation,
}
#[derive(Component)]
pub(super) struct CustomPropertyBinding {
pub(super) source_entity: Entity,
pub(super) property_name: String,
}
#[derive(Component)]
pub(super) struct CustomPropertyAddRow;
#[derive(Component)]
pub(super) struct CustomPropertyTypeSelector;
#[derive(Component)]
pub(super) struct CustomPropertyNameInput;
#[derive(Component)]
pub(super) struct InspectorTarget(pub Entity);
#[derive(Component)]
pub(crate) struct InspectorDirty;
pub(super) fn rebuild_inspector(world: &mut World, source_entity: Entity) {
world.entity_mut(source_entity).insert(InspectorDirty);
}