use crate::ops::{GraphTransaction, normalize_transaction};
pub const DEFAULT_HISTORY_LIMIT: usize = 256;
#[derive(Debug, Clone)]
pub struct GraphHistory {
limit: usize,
undo: Vec<GraphTransaction>,
redo: Vec<GraphTransaction>,
}
impl Default for GraphHistory {
fn default() -> Self {
Self::new(DEFAULT_HISTORY_LIMIT)
}
}
impl GraphHistory {
pub fn new(limit: usize) -> Self {
Self {
limit: limit.max(1),
undo: Vec::new(),
redo: Vec::new(),
}
}
pub fn clear(&mut self) {
self.undo.clear();
self.redo.clear();
}
pub fn can_undo(&self) -> bool {
!self.undo.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo.is_empty()
}
pub fn undo_len(&self) -> usize {
self.undo.len()
}
pub fn redo_len(&self) -> usize {
self.redo.len()
}
pub fn record(&mut self, tx: GraphTransaction) {
let tx = normalize_transaction(tx);
if tx.is_empty() {
return;
}
self.undo.push(tx);
self.redo.clear();
if self.undo.len() > self.limit {
let overflow = self.undo.len() - self.limit;
self.undo.drain(0..overflow);
}
}
pub fn undo<E>(
&mut self,
mut apply: impl FnMut(&GraphTransaction) -> Result<GraphTransaction, E>,
) -> Result<bool, E> {
let Some(tx) = self.undo.pop() else {
return Ok(false);
};
let inverse = tx.inverse();
match apply(&inverse) {
Ok(committed) => {
let redo_tx = normalize_transaction(committed.inverse());
if !redo_tx.is_empty() {
self.redo.push(redo_tx);
}
Ok(true)
}
Err(err) => {
self.undo.push(tx);
Err(err)
}
}
}
pub fn redo<E>(
&mut self,
mut apply: impl FnMut(&GraphTransaction) -> Result<GraphTransaction, E>,
) -> Result<bool, E> {
let Some(tx) = self.redo.pop() else {
return Ok(false);
};
match apply(&tx) {
Ok(committed) => {
let committed = normalize_transaction(committed);
if !committed.is_empty() {
self.undo.push(committed);
}
Ok(true)
}
Err(err) => {
self.redo.push(tx);
Err(err)
}
}
}
}