Skip to main content

promkit_widgets/
cursor.rs

1pub mod len;
2use len::Len;
3
4/// A generic cursor structure for navigating and manipulating collections.
5/// It maintains a position within the collection
6/// and provides methods to move forward, backward,
7/// to the head, and to the tail of the collection.
8/// It requires the collection to implement the `Len` trait.
9/// The `cyclic` parameter allows the cursor to cycle through the collection.
10#[derive(Clone)]
11pub struct Cursor<C> {
12    contents: C,
13    position: usize,
14    cyclic: bool,
15}
16
17impl<C: Len> Cursor<C> {
18    /// Constructs a new `Cursor` with the given contents, an initial position, and a cyclic flag.
19    /// If the given position is greater than the length of the contents,
20    /// it sets the position to the last item of the contents.
21    /// If `cyclic` is true, the cursor can cycle through the collection.
22    pub fn new(contents: C, position: usize, cyclic: bool) -> Self {
23        let adjusted_position = if position >= contents.len() {
24            contents.len().saturating_sub(1)
25        } else {
26            position
27        };
28
29        Self {
30            contents,
31            position: adjusted_position,
32            cyclic,
33        }
34    }
35
36    /// Returns a reference to the contents.
37    pub fn contents(&self) -> &C {
38        &self.contents
39    }
40
41    /// Returns a mutable reference to the contents.
42    pub fn contents_mut(&mut self) -> &mut C {
43        &mut self.contents
44    }
45
46    /// Replaces the contents with new contents and adjusts the position if necessary.
47    pub fn replace_contents(&mut self, contents: C) {
48        self.contents = contents;
49        if self.position >= self.contents.len() {
50            self.position = self.contents.len().saturating_sub(1);
51        }
52    }
53
54    /// Returns the current position of the cursor.
55    pub fn position(&self) -> usize {
56        self.position
57    }
58
59    pub fn shift(&mut self, backward: usize, forward: usize) -> bool {
60        let len = self.contents.len();
61        if self.cyclic {
62            let total_move = forward as isize - backward as isize;
63            let new_position =
64                (self.position as isize + total_move).rem_euclid(len as isize) as usize;
65            self.position = new_position;
66            true
67        } else if backward > self.position {
68            false
69        } else {
70            let new_position = self.position - backward;
71            if new_position + forward < len {
72                self.position = new_position + forward;
73                true
74            } else {
75                false
76            }
77        }
78    }
79
80    /// Moves the cursor one position backward, if possible. Returns `true` if successful.
81    /// If `cyclic` is true and the cursor is at the head, it moves to the tail.
82    pub fn backward(&mut self) -> bool {
83        self.shift(1, 0)
84    }
85
86    /// Moves the cursor one position forward, if possible. Returns `true` if successful.
87    /// If `cyclic` is true and the cursor is at the tail, it moves to the head.
88    pub fn forward(&mut self) -> bool {
89        self.shift(0, 1)
90    }
91
92    /// Moves the cursor to the head (start) of the contents.
93    pub fn move_to_head(&mut self) {
94        self.move_to(0);
95    }
96
97    /// Checks if the cursor is at the head (start) of the contents.
98    pub fn is_head(&self) -> bool {
99        self.position == 0
100    }
101
102    /// Moves the cursor to the tail (end) of the contents.
103    pub fn move_to_tail(&mut self) {
104        self.move_to(self.contents.len().saturating_sub(1));
105    }
106
107    /// Checks if the cursor is at the tail (end) of the contents.
108    pub fn is_tail(&self) -> bool {
109        self.position == self.contents.len().saturating_sub(1)
110    }
111
112    pub fn move_to(&mut self, position: usize) -> bool {
113        if position < self.contents.len() {
114            self.position = position;
115            true
116        } else {
117            false
118        }
119    }
120}
121
122#[cfg(test)]
123mod test {
124    use super::*;
125
126    mod shift {
127        use super::*;
128
129        #[test]
130        fn test_cyclic_forward() {
131            let mut cursor = Cursor::new(vec!["a", "b", "c"], 0, true);
132            assert!(cursor.shift(0, 2)); // 0 -> 2
133            assert_eq!(cursor.position(), 2);
134        }
135
136        #[test]
137        fn test_cyclic_backward() {
138            let mut cursor = Cursor::new(vec!["a", "b", "c"], 2, true);
139            assert!(cursor.shift(2, 0)); // 2 -> 0
140            assert_eq!(cursor.position(), 0);
141        }
142
143        #[test]
144        fn test_cyclic_wrap_around() {
145            let mut cursor = Cursor::new(vec!["a", "b", "c"], 2, true);
146            assert!(cursor.shift(0, 1)); // 2 -> 0 (wrap around)
147            assert_eq!(cursor.position(), 0);
148        }
149
150        #[test]
151        fn test_non_cyclic_forward_fail() {
152            let mut cursor = Cursor::new(vec!["a", "b", "c"], 2, false);
153            assert!(!cursor.shift(0, 1)); // 2 -> fail, no wrap around
154        }
155
156        #[test]
157        fn test_non_cyclic_backward_fail() {
158            let mut cursor = Cursor::new(vec!["a", "b", "c"], 0, false);
159            assert!(!cursor.shift(1, 0)); // 0 -> fail, can't move backward
160        }
161
162        #[test]
163        fn test_non_cyclic_forward_success() {
164            let mut cursor = Cursor::new(vec!["a", "b", "c"], 1, false);
165            assert!(cursor.shift(0, 1)); // 1 -> 2
166            assert_eq!(cursor.position(), 2);
167        }
168
169        #[test]
170        fn test_non_cyclic_backward_success() {
171            let mut cursor = Cursor::new(vec!["a", "b", "c"], 2, false);
172            assert!(cursor.shift(1, 0)); // 2 -> 1
173            assert_eq!(cursor.position(), 1);
174        }
175    }
176
177    mod backward {
178        use super::*;
179
180        #[test]
181        fn test() {
182            let mut b = Cursor::new(vec!["a", "b", "c"], 0, false);
183            assert!(!b.backward());
184            b.position = 1;
185            assert!(b.backward());
186        }
187    }
188
189    mod forward {
190        use super::*;
191
192        #[test]
193        fn test() {
194            let mut b = Cursor::new(vec!["a", "b", "c"], 0, false);
195            assert!(b.forward());
196            b.position = b.contents.len() - 1;
197            assert!(!b.forward());
198        }
199    }
200}