#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/readme.md"))]
#![warn(clippy::std_instead_of_core, clippy::std_instead_of_alloc)]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
use core::fmt::Debug;
#[cfg(feature = "std")]
pub use std::{print, println};
extern crate alloc;
use alloc::{vec, vec::Vec};
#[derive(PartialEq, Debug)]
enum Kind<T> {
Group,
Single(T),
}
pub trait Undoable
where
Self: Clone + PartialEq,
{
type ProjectType;
fn restore(self, target: &mut Self::ProjectType) -> Self;
}
pub struct UndoStack<T>
where
T: Undoable,
{
future_stack: Vec<Kind<T>>,
past_stack: Vec<Kind<T>>,
undo_buffer: Option<T>,
open_group: bool,
pub verbose: bool,
}
impl<T> UndoStack<T>
where
T: Undoable,
{
pub fn new() -> Self {
Self {
future_stack: vec![],
past_stack: vec![],
undo_buffer: None,
open_group: false,
verbose: true,
}
}
pub fn push(&mut self, undo_value: T) {
if let Some(Kind::Single(top_value)) = self.past_stack.last() {
if *top_value == undo_value {
return;
}
}
self.past_stack.push(Kind::Single(undo_value));
self.future_stack.clear();
}
pub fn start_group(&mut self) {
if !self.open_group {
self.past_stack.push(Kind::Group);
self.open_group = true;
} else {
self.log("Warning, can't open new group before closing current one.");
}
}
pub fn finish_group(&mut self) {
if self.open_group {
self.past_stack.push(Kind::Group);
self.open_group = false;
} else {
self.log("Warning, no open groups to close.");
}
}
pub fn reopen_group(&mut self) {
if let Some(kind) = self.past_stack.pop() {
match kind {
Kind::Group => {
if self.open_group {
self.log("Warning, last value is already an open group. Skipping.");
} else {
self.open_group = true;
}
}
Kind::Single(value) => {
self.log("Warning, last value is not a group. Skipping.");
self.past_stack.push(Kind::Single(value));
}
}
}
}
pub fn undo(&mut self, project: &mut T::ProjectType) -> Option<&T> {
self.move_undo_value(project, false)
}
pub fn redo(&mut self, project: &mut T::ProjectType) -> Option<&T> {
self.move_undo_value(project, true)
}
fn move_undo_value(&mut self, project: &mut T::ProjectType, is_redo: bool) -> Option<&T> {
let from_stack: &mut Vec<Kind<T>>;
let to_stack: &mut Vec<Kind<T>>;
if is_redo {
from_stack = &mut self.future_stack;
to_stack = &mut self.past_stack;
} else {
from_stack = &mut self.past_stack;
to_stack = &mut self.future_stack;
};
match from_stack.pop() {
Some(kind) => match kind {
Kind::Group => {
to_stack.push(Kind::Group);
loop {
let next_value = from_stack.pop();
match next_value {
Some(Kind::Group) => {
to_stack.push(Kind::Group);
break;
}
Some(Kind::Single(value)) => {
let old_value = value.restore(project);
to_stack.push(Kind::Single(old_value));
}
None => {
#[cfg(feature = "std")]
{
println!("UndoStack: Warning, Undo failure due to incomplete Undo Group");
}
break;
}
}
}
}
Kind::Single(value) => {
let old_value = value.restore(project);
to_stack.push(Kind::Single(old_value));
}
},
None => {
#[cfg(feature = "std")]
{
if self.verbose {
println!("UndoStack: No value to undo/redo.");
}
}
}
}
to_stack.last().map_or_else(
|| None,
|kind| match kind {
Kind::Group => None,
Kind::Single(value) => Some(value),
},
)
}
pub fn clear(&mut self) {
self.undo_buffer = None;
self.past_stack.clear();
self.future_stack.clear();
self.open_group = false;
}
pub fn is_empty(&self) -> bool {
self.past_stack.is_empty() && self.future_stack.is_empty()
}
pub fn buffer_is_empty(&self) -> bool {
self.undo_buffer.is_none()
}
pub fn start_buffer(&mut self, value: T) {
if let Some(ref value) = self.undo_buffer {
self.push(value.clone());
self.log("Warning, previous undo value was commited automatically");
}
self.log("Initiating undo buffer...");
self.undo_buffer = Some(value);
}
pub fn finish_buffer(&mut self, final_value: T) {
if let Some(ref value) = self.undo_buffer {
if *value != final_value {
self.push(value.clone());
} else {
self.log("Skipping commit, values don't differ.");
}
self.undo_buffer = None;
} else {
self.log("Warning, buffer is empty and can't be committed.");
}
}
pub fn buffer(&self) -> Option<T> {
self.undo_buffer.clone()
}
fn log(&self, _text: &str) {
#[cfg(feature = "std")]
{
if self.verbose {
println!("UndoStack: {}", _text);
}
}
}
pub fn past_stack_len(&self) -> usize {
self.past_stack.len()
}
pub fn future_stack_len(&self) -> usize {
self.future_stack.len()
}
}
impl<T> Default for UndoStack<T>
where
T: Undoable,
{
fn default() -> Self {
Self::new()
}
}
impl<T> Debug for UndoStack<T>
where
T: Debug + Undoable,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Past stack:\n{:?}\nFuture stack:\n{:?}",
self.past_stack, self.future_stack
)
}
}