Skip to main content

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 (y, row_items) in self.state.0.iter().enumerate() {
251            for x in 0..row_items.items.len() {
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((x, y)) {
260                    let (mut clean, state) = self.split_clean();
261                    let a = state.get_mut(x, y).mouse_passthrough(
262                        &mut clean,
263                        true,
264                        col - chunk.x,
265                        row - chunk.y,
266                        col,
267                        row,
268                    );
269                    let b = state.get_mut(x, y).mouse_event(
270                        &mut clean,
271                        col - chunk.x,
272                        row - chunk.y,
273                        col,
274                        row,
275                    );
276                    return a || b;
277                } else {
278                    let (mut clean, state) = self.split_clean();
279                    state.get_mut(x, y).mouse_passthrough(
280                        &mut clean,
281                        false,
282                        col - chunk.x,
283                        row - chunk.y,
284                        col,
285                        row,
286                    );
287                }
288
289                if self.cursor.hover(&self.selectables) == Some((x, y)) {
290                    return self.select().is_ok();
291                }
292
293                let mut selectable_index = None;
294
295                for (sel_row, row_items) in self.selectables.iter().enumerate() {
296                    for (sel_col, (x2, y2)) in row_items.iter().enumerate() {
297                        if (x2, y2) == (&x, &y) {
298                            selectable_index = Some((sel_col, sel_row))
299                        }
300                    }
301                }
302
303                if let Some(sel_index) = selectable_index {
304                    self.deselect().ok();
305                    self.cursor = CursorState::to_hover(sel_index);
306                }
307
308                return true;
309            }
310        }
311
312        self.deselect().ok();
313        self.cursor = CursorState::default();
314        true
315    }
316
317    /// Send message to selected object, returns true if anything updated
318    pub fn message(&mut self, data: HashMap<String, Box<dyn Any>>) -> bool {
319        let selected = self.cursor.selected(&self.selectables);
320        let (mut frameworkclean, state) = self.split_clean();
321
322        if let Some((x, y)) = selected {
323            return state.get_mut(x, y).message(&mut frameworkclean, data);
324        }
325
326        false
327    }
328
329    pub fn load(&mut self) -> Result<(), Box<dyn Error>> {
330        let selected = self.cursor.selected(&self.selectables);
331        let hover = self.cursor.hover(&self.selectables);
332        let (mut frameworkclean, state) = self.split_clean();
333
334        for (y, row) in state.0.iter_mut().enumerate() {
335            for (x, row_item) in row.items.iter_mut().enumerate() {
336                row_item.item.load_item(
337                    &mut frameworkclean,
338                    ItemInfo {
339                        selected: Some((x, y)) == selected,
340                        hover: Some((x, y)) == hover,
341                        x,
342                        y,
343                    },
344                )?;
345            }
346        }
347
348        Ok(())
349    }
350
351    pub fn load_only(&mut self, x: usize, y: usize) -> Result<(), Box<dyn Error>> {
352        let selected = self.cursor.selected(&self.selectables);
353        let hover = self.cursor.hover(&self.selectables);
354        let (mut frameworkclean, state) = self.split_clean();
355
356        state.get_mut(x, y).load_item(
357            &mut frameworkclean,
358            ItemInfo {
359                selected: Some((x, y)) == selected,
360                hover: Some((x, y)) == hover,
361                x,
362                y,
363            },
364        )
365    }
366
367    pub fn load_only_multiple(&mut self, locations: &[(usize, usize)]) {
368        let selected = self.cursor.selected(&self.selectables);
369        let hover = self.cursor.hover(&self.selectables);
370        let (mut frameworkclean, state) = self.split_clean();
371
372        locations.iter().for_each(|(x, y)| {
373            let _ = state.get_mut(*x, *y).load_item(
374                &mut frameworkclean,
375                ItemInfo {
376                    selected: Some((*x, *y)) == selected,
377                    hover: Some((*x, *y)) == hover,
378                    x: *x,
379                    y: *y,
380                },
381            );
382        })
383    }
384}
385
386impl Framework {
387    /// Split `Framework` into `FrameworkClean` and `&mut State`
388    pub fn split_clean(&mut self) -> (FrameworkClean<'_>, &mut State) {
389        self.into()
390    }
391}
392
393impl Framework {
394    /// Move cursor in corresponding direction, will return an `Err(E)` if something is selected
395    /// and the cursor is not free to move around
396    pub fn r#move(&mut self, direction: FrameworkDirection) -> Result<(), FrameworkError> {
397        self.cursor.r#move(direction, &self.selectables)
398    }
399
400    /// Select the hovering item
401    pub fn select(&mut self) -> Result<(), Box<dyn Error>> {
402        if let Some((x, y)) = self.cursor.hover(&self.selectables) {
403            let (mut frameworkclean, state) = self.split_clean();
404            let item = state.get_mut(x, y);
405            if item.select(&mut frameworkclean) {
406                self.cursor.select()?;
407            }
408        } else {
409            Err(FrameworkError::CursorStateMismatch)?;
410        }
411
412        Ok(())
413    }
414
415    /// Deselect the hovering item
416    pub fn deselect(&mut self) -> Result<(), Box<dyn Error>> {
417        if let Some((x, y)) = self.cursor.selected(&self.selectables) {
418            let (mut frameworkclean, state) = self.split_clean();
419            let item = state.get_mut(x, y);
420            if item.deselect(&mut frameworkclean) {
421                self.cursor.deselect()?;
422            }
423        } else {
424            Err(FrameworkError::CursorStateMismatch)?;
425        }
426
427        Ok(())
428    }
429}
430
431/// Errors that may be returned by `Framework`
432#[derive(Debug)]
433pub enum FrameworkError {
434    /// Moving the cursor when something is selected (not allowed)
435    MoveSelected,
436    /// Calling `self.select()` when not hovering and `self.deselect()` when nothing is selected
437    CursorStateMismatch,
438    /// Not found in `self.history`, caused by incorrect index or `self.history` is empty
439    NoSuchSave,
440}
441
442impl Display for FrameworkError {
443    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444        f.write_fmt(format_args!("{:?}", self))
445    }
446}
447
448impl Error for FrameworkError {}