Skip to main content

tui_additions/framework/
state.rs

1use crate::utils;
2
3use super::{FrameworkError, FrameworkItem};
4use ratatui::layout::{Constraint, Direction, Layout, Rect};
5
6/// Contains an item
7#[derive(Clone)]
8pub struct RowItem {
9    /// The actual item Boxed
10    pub item: Box<dyn FrameworkItem>,
11    /// Width of the item
12    pub width: Constraint,
13}
14
15/// Contains a row of objects
16#[derive(Clone)]
17pub struct Row {
18    /// All the items in the row
19    pub items: Vec<RowItem>,
20    /// If the row should be centered or not
21    pub centered: bool,
22    /// The height of the row
23    pub height: Constraint,
24}
25
26/// Contains the items and the layout of the TUI
27#[derive(Clone)]
28pub struct State(pub Vec<Row>);
29
30impl State {
31    /// Generate selectables which is a 2D vector of items that can be selected
32    ///
33    /// `(usize, usize)` maps to the `(x, y)` position to `State.0`, items that are not selectable
34    /// are excluded
35    pub fn selectables(&self) -> Vec<Vec<(usize, usize)>> {
36        let mut selectables = Vec::new();
37
38        self.0.iter().enumerate().for_each(|(y, row)| {
39            let mut row_selectables = Vec::new();
40            row.items.iter().enumerate().for_each(|(x, row_item)| {
41                if row_item.item.selectable() {
42                    row_selectables.push((x, y));
43                }
44            });
45            if !row_selectables.is_empty() {
46                selectables.push(row_selectables);
47            }
48        });
49
50        selectables
51    }
52
53    /// Return chunks as 2D array of rects
54    pub fn get_chunks(&self, area: Rect) -> Vec<Vec<Rect>> {
55        // chunks
56        let mut row_constraints = vec![Constraint::Length(0)];
57        row_constraints.extend(self.0.iter().map(|row| row.height));
58        row_constraints.push(Constraint::Length(0));
59
60        let row_constraints_length = row_constraints.len() - 2;
61
62        Layout::default()
63            .direction(Direction::Vertical)
64            .constraints(row_constraints)
65            .split(area)
66            .iter()
67            .skip(1)
68            .take(row_constraints_length)
69            .zip(self.0.iter().map(|row| {
70                let begin_length = if row.centered {
71                    Constraint::Length(
72                        (area.width
73                            - row
74                                .items
75                                .iter()
76                                .map(|item| utils::constraint_apply(item.width, area.width))
77                                .sum::<u16>())
78                            / 2,
79                    )
80                } else {
81                    Constraint::Length(0)
82                };
83
84                let mut out = vec![begin_length];
85                out.extend(row.items.iter().map(|item| item.width));
86                out.push(Constraint::Length(0));
87                out
88            }))
89            .map(|(row_chunk, constraints)| {
90                let constraints_length = constraints.len() - 2;
91
92                Layout::default()
93                    .direction(Direction::Horizontal)
94                    .constraints(constraints)
95                    .split(*row_chunk)
96                    .iter()
97                    .skip(1)
98                    .take(constraints_length)
99                    .copied()
100                    .collect()
101            })
102            .collect::<Vec<_>>()
103    }
104
105    /// Get reference to item with x and y value
106    pub fn get(&self, x: usize, y: usize) -> &dyn FrameworkItem {
107        &*self.0[y].items[x].item
108    }
109
110    /// Get mutable reference to item with x and y value
111    pub fn get_mut(&mut self, x: usize, y: usize) -> &mut Box<dyn FrameworkItem> {
112        &mut self.0[y].items[x].item
113    }
114}
115
116/// State of cursor
117///
118/// The 2 numbers represent the x and y in `Framework.selectables` rather than `State.0`
119#[derive(Clone, Copy, PartialEq, Eq)]
120pub enum CursorState {
121    /// Nothing is selected
122    None,
123    /// Cursor is hovering on an item
124    Hover(usize, usize),
125    /// An item is selected
126    Selected(usize, usize),
127}
128
129impl Default for CursorState {
130    fn default() -> Self {
131        Self::None
132    }
133}
134
135impl CursorState {
136    pub fn is_selected(&self) -> bool {
137        matches!(self, Self::Selected(_, _))
138    }
139
140    pub fn is_hover(&self) -> bool {
141        matches!(self, Self::Hover(_, _))
142    }
143
144    pub fn is_none(&self) -> bool {
145        self == &Self::None
146    }
147}
148
149impl CursorState {
150    /// Try select an item, will not work if an item is already selected or the cursor is not
151    /// hovering on anything
152    pub fn select(&mut self) -> Result<(), FrameworkError> {
153        match self {
154            Self::Hover(x, y) => *self = Self::Selected(*x, *y),
155            _ => return Err(FrameworkError::CursorStateMismatch),
156        }
157
158        Ok(())
159    }
160
161    /// Try deselect an item, will not work if no items are selected
162    pub fn deselect(&mut self) -> Result<(), FrameworkError> {
163        match self {
164            Self::Selected(x, y) => *self = Self::Hover(*x, *y),
165            _ => return Err(FrameworkError::CursorStateMismatch),
166        }
167
168        Ok(())
169    }
170}
171
172impl CursorState {
173    pub fn to_hover(location: (usize, usize)) -> Self {
174        Self::Hover(location.0, location.1)
175    }
176
177    pub fn to_selected(location: (usize, usize)) -> Self {
178        Self::Selected(location.0, location.1)
179    }
180
181    pub fn hover(&self, selectables: &[Vec<(usize, usize)>]) -> Option<(usize, usize)> {
182        match self {
183            Self::Hover(x, y) if !selectables.is_empty() => {
184                Some(Self::selectables_to_coors(selectables, (*x, *y)))
185            }
186            _ => None,
187        }
188    }
189
190    pub fn selected(&self, selectables: &[Vec<(usize, usize)>]) -> Option<(usize, usize)> {
191        match self {
192            Self::Selected(x, y) if !selectables.is_empty() => {
193                Some(Self::selectables_to_coors(selectables, (*x, *y)))
194            }
195            _ => None,
196        }
197    }
198
199    fn selectables_to_coors(
200        selectables: &[Vec<(usize, usize)>],
201        location: (usize, usize),
202    ) -> (usize, usize) {
203        let (location_x, location_y) = location;
204
205        selectables[location_y][location_x]
206    }
207}
208
209impl CursorState {
210    /// Move in the corresponding direction
211    pub fn r#move(
212        &mut self,
213        direction: FrameworkDirection,
214        selectables: &[Vec<(usize, usize)>],
215    ) -> Result<(), FrameworkError> {
216        match direction {
217            FrameworkDirection::Up => self.up(),
218            FrameworkDirection::Down => self.down(),
219            FrameworkDirection::Left => self.left(),
220            FrameworkDirection::Right => self.right(),
221        }?;
222
223        self.move_check(selectables);
224
225        Ok(())
226    }
227
228    fn move_check(&mut self, selectables: &[Vec<(usize, usize)>]) {
229        if let Self::Hover(x, y) = self {
230            if selectables.is_empty() {
231                *x = 0;
232                *y = 0;
233                return;
234            }
235            let y_max = selectables.len() - 1;
236            if *y > y_max {
237                *y = y_max;
238            }
239
240            let x_max = selectables[*y].len() - 1;
241            if *x > x_max {
242                *x = x_max;
243            }
244        } else {
245            unreachable!("move_check is only called after a hovering cursor is moved, when cursor is at hover state")
246        }
247    }
248
249    fn left(&mut self) -> Result<(), FrameworkError> {
250        match self {
251            Self::Hover(x, _) => {
252                if *x != 0 {
253                    *x -= 1
254                }
255            }
256            Self::None => *self = Self::Hover(0, 0),
257            Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
258        }
259
260        Ok(())
261    }
262
263    fn right(&mut self) -> Result<(), FrameworkError> {
264        match self {
265            Self::Hover(x, _) => *x += 1,
266            Self::None => *self = Self::Hover(usize::MAX, 0),
267            Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
268        }
269
270        Ok(())
271    }
272
273    fn up(&mut self) -> Result<(), FrameworkError> {
274        match self {
275            Self::Hover(_, y) => {
276                if *y != 0 {
277                    *y -= 1
278                }
279            }
280            Self::None => *self = Self::Hover(0, 0),
281            Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
282        }
283
284        Ok(())
285    }
286
287    fn down(&mut self) -> Result<(), FrameworkError> {
288        match self {
289            Self::Hover(_, y) => *y += 1,
290            Self::None => *self = Self::Hover(0, usize::MAX),
291            Self::Selected(_, _) => return Err(FrameworkError::MoveSelected),
292        }
293
294        Ok(())
295    }
296}
297
298/// Used to represent direction in this crate
299#[derive(Clone, Copy)]
300pub enum FrameworkDirection {
301    Up,
302    Down,
303    Left,
304    Right,
305}
306
307/// Passed into the `FrameworkItem` trait functions for info of the item
308#[derive(Clone, Copy)]
309pub struct ItemInfo {
310    pub selected: bool,
311    pub hover: bool,
312    pub x: usize,
313    pub y: usize,
314}