tui_realm_stdlib/components/
spinner.rs

1//! ## Spinner
2//!
3//! A loading spinner. You can provide the "spinning sequence". At each `view()` call, the sequence step is increased
4
5use tuirealm::command::{Cmd, CmdResult};
6use tuirealm::props::{Alignment, AttrValue, Attribute, Color, Props, Style};
7use tuirealm::ratatui::text::Line as Spans;
8use tuirealm::ratatui::{
9    layout::Rect,
10    text::{Span as TuiSpan, Text},
11    widgets::Paragraph,
12};
13use tuirealm::{Frame, MockComponent, State};
14
15// -- states
16
17#[derive(Default)]
18pub struct SpinnerStates {
19    pub sequence: Vec<char>,
20    pub step: usize,
21}
22
23impl SpinnerStates {
24    /// ### reset
25    ///
26    /// Re initialize sequence
27    pub fn reset(&mut self, sequence: &str) {
28        self.sequence = sequence.chars().collect();
29        self.step = 0;
30    }
31
32    /// ### step
33    ///
34    /// Get current step char and increments step
35    pub fn step(&mut self) -> char {
36        let ch = self.sequence.get(self.step).cloned().unwrap_or(' ');
37        // Incr step
38        if self.step + 1 >= self.sequence.len() {
39            self.step = 0;
40        } else {
41            self.step += 1;
42        }
43        ch
44    }
45}
46
47// -- Component
48
49/// ## Spinner
50///
51/// A textual spinner which step changes at each `view()` call
52#[derive(Default)]
53pub struct Spinner {
54    props: Props,
55    pub states: SpinnerStates,
56}
57
58impl Spinner {
59    pub fn foreground(mut self, fg: Color) -> Self {
60        self.attr(Attribute::Foreground, AttrValue::Color(fg));
61        self
62    }
63
64    pub fn background(mut self, bg: Color) -> Self {
65        self.attr(Attribute::Background, AttrValue::Color(bg));
66        self
67    }
68
69    pub fn sequence<S: Into<String>>(mut self, s: S) -> Self {
70        self.attr(Attribute::Text, AttrValue::String(s.into()));
71        self
72    }
73}
74
75impl MockComponent for Spinner {
76    fn view(&mut self, render: &mut Frame, area: Rect) {
77        // Make a Span
78        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
79            // Make text
80            let foreground = self
81                .props
82                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
83                .unwrap_color();
84            let background = self
85                .props
86                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
87                .unwrap_color();
88            // Get text
89            let text: Text = Text::from(Spans::from(TuiSpan::from(self.states.step().to_string())));
90            render.render_widget(
91                Paragraph::new(text)
92                    .alignment(Alignment::Left)
93                    .style(Style::default().bg(background).fg(foreground)),
94                area,
95            );
96        }
97    }
98
99    fn query(&self, attr: Attribute) -> Option<AttrValue> {
100        self.props.get(attr)
101    }
102
103    fn attr(&mut self, attr: Attribute, value: AttrValue) {
104        if matches!(attr, Attribute::Text) {
105            // Update sequence
106            self.states.reset(value.unwrap_string().as_str());
107        } else {
108            self.props.set(attr, value);
109        }
110    }
111
112    fn state(&self) -> State {
113        State::None
114    }
115
116    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
117        CmdResult::None
118    }
119}
120
121#[cfg(test)]
122mod tests {
123
124    use super::*;
125
126    use pretty_assertions::assert_eq;
127
128    #[test]
129    fn test_components_span() {
130        let component = Spinner::default()
131            .background(Color::Blue)
132            .foreground(Color::Red)
133            .sequence("⣾⣽⣻⢿⡿⣟⣯⣷");
134        // Get value
135        assert_eq!(component.state(), State::None);
136    }
137}