Skip to main content

spru_util/rotating/
mod.rs

1pub mod error;
2
3use std::marker::PhantomData;
4
5use derive_where::derive_where;
6use spru::common::error::AnyResult;
7use tagset::tagset;
8use telety::telety;
9
10use crate::cloned;
11
12/// A 'wheel' of values which loops around at both ends. Can represent turn order, round phases,
13#[telety(crate::rotating)]
14#[derive(Debug, Clone)]
15#[derive_where(Default)]
16#[derive(serde::Serialize, serde::Deserialize)]
17pub struct Rotating<T> {
18    items: Vec<T>,
19    position: usize,
20}
21
22impl<T> Rotating<T> {
23    pub fn as_slice(&self) -> &[T] {
24        self.items.as_slice()
25    }
26
27    /// The index of the selected value. [None] if this item has no elements.
28    pub fn position(&self) -> Option<usize> {
29        if self.items.is_empty() {
30            None
31        } else {
32            Some(self.position)
33        }
34    }
35
36    /// The current selected value. [None] if this item has no elements.
37    pub fn current(&self) -> Option<&T> {
38        self.items.get(self.position)
39    }
40
41    pub fn len(&self) -> usize {
42        self.items.len()
43    }
44
45    pub fn is_empty(&self) -> bool {
46        self.items.is_empty()
47    }
48
49    pub fn expect<U>(&self, expected_value: &U) -> Result<&T, error::Expected>
50    where
51        T: PartialEq<U>,
52    {
53        if let Some(current) = self.current()
54            && current == expected_value
55        {
56            return Ok(current);
57        }
58        Err(error::Expected)
59    }
60}
61
62pub fn create<T>(items: Vec<T>, position: usize) -> Create<T> {
63    assert!(items.is_empty() || position < items.len());
64
65    cloned::create(Rotating { items, position })
66}
67
68pub fn default<T>() -> Create<T> {
69    create(vec![], 0)
70}
71
72pub fn rotate<T>(reverse: bool) -> Rotate<T> {
73    Rotate {
74        reverse,
75        _p: PhantomData,
76    }
77}
78
79pub fn set_position<T>(position: usize) -> SetPosition<T> {
80    SetPosition {
81        position,
82        _p: PhantomData,
83    }
84}
85
86pub fn insert<T>(position: usize, item: T) -> Insert<T> {
87    Insert {
88        position,
89        item,
90        set_to_inserted: false,
91    }
92}
93
94pub fn remove<T>(position: usize) -> Remove<T> {
95    Remove {
96        position,
97        _p: PhantomData,
98    }
99}
100
101pub fn destroy<T>() -> Destroy<T> {
102    cloned::destroy()
103}
104
105#[telety(crate::rotating)]
106#[tagset(Create<T>)]
107#[tagset(Rotate<T>)]
108#[tagset(SetPosition<T>)]
109#[tagset(Insert<T>)]
110#[tagset(Remove<T>)]
111#[tagset(Destroy<T>)]
112#[tagset(reserved(..16))]
113pub struct Actions<T>;
114
115pub type Create<T> = cloned::Create<Rotating<T>>;
116
117pub type Destroy<T> = cloned::Destroy<Rotating<T>>;
118
119#[derive_where(Debug, Clone, Default, Serialize, Deserialize)]
120#[derive(spru::action::Update)]
121#[must_use]
122pub struct Rotate<T> {
123    reverse: bool,
124    _p: PhantomData<T>,
125}
126
127impl<T> spru::action::Update for Rotate<T>
128where
129    T: Clone,
130{
131    type T = Rotating<T>;
132    type Undo = Self;
133
134    #[allow(refining_impl_trait)]
135    fn update(&self, value: &mut Self::T) -> AnyResult<Option<Self::Undo>> {
136        let Self { reverse, _p } = *self;
137
138        let len = value.items.len();
139        if len > 0 {
140            let old_index = value.position;
141            let new_index = if reverse {
142                if old_index == 0 {
143                    len - 1
144                } else {
145                    old_index - 1
146                }
147            } else if old_index == len - 1 {
148                0
149            } else {
150                old_index + 1
151            };
152
153            if old_index != new_index {
154                value.position = new_index;
155                return Ok(Some(Self {
156                    reverse: !reverse,
157                    _p,
158                }));
159            }
160        }
161
162        Ok(None)
163    }
164}
165
166#[derive_where(Debug, Clone, Default, Serialize, Deserialize)]
167#[derive(spru::action::Update)]
168#[must_use]
169pub struct SetPosition<T> {
170    position: usize,
171    _p: PhantomData<T>,
172}
173
174impl<T> spru::action::Update for SetPosition<T>
175where
176    T: Clone,
177{
178    type T = Rotating<T>;
179    type Undo = Self;
180
181    #[allow(refining_impl_trait)]
182    fn update(&self, value: &mut Self::T) -> AnyResult<Option<Self::Undo>> {
183        let Self { mut position, _p } = *self;
184
185        if position >= value.items.len() {
186            Err(Error::InvalidPosition(position, value.items.len()).into())
187        } else if position != value.position {
188            std::mem::swap(&mut value.position, &mut position);
189
190            Ok(Some(Self { position, _p }))
191        } else {
192            Ok(None)
193        }
194    }
195}
196
197#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, spru::action::Update)]
198#[must_use]
199pub struct Insert<T> {
200    position: usize,
201    item: T,
202    set_to_inserted: bool,
203}
204
205impl<T> spru::action::Update for Insert<T>
206where
207    T: Clone,
208{
209    type T = Rotating<T>;
210    type Undo = Remove<T>;
211
212    #[allow(refining_impl_trait)]
213    fn update(&self, value: &mut Self::T) -> AnyResult<Self::Undo> {
214        let Self {
215            position,
216            ref item,
217            set_to_inserted,
218        } = *self;
219
220        if position <= value.items.len() {
221            value.items.insert(position, item.clone());
222
223            if set_to_inserted {
224                // This is the undo of a Remove of the current item
225                value.position = position;
226            } else if value.position >= position && value.items.len() > 1 {
227                // If this is inserted before the current selection (and we had an existing selection
228                // because we weren't empty), bump the index to maintain the same selected item
229                value.position += 1;
230            }
231
232            Ok(Remove {
233                position,
234                _p: PhantomData,
235            })
236        } else {
237            Err(Error::InvalidPosition(position, value.items.len()).into())
238        }
239    }
240}
241
242#[derive_where(Debug, Clone, Default, Serialize, Deserialize)]
243#[derive(spru::action::Update)]
244#[must_use]
245pub struct Remove<T> {
246    position: usize,
247    _p: PhantomData<fn(T) -> T>,
248}
249
250impl<T> spru::action::Update for Remove<T>
251where
252    T: Clone,
253{
254    type T = Rotating<T>;
255    type Undo = Insert<T>;
256
257    #[allow(refining_impl_trait)]
258    fn update(&self, value: &mut Self::T) -> AnyResult<Self::Undo> {
259        let Self { position, _p } = *self;
260
261        if position < value.items.len() {
262            let set_to_inserted = position == value.position;
263            if position < value.position {
264                value.position -= 1;
265            }
266            let item = value.items.remove(position);
267            if value.position == value.items.len() {
268                value.position = 0;
269            }
270            Ok(Insert {
271                position,
272                item,
273                set_to_inserted,
274            })
275        } else {
276            Err(Error::InvalidPosition(position, value.items.len()).into())
277        }
278    }
279}
280
281#[derive(Debug, Clone, crate::FromInfallible, thiserror::Error)]
282#[non_exhaustive]
283pub enum Error {
284    #[error("Invalid position {0}, len() is {1}")]
285    InvalidPosition(usize, usize),
286}