nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
#[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
}