aboutsummaryrefslogtreecommitdiff
path: root/src/undo.rs
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-03-12 14:54:55 +0000
committerAkshay <[email protected]>2021-03-12 14:54:55 +0000
commit9f0dc65dabff2a1e443199ed68292398dcb390b1 (patch)
tree17b29a4315df3d5caaa967b079c3d459063c0de9 /src/undo.rs
init
Diffstat (limited to 'src/undo.rs')
-rw-r--r--src/undo.rs133
1 files changed, 133 insertions, 0 deletions
diff --git a/src/undo.rs b/src/undo.rs
new file mode 100644
index 0000000..2249fe7
--- /dev/null
+++ b/src/undo.rs
@@ -0,0 +1,133 @@
1#[derive(Copy, Clone, Debug)]
2pub struct ModifyRecord {
3 pub point: (i32, i32),
4 pub old_val: bool,
5 pub val: bool,
6}
7
8impl ModifyRecord {
9 pub fn new(point: (i32, i32), old_val: bool, val: bool) -> Self {
10 ModifyRecord {
11 point,
12 old_val,
13 val,
14 }
15 }
16}
17
18pub enum OpKind {
19 Undo,
20 Redo,
21}
22
23pub type Operation = Vec<ModifyRecord>;
24
25#[derive(Debug)]
26pub struct UndoStack<T> {
27 operations: Vec<T>,
28 position: Option<u32>,
29}
30
31impl<T> UndoStack<T>
32where
33 T: Clone,
34{
35 pub fn new() -> Self {
36 Self {
37 operations: Vec::with_capacity(64),
38 position: None,
39 }
40 }
41
42 pub fn push(&mut self, op: T) {
43 if let Some(p) = self.position {
44 // remove all operations past the newly pushed operation
45 for _ in 1 + (p as usize)..self.operations.len() {
46 self.operations.pop();
47 }
48 // advance position
49 self.position = Some(p + 1);
50 // add new operation
51 self.operations.push(op);
52 } else {
53 // empty ops list or undone till start of stack
54 // remove all operations past the newly pushed operation
55 self.operations.clear();
56 // advance position
57 self.position = Some(0);
58 // add new operation
59 self.operations.push(op);
60 }
61 }
62
63 pub fn undo(&mut self) -> Option<T> {
64 if let Some(p) = self.position {
65 self.position = p.checked_sub(1);
66 // we want to return a clone and not a reference because push deletes the item
67 return Some(self.operations[p as usize].clone());
68 }
69 return None;
70 }
71
72 pub fn redo(&mut self) -> Option<T> {
73 if let Some(p) = self.position {
74 if p < self.operations.len() as u32 - 1 {
75 self.position = Some(p + 1);
76 return Some(self.operations[1 + p as usize].clone());
77 }
78 } else if !self.operations.is_empty() {
79 self.position = Some(0);
80 return Some(self.operations[0].clone());
81 }
82 return None;
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 fn setup() -> UndoStack<u32> {
91 let mut stack = UndoStack::new();
92 stack.push(10);
93 stack.push(5);
94 stack.push(2);
95 stack
96 }
97
98 #[test]
99 fn undo_works() {
100 let mut stack = setup();
101 assert_eq!(stack.undo(), Some(2));
102 assert_eq!(stack.undo(), Some(5));
103 assert_eq!(stack.undo(), Some(10));
104 }
105 #[test]
106 fn redo_works() {
107 let mut stack = setup();
108 stack.undo();
109 stack.undo();
110 stack.undo();
111 assert_eq!(stack.redo(), Some(10));
112 assert_eq!(stack.redo(), Some(5));
113 assert_eq!(stack.redo(), Some(2));
114 }
115
116 #[test]
117 fn undo_push_redo() {
118 let mut stack = setup();
119 stack.undo();
120 stack.push(16);
121 assert_eq!(stack.redo(), None);
122 assert_eq!(stack.undo(), Some(16));
123 }
124
125 #[test]
126 fn stack_identity() {
127 let mut stack = setup();
128 stack.undo();
129 stack.redo();
130 stack.undo();
131 assert_eq!(stack.operations, setup().operations);
132 }
133}