#[cfg(not(target_arch = "wasm32"))]
use super::clipboard::{ClipboardData, ClipboardHierarchy};
use super::context::{EditorContext, TransformOperation};
use super::undo::{
HierarchyNode, UndoableOperation, capture_hierarchy, perform_redo, perform_undo,
push_bulk_entities_created, push_bulk_entities_deleted, recreate_hierarchy_with_mapping,
};
use crate::prelude::*;
use indexmap::IndexSet;
#[derive(Clone, Default)]
pub struct Selection<T: Eq + std::hash::Hash + Copy> {
pub items: IndexSet<T>,
pub primary: Option<T>,
}
impl<T: Eq + std::hash::Hash + Copy> Selection<T> {
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn contains(&self, item: T) -> bool {
self.items.contains(&item)
}
pub fn primary(&self) -> Option<T> {
self.primary
}
pub fn items_vec(&self) -> Vec<T> {
self.items.iter().copied().collect()
}
pub fn set_single(&mut self, item: T) {
self.items.clear();
self.items.insert(item);
self.primary = Some(item);
}
pub fn add(&mut self, item: T) {
if self.items.insert(item) && self.primary.is_none() {
self.primary = Some(item);
}
}
pub fn remove(&mut self, item: T) {
self.items.swap_remove(&item);
if self.primary == Some(item) {
self.primary = self.items.first().copied();
}
}
pub fn toggle(&mut self, item: T) {
if self.items.contains(&item) {
self.remove(item);
} else {
self.add(item);
}
}
pub fn clear(&mut self) {
self.items.clear();
self.primary = None;
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn iter(&self) -> indexmap::set::Iter<'_, T> {
self.items.iter()
}
}
pub type EntitySelection = Selection<Entity>;
fn detach_gizmo(context: &EditorContext, world: &mut World) {
if let Some(gizmo) = &context.gizmo_interaction.active {
world.update_parent(gizmo.root, None);
}
}
pub fn delete_selection(context: &mut EditorContext, world: &mut World) -> bool {
let entities: Vec<Entity> = context.selection.items_vec();
if entities.is_empty() {
return false;
}
detach_gizmo(context, world);
if entities.len() == 1 {
let entity = entities[0];
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));
context.undo_history.push(
UndoableOperation::EntityDeleted {
hierarchy,
deleted_entity: entity,
},
format!("Delete {}", entity_name),
);
despawn_recursive_immediate(world, entity);
} else {
push_bulk_entities_deleted(
&mut context.undo_history,
world,
entities.clone(),
"Delete selection",
);
for entity in &entities {
despawn_recursive_immediate(world, *entity);
}
}
context.selection.clear();
true
}
pub fn duplicate_selection(context: &mut EditorContext, world: &mut World) -> bool {
let entities: Vec<Entity> = context.selection.items_vec();
if entities.is_empty() {
return false;
}
detach_gizmo(context, world);
let mut new_entities = Vec::new();
for entity in &entities {
let source_hierarchy = capture_hierarchy(world, *entity);
let parent = world.get_parent(*entity).and_then(|p| p.0);
let new_entity = recreate_hierarchy_for_duplicate(world, &source_hierarchy, parent);
new_entities.push(new_entity);
}
if new_entities.len() == 1 {
let new_entity = new_entities[0];
let new_hierarchy = Box::new(capture_hierarchy(world, new_entity));
context.undo_history.push(
UndoableOperation::EntityCreated {
hierarchy: new_hierarchy,
current_entity: new_entity,
},
"Duplicate entity".to_string(),
);
context.selection.set_single(new_entity);
} else {
push_bulk_entities_created(
&mut context.undo_history,
world,
new_entities.clone(),
"Duplicate selection",
);
context.selection.clear();
for entity in &new_entities {
context.selection.add(*entity);
}
}
super::gizmo::start_modal_transform(context, world, TransformOperation::Grab);
true
}
pub fn select_all(context: &mut EditorContext, world: &mut World) {
let camera_entity = world.resources.active_camera;
let gizmo = context.gizmo_interaction.active.as_ref();
context.selection.clear();
let entities: Vec<Entity> = world
.query_entities(crate::ecs::LOCAL_TRANSFORM)
.filter(|entity| {
!super::picking::is_editor_utility_entity(world, *entity, camera_entity, gizmo)
})
.collect();
for entity in entities {
context.selection.add(entity);
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn copy_selection_to_clipboard(context: &EditorContext, world: &World) -> bool {
let entities: Vec<Entity> = context.selection.items_vec();
if entities.is_empty() {
return false;
}
let hierarchies: Vec<ClipboardHierarchy> = entities
.iter()
.map(|&entity| ClipboardHierarchy::from_world(world, entity))
.collect();
if let Ok(json) = serde_json::to_string(&ClipboardData {
version: "1.0".to_string(),
hierarchies,
}) && let Ok(mut clipboard) = arboard::Clipboard::new()
&& clipboard.set_text(json).is_ok()
{
return true;
}
false
}
#[cfg(target_arch = "wasm32")]
pub fn copy_selection_to_clipboard(_context: &EditorContext, _world: &World) -> bool {
false
}
#[cfg(not(target_arch = "wasm32"))]
pub fn paste_from_clipboard(context: &mut EditorContext, world: &mut World) -> bool {
let json = match arboard::Clipboard::new().and_then(|mut c| c.get_text()) {
Ok(text) => text,
Err(_) => return false,
};
let data: ClipboardData = match serde_json::from_str(&json) {
Ok(data) => data,
Err(_) => return false,
};
detach_gizmo(context, world);
let mut new_entities = Vec::new();
for hierarchy in &data.hierarchies {
let new_entity = hierarchy.spawn(world, None);
new_entities.push(new_entity);
}
if !new_entities.is_empty() {
push_bulk_entities_created(
&mut context.undo_history,
world,
new_entities.clone(),
"Paste",
);
context.selection.clear();
for entity in &new_entities {
context.selection.add(*entity);
}
return true;
}
false
}
#[cfg(target_arch = "wasm32")]
pub fn paste_from_clipboard(_context: &mut EditorContext, _world: &mut World) -> bool {
false
}
pub fn undo_with_selection_update(context: &mut EditorContext, world: &mut World) -> bool {
detach_gizmo(context, world);
let result = perform_undo(
&mut context.undo_history,
world,
&mut context.transform_edit.pending,
);
if let Some(result) = result {
if let Some(entity) = result.select_entity {
context.selection.set_single(entity);
} else {
context.selection.clear();
}
return true;
}
false
}
pub fn redo_with_selection_update(context: &mut EditorContext, world: &mut World) -> bool {
detach_gizmo(context, world);
let result = perform_redo(
&mut context.undo_history,
world,
&mut context.transform_edit.pending,
);
if let Some(result) = result {
if let Some(entity) = result.select_entity {
context.selection.set_single(entity);
} else {
context.selection.clear();
}
return true;
}
false
}
fn recreate_hierarchy_for_duplicate(
world: &mut World,
node: &HierarchyNode,
parent: Option<Entity>,
) -> Entity {
let (new_entity, _) = recreate_hierarchy_with_mapping(world, node, parent);
world.resources.children_cache_valid = false;
new_entity
}