use super::TreeCache;
use crate::editor::selection::EntitySelection;
use crate::editor::undo::{UndoHistory, UndoableOperation, capture_hierarchy};
use crate::prelude::*;
pub struct TreeUiResult {
pub open_add_node_modal: bool,
pub project_modified: bool,
}
fn get_entity_name(world: &World, entity: Entity) -> String {
if let Some(Name(name)) = world.get_name(entity) {
name.clone()
} else {
format!("Entity {}", entity.id)
}
}
struct TreeRenderState<'a> {
selection: &'a mut EntitySelection,
undo_history: &'a mut UndoHistory,
project_modified: bool,
tree_cache: &'a mut TreeCache,
}
pub struct WorldTreeUi;
impl WorldTreeUi {
pub fn ui_with_context(
world: &mut World,
selection: &mut EntitySelection,
undo_history: &mut UndoHistory,
tree_cache: &mut TreeCache,
gizmo_root: Option<Entity>,
ui: &mut egui::Ui,
) -> TreeUiResult {
if tree_cache.needs_rebuild(gizmo_root, selection.len()) {
tree_cache.rebuild(world, world.resources.active_camera, gizmo_root);
tree_cache.update_selection_count(selection.len());
}
let mut open_add_node_modal = false;
ui.horizontal(|ui| {
if ui.button("+ Add Node").clicked() {
open_add_node_modal = true;
}
});
ui.separator();
let mut state = TreeRenderState {
selection,
undo_history,
project_modified: false,
tree_cache,
};
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.show(ui, |ui| {
for root_index in 0..state.tree_cache.roots.len() {
let root = state.tree_cache.roots[root_index];
Self::render_entity_recursive(world, &mut state, root, ui);
}
});
TreeUiResult {
open_add_node_modal,
project_modified: state.project_modified,
}
}
fn render_entity_recursive(
world: &mut World,
state: &mut TreeRenderState,
entity: Entity,
ui: &mut egui::Ui,
) {
if state.tree_cache.editor_entities.contains(&entity) {
return;
}
let name = get_entity_name(world, entity);
let children_len = state
.tree_cache
.sorted_children
.get(&entity)
.map_or(0, |children| children.len());
let has_children = children_len > 0;
let selected = state.selection.contains(entity);
if has_children {
let id = ui.make_persistent_id(entity.id);
egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false)
.show_header(ui, |ui| {
let response = ui.selectable_label(selected, &name);
Self::handle_click(state.selection, entity, &response, ui);
Self::show_context_menu(world, state, entity, &response);
})
.body(|ui| {
for child_index in 0..children_len {
let child = state.tree_cache.sorted_children[&entity][child_index];
Self::render_entity_recursive(world, state, child, ui);
}
});
} else {
ui.horizontal(|ui| {
ui.add_space(19.0);
let response = ui.selectable_label(selected, &name);
Self::handle_click(state.selection, entity, &response, ui);
Self::show_context_menu(world, state, entity, &response);
});
}
}
fn handle_click(
selection: &mut EntitySelection,
entity: Entity,
response: &egui::Response,
ui: &egui::Ui,
) {
if response.clicked() {
let modifiers = ui.input(|i| i.modifiers);
if modifiers.ctrl {
selection.toggle(entity);
} else if modifiers.shift {
selection.add(entity);
} else {
selection.set_single(entity);
}
}
}
fn show_context_menu(
world: &mut World,
state: &mut TreeRenderState,
entity: Entity,
response: &egui::Response,
) {
let has_prefab_source = world.get_prefab_source(entity).is_some();
response.context_menu(|ui| {
if ui.button("Add Entity").clicked() {
Self::add_entity_internal(world, state, entity);
ui.close();
}
if has_prefab_source && ui.button("Instantiate Prefab").clicked() {
super::prefab::instantiate_prefab(world, state.tree_cache, entity);
ui.close();
}
if ui.button("Remove").clicked() {
let hierarchy = Box::new(capture_hierarchy(world, entity));
let entity_name = world
.get_name(entity)
.map(|n| n.0.clone())
.unwrap_or_else(|| format!("Entity {}", entity.id));
state.undo_history.push(
UndoableOperation::EntityDeleted {
hierarchy,
deleted_entity: entity,
},
format!("Delete {}", entity_name),
);
despawn_recursive_immediate(world, entity);
state.selection.clear();
state.tree_cache.mark_dirty();
state.project_modified = true;
ui.close();
}
});
}
fn add_entity_internal(world: &mut World, state: &mut TreeRenderState, parent_entity: Entity) {
let entities = EntityBuilder::new()
.with_name(Name(String::new()))
.with_local_transform(LocalTransform::default())
.with_global_transform(GlobalTransform::default())
.with_parent(Parent(Some(parent_entity)))
.spawn(world, 1);
let new_entity = entities[0];
if let Some(name) = world.get_name_mut(new_entity) {
*name = Name(format!("Entity {}", new_entity.id));
}
let hierarchy = Box::new(capture_hierarchy(world, new_entity));
state.undo_history.push(
UndoableOperation::EntityCreated {
hierarchy,
current_entity: new_entity,
},
format!("Create Entity {}", new_entity.id),
);
state.tree_cache.mark_dirty();
state.selection.set_single(new_entity);
state.project_modified = true;
}
}