onedrop_engine/
history.rs

1//! History management for presets, mash-ups, and colors.
2
3use std::collections::VecDeque;
4
5/// Generic history structure with back/forward navigation.
6#[derive(Debug, Clone)]
7pub struct History<T> {
8    /// Items in history
9    items: VecDeque<T>,
10
11    /// Current index in history
12    current_index: Option<usize>,
13
14    /// Maximum size of history
15    max_size: usize,
16}
17
18impl<T: Clone> History<T> {
19    /// Create a new history with specified maximum size.
20    pub fn new(max_size: usize) -> Self {
21        Self {
22            items: VecDeque::with_capacity(max_size),
23            current_index: None,
24            max_size,
25        }
26    }
27
28    /// Push a new item to history.
29    pub fn push(&mut self, item: T) {
30        // If we're not at the end, remove all items after current
31        if let Some(idx) = self.current_index {
32            while self.items.len() > idx + 1 {
33                self.items.pop_back();
34            }
35        }
36
37        // Add new item
38        self.items.push_back(item);
39
40        // Trim if exceeds max size
41        while self.items.len() > self.max_size {
42            self.items.pop_front();
43        }
44
45        // Update current index
46        self.current_index = if self.items.is_empty() {
47            None
48        } else {
49            Some(self.items.len() - 1)
50        };
51    }
52
53    /// Go back to previous item.
54    pub fn back(&mut self) -> Option<&T> {
55        if self.items.is_empty() {
56            return None;
57        }
58
59        match self.current_index {
60            None => {
61                // Start at the end
62                self.current_index = Some(self.items.len() - 1);
63                self.items.back()
64            }
65            Some(0) => {
66                // Already at the beginning
67                self.items.front()
68            }
69            Some(idx) => {
70                // Go back one
71                self.current_index = Some(idx - 1);
72                self.items.get(idx - 1)
73            }
74        }
75    }
76
77    /// Go forward to next item.
78    pub fn forward(&mut self) -> Option<&T> {
79        if self.items.is_empty() {
80            return None;
81        }
82
83        match self.current_index {
84            None => None,
85            Some(idx) if idx >= self.items.len() - 1 => {
86                // Already at the end
87                self.items.back()
88            }
89            Some(idx) => {
90                // Go forward one
91                self.current_index = Some(idx + 1);
92                self.items.get(idx + 1)
93            }
94        }
95    }
96
97    /// Get current item.
98    pub fn current(&self) -> Option<&T> {
99        self.current_index.and_then(|idx| self.items.get(idx))
100    }
101
102    /// Check if can go back.
103    pub fn can_go_back(&self) -> bool {
104        match self.current_index {
105            None => !self.items.is_empty(),
106            Some(0) => false,
107            Some(_) => true,
108        }
109    }
110
111    /// Check if can go forward.
112    pub fn can_go_forward(&self) -> bool {
113        match self.current_index {
114            None => false,
115            Some(idx) => idx < self.items.len() - 1,
116        }
117    }
118
119    /// Get the number of items in history.
120    pub fn len(&self) -> usize {
121        self.items.len()
122    }
123
124    /// Check if history is empty.
125    pub fn is_empty(&self) -> bool {
126        self.items.is_empty()
127    }
128
129    /// Clear all history.
130    pub fn clear(&mut self) {
131        self.items.clear();
132        self.current_index = None;
133    }
134}
135
136impl<T: Clone> Default for History<T> {
137    fn default() -> Self {
138        Self::new(100) // Default max size
139    }
140}
141
142/// State for mash-up operations.
143#[derive(Debug, Clone, PartialEq)]
144pub struct MashUpState {
145    /// Source preset names
146    pub source_presets: Vec<String>,
147
148    /// Mash-up type
149    pub mash_type: MashUpType,
150
151    /// Timestamp when created
152    pub timestamp: std::time::SystemTime,
153}
154
155/// Type of mash-up operation.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum MashUpType {
158    /// Regular mash-up (warp + comp)
159    Regular,
160
161    /// Deep mash-up (all 5 bins)
162    Deep,
163}
164
165/// State for color randomization.
166#[derive(Debug, Clone, PartialEq)]
167pub struct ColorState {
168    /// Color values (RGB)
169    pub colors: Vec<[f32; 3]>,
170
171    /// Timestamp when created
172    pub timestamp: std::time::SystemTime,
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_history_push() {
181        let mut history = History::new(5);
182
183        history.push(1);
184        history.push(2);
185        history.push(3);
186
187        assert_eq!(history.len(), 3);
188        assert_eq!(history.current(), Some(&3));
189    }
190
191    #[test]
192    fn test_history_back() {
193        let mut history = History::new(5);
194
195        history.push(1);
196        history.push(2);
197        history.push(3);
198
199        assert_eq!(history.back(), Some(&2));
200        assert_eq!(history.back(), Some(&1));
201        assert_eq!(history.back(), Some(&1)); // Can't go before first
202    }
203
204    #[test]
205    fn test_history_forward() {
206        let mut history = History::new(5);
207
208        history.push(1);
209        history.push(2);
210        history.push(3);
211
212        history.back();
213        history.back();
214
215        assert_eq!(history.forward(), Some(&2));
216        assert_eq!(history.forward(), Some(&3));
217        assert_eq!(history.forward(), Some(&3)); // Can't go past last
218    }
219
220    #[test]
221    fn test_history_max_size() {
222        let mut history = History::new(3);
223
224        history.push(1);
225        history.push(2);
226        history.push(3);
227        history.push(4);
228        history.push(5);
229
230        assert_eq!(history.len(), 3);
231        assert_eq!(history.items[0], 3);
232        assert_eq!(history.items[1], 4);
233        assert_eq!(history.items[2], 5);
234    }
235
236    #[test]
237    fn test_history_can_navigate() {
238        let mut history = History::new(5);
239
240        history.push(1);
241        history.push(2);
242        history.push(3);
243
244        assert!(!history.can_go_forward());
245        assert!(history.can_go_back());
246
247        history.back();
248
249        assert!(history.can_go_forward());
250        assert!(history.can_go_back());
251
252        history.back();
253
254        assert!(history.can_go_forward());
255        assert!(!history.can_go_back());
256    }
257
258    #[test]
259    fn test_history_clear() {
260        let mut history = History::new(5);
261
262        history.push(1);
263        history.push(2);
264        history.push(3);
265
266        history.clear();
267
268        assert!(history.is_empty());
269        assert_eq!(history.current(), None);
270    }
271}