tui_additions/framework/
framework.rs

1use std::{any::Any, collections::HashMap, error::Error, fmt::Display};
2
3use crossterm::event::KeyEvent;
4use ratatui::{layout::Rect, Frame};
5
6use super::{
7    CursorState, FrameworkClean, FrameworkData, FrameworkDirection, FrameworkHistory, ItemInfo,
8    State,
9};
10
11/// Struct for a declarative TUI framework
12///
13/// Copy & paste examples can be found
14/// [here](https://github.com/siriusmart/tui-additions/tree/master/examples/framework)
15
16#[derive(Clone)]
17pub struct Framework {
18    /// Selectable items, auto generated when `state` is set with `new()` or `set_state()`
19    pub selectables: Vec<Vec<(usize, usize)>>,
20    /// Global data store for the framework
21    pub data: FrameworkData,
22    /// Defines the layout of items on screen
23    pub state: State,
24    /// The state and position of cursor
25    pub cursor: CursorState,
26    /// Stores saved states
27    pub history: Vec<FrameworkHistory>,
28    /// Stores the area of the previous frame
29    pub frame_area: Option<Rect>,
30}
31
32impl Framework {
33    /// Clears `self.history`
34    pub fn clear_history(&mut self) {
35        self.history.clear();
36    }
37
38    /// Save current state
39    pub fn push_history(&mut self) {
40        self.history.push(FrameworkHistory {
41            selectables: self.selectables.clone(),
42            data: self.data.state.clone(),
43            state: self.state.clone(),
44            cursor: self.cursor,
45        });
46    }
47
48    /// Removes the last history and returns it
49    pub fn pop_history(&mut self) -> Option<FrameworkHistory> {
50        self.history.pop()
51    }
52
53    /// Revert self to last save (if there is)
54    pub fn revert_last_history(&mut self) -> Result<(), FrameworkError> {
55        let history = match self.history.pop() {
56            None => return Err(FrameworkError::NoSuchSave),
57            Some(history) => history,
58        };
59
60        self.selectables = history.selectables;
61        self.data.state = history.data;
62        self.state = history.state;
63        self.cursor = history.cursor;
64
65        Ok(())
66    }
67
68    /// Revert self to history at index
69    pub fn revert_history(&mut self, index: usize) -> Result<(), FrameworkError> {
70        if index >= self.history.len() {
71            return Err(FrameworkError::NoSuchSave);
72        }
73
74        let history = self.history.remove(index);
75
76        self.selectables = history.selectables;
77        self.data.state = history.data;
78        self.state = history.state;
79        self.cursor = history.cursor;
80
81        Ok(())
82    }
83}
84
85impl Framework {
86    pub fn is_selected(&self) -> bool {
87        self.cursor.is_selected()
88    }
89
90    pub fn is_hover(&self) -> bool {
91        self.cursor.is_hover()
92    }
93
94    pub fn is_none(&self) -> bool {
95        self.cursor.is_none()
96    }
97}
98
99impl Framework {
100    /// Create a new Framework struct
101    pub fn new(state: State) -> Self {
102        Self {
103            selectables: state.selectables(),
104            data: FrameworkData::default(),
105            state,
106            frame_area: None,
107            cursor: CursorState::default(),
108            history: Vec::new(),
109        }
110    }
111
112    /// Set `self.state` and also update `self.selectables`
113    pub fn set_state(&mut self, state: State) {
114        self.state = state;
115        self.selectables = self.state.selectables();
116    }
117
118    /// Render every item to screen
119    pub fn render(&mut self, frame: &mut Frame) {
120        let area = frame.area();
121        self.frame_area = Some(area);
122
123        let chunks = self.state.get_chunks(area);
124
125        let selected = self.cursor.selected(&self.selectables);
126        let hover = self.cursor.hover(&self.selectables);
127
128        // actually rendering the stuff
129        self.render_raw(frame, &chunks, selected, hover, false);
130        self.render_raw(frame, &chunks, selected, hover, true);
131    }
132
133    /// Render to screen with more controls
134    pub fn render_raw(
135        &mut self,
136        frame: &mut Frame,
137        chunks: &[Vec<Rect>],
138        selected: Option<(usize, usize)>,
139        hover: Option<(usize, usize)>,
140        popup_render: bool,
141    ) {
142        let (mut frameworkclean, state) = self.split_clean();
143
144        for (y, (row, row_chunks)) in state.0.iter_mut().zip(chunks.iter()).enumerate() {
145            for (x, (row_item, item_chunk)) in
146                row.items.iter_mut().zip(row_chunks.iter()).enumerate()
147            {
148                row_item.item.render(
149                    frame,
150                    &mut frameworkclean,
151                    *item_chunk,
152                    // Some((x, y)) == selected,
153                    // Some((x, y)) == hover,
154                    popup_render,
155                    ItemInfo {
156                        selected: Some((x, y)) == selected,
157                        hover: Some((x, y)) == hover,
158                        x,
159                        y,
160                    },
161                );
162            }
163        }
164    }
165
166    /// Render only one item
167    pub fn render_only(&mut self, frame: &mut Frame, x: usize, y: usize) {
168        let chunk = self.state.get_chunks(frame.area())[y][x];
169
170        let selected = self.cursor.selected(&self.selectables);
171        let hover = self.cursor.hover(&self.selectables);
172
173        self.render_only_raw(frame, x, y, chunk, false, selected, hover);
174        self.render_only_raw(frame, x, y, chunk, true, selected, hover);
175    }
176
177    /// Render multiple items
178    ///
179    /// Location is in a format of `Vec<(x, y)>`
180    pub fn render_only_multiple(&mut self, frame: &mut Frame, locations: &[(usize, usize)]) {
181        let chunks = self.state.get_chunks(frame.area());
182
183        let selected = self.cursor.selected(&self.selectables);
184        let hover = self.cursor.hover(&self.selectables);
185
186        locations.iter().for_each(|(x, y)| {
187            self.render_only_raw(frame, *x, *y, chunks[*y][*x], false, selected, hover);
188        });
189
190        locations.iter().for_each(|(x, y)| {
191            self.render_only_raw(frame, *x, *y, chunks[*y][*x], true, selected, hover);
192        });
193    }
194
195    /// Render only with more controls
196    pub fn render_only_raw(
197        &mut self,
198        frame: &mut Frame,
199        x: usize,
200        y: usize,
201        chunk: Rect,
202        popup_render: bool,
203        selected: Option<(usize, usize)>,
204        hover: Option<(usize, usize)>,
205    ) {
206        let (mut frameworkclean, state) = self.split_clean();
207        state.get_mut(x, y).render(
208            frame,
209            &mut frameworkclean,
210            chunk,
211            popup_render,
212            ItemInfo {
213                selected: selected == Some((x, y)),
214                hover: hover == Some((x, y)),
215                x,
216                y,
217            },
218        )
219    }
220
221    /// Send key input to selected object, returns an `Err(())` when no objct is selected
222    pub fn key_input(&mut self, key: KeyEvent) -> Result<(), Box<dyn Error>> {
223        let selected = self.cursor.selected(&self.selectables);
224        let (mut frameworkclean, state) = self.split_clean();
225
226        if let Some((x, y)) = selected {
227            state.get_mut(x, y).key_event(
228                &mut frameworkclean,
229                key,
230                ItemInfo {
231                    selected: true,
232                    hover: false,
233                    x,
234                    y,
235                },
236            )?;
237        }
238
239        Ok(())
240    }
241
242    /// Handles when mouse is clicked
243    pub fn mouse_event(&mut self, col: u16, row: u16) -> bool {
244        let chunks = match self.frame_area {
245            Some(area) => self.state.get_chunks(area),
246            None => return false,
247        };
248
249        // loops over selectable items only
250        for (row_no, row_selectables) in self.selectables.iter().enumerate() {
251            for (col_no, &(x, y)) in row_selectables.iter().enumerate() {
252                let chunk = chunks[y][x];
253                // guard gate to only do stuff if clicking on item
254                if !chunk.intersects(Rect::new(col, row, 1, 1)) {
255                    continue;
256                }
257
258                // pass click event to item only if it is already selected
259                if self.cursor.selected(&self.selectables) == Some((col_no, row_no)) {
260                    let (mut clean, state) = self.split_clean();
261                    return state.get_mut(x, y).mouse_event(
262                        &mut clean,
263                        col - chunk.x,
264                        row - chunk.y,
265                        col,
266                        row,
267                    );
268                }
269
270                if self.cursor.hover(&self.selectables) == Some((col_no, row_no)) {
271                    return self.select().is_ok();
272                }
273
274                self.deselect().ok();
275                self.cursor = CursorState::to_hover((col_no, row_no));
276                return true;
277            }
278        }
279
280        self.deselect().ok();
281        self.cursor = CursorState::default();
282        true
283    }
284
285    /// Send message to selected object, returns true if anything updated
286    pub fn message(&mut self, data: HashMap<String, Box<dyn Any>>) -> bool {
287        let selected = self.cursor.selected(&self.selectables);
288        let (mut frameworkclean, state) = self.split_clean();
289
290        if let Some((x, y)) = selected {
291            return state.get_mut(x, y).message(
292                &mut frameworkclean,
293                data
294            );
295        }
296
297        false
298    }
299
300    pub fn load(&mut self) -> Result<(), Box<dyn Error>> {
301        let selected = self.cursor.selected(&self.selectables);
302        let hover = self.cursor.hover(&self.selectables);
303        let (mut frameworkclean, state) = self.split_clean();
304
305        for (y, row) in state.0.iter_mut().enumerate() {
306            for (x, row_item) in row.items.iter_mut().enumerate() {
307                row_item.item.load_item(
308                    &mut frameworkclean,
309                    ItemInfo {
310                        selected: Some((x, y)) == selected,
311                        hover: Some((x, y)) == hover,
312                        x,
313                        y,
314                    },
315                )?;
316            }
317        }
318
319        Ok(())
320    }
321
322    pub fn load_only(&mut self, x: usize, y: usize) -> Result<(), Box<dyn Error>> {
323        let selected = self.cursor.selected(&self.selectables);
324        let hover = self.cursor.hover(&self.selectables);
325        let (mut frameworkclean, state) = self.split_clean();
326
327        state.get_mut(x, y).load_item(
328            &mut frameworkclean,
329            ItemInfo {
330                selected: Some((x, y)) == selected,
331                hover: Some((x, y)) == hover,
332                x,
333                y,
334            },
335        )
336    }
337
338    pub fn load_only_multiple(&mut self, locations: &[(usize, usize)]) {
339        let selected = self.cursor.selected(&self.selectables);
340        let hover = self.cursor.hover(&self.selectables);
341        let (mut frameworkclean, state) = self.split_clean();
342
343        locations.iter().for_each(|(x, y)| {
344            let _ = state.get_mut(*x, *y).load_item(
345                &mut frameworkclean,
346                ItemInfo {
347                    selected: Some((*x, *y)) == selected,
348                    hover: Some((*x, *y)) == hover,
349                    x: *x,
350                    y: *y,
351                },
352            );
353        })
354    }
355}
356
357impl Framework {
358    /// Split `Framework` into `FrameworkClean` and `&mut State`
359    pub fn split_clean(&mut self) -> (FrameworkClean, &mut State) {
360        self.into()
361    }
362}
363
364impl Framework {
365    /// Move cursor in corresponding direction, will return an `Err(E)` if something is selected
366    /// and the cursor is not free to move around
367    pub fn r#move(&mut self, direction: FrameworkDirection) -> Result<(), FrameworkError> {
368        self.cursor.r#move(direction, &self.selectables)
369    }
370
371    /// Select the hovering item
372    pub fn select(&mut self) -> Result<(), Box<dyn Error>> {
373        if let Some((x, y)) = self.cursor.hover(&self.selectables) {
374            let (mut frameworkclean, state) = self.split_clean();
375            let item = state.get_mut(x, y);
376            if item.select(&mut frameworkclean) {
377                self.cursor.select()?;
378            }
379        } else {
380            Err(FrameworkError::CursorStateMismatch)?;
381        }
382
383        Ok(())
384    }
385
386    /// Deselect the hovering item
387    pub fn deselect(&mut self) -> Result<(), Box<dyn Error>> {
388        if let Some((x, y)) = self.cursor.selected(&self.selectables) {
389            let (mut frameworkclean, state) = self.split_clean();
390            let item = state.get_mut(x, y);
391            if item.deselect(&mut frameworkclean) {
392                self.cursor.deselect()?;
393            }
394        } else {
395            Err(FrameworkError::CursorStateMismatch)?;
396        }
397
398        Ok(())
399    }
400}
401
402/// Errors that may be returned by `Framework`
403#[derive(Debug)]
404pub enum FrameworkError {
405    /// Moving the cursor when something is selected (not allowed)
406    MoveSelected,
407    /// Calling `self.select()` when not hovering and `self.deselect()` when nothing is selected
408    CursorStateMismatch,
409    /// Not found in `self.history`, caused by incorrect index or `self.history` is empty
410    NoSuchSave,
411}
412
413impl Display for FrameworkError {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        f.write_fmt(format_args!("{:?}", self))
416    }
417}
418
419impl Error for FrameworkError {}