use bevy::prelude::*;
use bevy_map_core::LayerData;
use std::collections::HashMap;
use uuid::Uuid;
use crate::project::Project;
use crate::render::RenderState;
pub trait Command: Send + Sync {
fn execute(&self, project: &mut Project, render_state: &mut RenderState);
fn undo(&self, project: &mut Project, render_state: &mut RenderState);
fn description(&self) -> &str;
}
pub struct BatchTileCommand {
pub level_id: Uuid,
pub layer_idx: usize,
pub changes: HashMap<(u32, u32), (Option<u32>, Option<u32>)>,
description: String,
}
impl BatchTileCommand {
pub fn new(
level_id: Uuid,
layer_idx: usize,
changes: HashMap<(u32, u32), (Option<u32>, Option<u32>)>,
description: impl Into<String>,
) -> Self {
Self {
level_id,
layer_idx,
changes,
description: description.into(),
}
}
pub fn from_diff(
level_id: Uuid,
layer_idx: usize,
before: HashMap<(u32, u32), Option<u32>>,
after: HashMap<(u32, u32), Option<u32>>,
description: impl Into<String>,
) -> Self {
let mut changes = HashMap::new();
for ((x, y), old_tile) in before {
let new_tile = after.get(&(x, y)).copied().flatten();
if old_tile != new_tile {
changes.insert((x, y), (old_tile, Some(new_tile).flatten()));
}
}
Self::new(level_id, layer_idx, changes, description)
}
}
impl Command for BatchTileCommand {
fn execute(&self, project: &mut Project, render_state: &mut RenderState) {
if let Some(level) = project.get_level_mut(self.level_id) {
if let Some(layer) = level.layers.get_mut(self.layer_idx) {
if let LayerData::Tiles { tiles, .. } = &mut layer.data {
for ((x, y), (_, new_tile)) in &self.changes {
let idx = (*y * level.width + *x) as usize;
if idx < tiles.len() {
tiles[idx] = *new_tile;
}
}
}
}
}
render_state.needs_rebuild = true;
}
fn undo(&self, project: &mut Project, render_state: &mut RenderState) {
if let Some(level) = project.get_level_mut(self.level_id) {
if let Some(layer) = level.layers.get_mut(self.layer_idx) {
if let LayerData::Tiles { tiles, .. } = &mut layer.data {
for ((x, y), (old_tile, _)) in &self.changes {
let idx = (*y * level.width + *x) as usize;
if idx < tiles.len() {
tiles[idx] = *old_tile;
}
}
}
}
}
render_state.needs_rebuild = true;
}
fn description(&self) -> &str {
&self.description
}
}
pub fn collect_tiles_in_region(
project: &Project,
level_id: Uuid,
layer_idx: usize,
min_x: i32,
max_x: i32,
min_y: i32,
max_y: i32,
) -> HashMap<(u32, u32), Option<u32>> {
let mut tiles = HashMap::new();
if let Some(level) = project.levels.iter().find(|l| l.id == level_id) {
if let Some(layer) = level.layers.get(layer_idx) {
if let LayerData::Tiles {
tiles: tile_data, ..
} = &layer.data
{
for y in min_y..=max_y {
for x in min_x..=max_x {
if x >= 0 && y >= 0 && x < level.width as i32 && y < level.height as i32 {
let idx = (y as u32 * level.width + x as u32) as usize;
let tile = tile_data.get(idx).copied().flatten();
tiles.insert((x as u32, y as u32), tile);
}
}
}
}
}
}
tiles
}
pub struct MoveEntityCommand {
pub level_id: Uuid,
pub entity_id: Uuid,
pub old_position: [f32; 2],
pub new_position: [f32; 2],
}
impl MoveEntityCommand {
pub fn new(
level_id: Uuid,
entity_id: Uuid,
old_position: [f32; 2],
new_position: [f32; 2],
) -> Self {
Self {
level_id,
entity_id,
old_position,
new_position,
}
}
}
impl Command for MoveEntityCommand {
fn execute(&self, project: &mut Project, _render_state: &mut RenderState) {
if let Some(level) = project.get_level_mut(self.level_id) {
if let Some(entity) = level.entities.iter_mut().find(|e| e.id == self.entity_id) {
entity.position = self.new_position;
}
}
}
fn undo(&self, project: &mut Project, _render_state: &mut RenderState) {
if let Some(level) = project.get_level_mut(self.level_id) {
if let Some(entity) = level.entities.iter_mut().find(|e| e.id == self.entity_id) {
entity.position = self.old_position;
}
}
}
fn description(&self) -> &str {
"Move Entity"
}
}
#[derive(Resource, Default)]
pub struct CommandHistory {
undo_stack: Vec<Box<dyn Command>>,
redo_stack: Vec<Box<dyn Command>>,
}
impl CommandHistory {
pub fn execute(
&mut self,
command: Box<dyn Command>,
project: &mut Project,
render_state: &mut RenderState,
) {
command.execute(project, render_state);
self.undo_stack.push(command);
self.redo_stack.clear(); project.mark_dirty();
}
pub fn undo(&mut self, project: &mut Project, render_state: &mut RenderState) {
if let Some(command) = self.undo_stack.pop() {
command.undo(project, render_state);
self.redo_stack.push(command);
project.mark_dirty();
}
}
pub fn redo(&mut self, project: &mut Project, render_state: &mut RenderState) {
if let Some(command) = self.redo_stack.pop() {
command.execute(project, render_state);
self.undo_stack.push(command);
project.mark_dirty();
}
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn undo_description(&self) -> Option<&str> {
self.undo_stack.last().map(|c| c.description())
}
pub fn redo_description(&self) -> Option<&str> {
self.redo_stack.last().map(|c| c.description())
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
}
pub fn push_undo(&mut self, command: Box<dyn Command>) {
self.undo_stack.push(command);
self.redo_stack.clear();
}
}