use crate::event::{Event, EventHub, Origin, UndoRedoEvent};
use crate::types::EntityId;
use anyhow::{Result, anyhow};
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
pub trait UndoRedoCommand: Send {
fn undo(&mut self) -> Result<()>;
fn redo(&mut self) -> Result<()>;
fn can_merge(&self, _other: &dyn UndoRedoCommand) -> bool {
false
}
fn merge(&mut self, _other: &dyn UndoRedoCommand) -> bool {
false
}
fn as_any(&self) -> &dyn Any;
}
pub struct CompositeCommand {
commands: Vec<Box<dyn UndoRedoCommand>>,
pub stack_id: u64,
}
impl CompositeCommand {
pub fn new(stack_id: Option<u64>) -> Self {
CompositeCommand {
commands: Vec::new(),
stack_id: stack_id.unwrap_or(0),
}
}
pub fn add_command(&mut self, command: Box<dyn UndoRedoCommand>) {
self.commands.push(command);
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
impl UndoRedoCommand for CompositeCommand {
fn undo(&mut self) -> Result<()> {
for command in self.commands.iter_mut().rev() {
command.undo()?;
}
Ok(())
}
fn redo(&mut self) -> Result<()> {
for command in self.commands.iter_mut() {
command.redo()?;
}
Ok(())
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub trait AsyncUndoRedoCommand: UndoRedoCommand {
fn start_undo(&mut self) -> Result<()>;
fn start_redo(&mut self) -> Result<()>;
fn check_progress(&self) -> f32;
fn cancel(&mut self) -> Result<()>;
fn is_complete(&self) -> bool;
}
#[derive(Default)]
struct StackData {
undo_stack: Vec<Box<dyn UndoRedoCommand>>,
redo_stack: Vec<Box<dyn UndoRedoCommand>>,
}
pub struct UndoRedoManager {
stacks: HashMap<u64, StackData>,
next_stack_id: u64,
in_progress_composite: Option<CompositeCommand>,
composite_nesting_level: usize,
composite_stack_id: Option<u64>,
event_hub: Option<Arc<EventHub>>,
}
impl Default for UndoRedoManager {
fn default() -> Self {
Self::new()
}
}
impl UndoRedoManager {
pub fn new() -> Self {
let mut stacks = HashMap::new();
stacks.insert(0, StackData::default());
UndoRedoManager {
stacks,
next_stack_id: 1,
in_progress_composite: None,
composite_nesting_level: 0,
composite_stack_id: None,
event_hub: None,
}
}
pub fn set_event_hub(&mut self, event_hub: &Arc<EventHub>) {
self.event_hub = Some(Arc::clone(event_hub));
}
pub fn undo(&mut self, stack_id: Option<u64>) -> Result<()> {
let target_stack_id = stack_id.unwrap_or(0);
let stack = self
.stacks
.get_mut(&target_stack_id)
.ok_or_else(|| anyhow!("Stack with ID {} not found", target_stack_id))?;
if let Some(mut command) = stack.undo_stack.pop() {
if let Err(e) = command.undo() {
log::error!("Undo failed, dropping command: {e}");
return Err(e);
}
stack.redo_stack.push(command);
if let Some(event_hub) = &self.event_hub {
event_hub.send_event(Event {
origin: Origin::UndoRedo(UndoRedoEvent::Undone),
ids: Vec::<EntityId>::new(),
data: None,
});
}
}
Ok(())
}
pub fn redo(&mut self, stack_id: Option<u64>) -> Result<()> {
let target_stack_id = stack_id.unwrap_or(0);
let stack = self
.stacks
.get_mut(&target_stack_id)
.ok_or_else(|| anyhow!("Stack with ID {} not found", target_stack_id))?;
if let Some(mut command) = stack.redo_stack.pop() {
if let Err(e) = command.redo() {
log::error!("Undo failed, dropping command: {e}");
return Err(e);
}
stack.undo_stack.push(command);
if let Some(event_hub) = &self.event_hub {
event_hub.send_event(Event {
origin: Origin::UndoRedo(UndoRedoEvent::Redone),
ids: Vec::<EntityId>::new(),
data: None,
});
}
}
Ok(())
}
pub fn begin_composite(&mut self, stack_id: Option<u64>) {
if self.composite_stack_id.is_some() && self.composite_stack_id != stack_id {
panic!(
"Cannot begin a composite on a different stack while another composite is in progress"
);
}
self.composite_stack_id = stack_id;
self.composite_nesting_level += 1;
if self.in_progress_composite.is_none() {
self.in_progress_composite = Some(CompositeCommand::new(stack_id));
}
if let Some(event_hub) = &self.event_hub {
event_hub.send_event(Event {
origin: Origin::UndoRedo(UndoRedoEvent::BeginComposite),
ids: Vec::<EntityId>::new(),
data: None,
});
}
}
pub fn end_composite(&mut self) {
if self.composite_nesting_level > 0 {
self.composite_nesting_level -= 1;
}
if self.composite_nesting_level == 0 {
if let Some(composite) = self.in_progress_composite.take()
&& !composite.is_empty()
{
let target_stack_id = self.composite_stack_id.unwrap_or(0);
let stack = self
.stacks
.get_mut(&target_stack_id)
.expect("Stack must exist");
stack.undo_stack.push(Box::new(composite));
stack.redo_stack.clear();
}
if let Some(event_hub) = &self.event_hub {
event_hub.send_event(Event {
origin: Origin::UndoRedo(UndoRedoEvent::EndComposite),
ids: Vec::<EntityId>::new(),
data: None,
});
}
}
}
pub fn cancel_composite(&mut self) {
if self.composite_nesting_level > 0 {
self.composite_nesting_level -= 1;
}
self.in_progress_composite = None;
self.composite_stack_id = None;
if let Some(event_hub) = &self.event_hub {
event_hub.send_event(Event {
origin: Origin::UndoRedo(UndoRedoEvent::CancelComposite),
ids: Vec::<EntityId>::new(),
data: None,
});
}
}
pub fn add_command(&mut self, command: Box<dyn UndoRedoCommand>) {
let _ = self.add_command_to_stack(command, None);
}
pub fn add_command_to_stack(
&mut self,
command: Box<dyn UndoRedoCommand>,
stack_id: Option<u64>,
) -> Result<()> {
if let Some(composite) = &mut self.in_progress_composite {
if composite.stack_id != stack_id.unwrap_or(0) {
return Err(anyhow!(
"Cannot add command to composite with different stack ID"
));
}
composite.add_command(command);
return Ok(());
}
let target_stack_id = stack_id.unwrap_or(0);
let stack = self
.stacks
.get_mut(&target_stack_id)
.ok_or_else(|| anyhow!("Stack with ID {} does not exist", target_stack_id))?;
if let Some(last_command) = stack.undo_stack.last_mut()
&& last_command.can_merge(&*command)
&& last_command.merge(&*command)
{
stack.redo_stack.clear();
return Ok(());
}
stack.undo_stack.push(command);
stack.redo_stack.clear();
Ok(())
}
pub fn can_undo(&self, stack_id: Option<u64>) -> bool {
let target_stack_id = stack_id.unwrap_or(0);
self.stacks
.get(&target_stack_id)
.map(|s| !s.undo_stack.is_empty())
.unwrap_or(false)
}
pub fn can_redo(&self, stack_id: Option<u64>) -> bool {
let target_stack_id = stack_id.unwrap_or(0);
self.stacks
.get(&target_stack_id)
.map(|s| !s.redo_stack.is_empty())
.unwrap_or(false)
}
pub fn clear_stack(&mut self, stack_id: u64) {
if let Some(stack) = self.stacks.get_mut(&stack_id) {
stack.undo_stack.clear();
stack.redo_stack.clear();
}
}
pub fn clear_all_stacks(&mut self) {
for stack in self.stacks.values_mut() {
stack.undo_stack.clear();
stack.redo_stack.clear();
}
self.in_progress_composite = None;
self.composite_nesting_level = 0;
}
pub fn create_new_stack(&mut self) -> u64 {
let id = self.next_stack_id;
self.stacks.insert(id, StackData::default());
self.next_stack_id += 1;
id
}
pub fn delete_stack(&mut self, stack_id: u64) -> Result<()> {
if stack_id == 0 {
return Err(anyhow!("Cannot delete the default stack"));
}
if self.stacks.remove(&stack_id).is_some() {
Ok(())
} else {
Err(anyhow!("Stack with ID {} does not exist", stack_id))
}
}
pub fn get_stack_size(&self, stack_id: u64) -> usize {
self.stacks
.get(&stack_id)
.map(|s| s.undo_stack.len())
.unwrap_or(0)
}
}