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).copied().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)]
53#[must_use]
54pub struct Spinner {
55    props: Props,
56    pub states: SpinnerStates,
57}
58
59impl Spinner {
60    pub fn foreground(mut self, fg: Color) -> Self {
61        self.attr(Attribute::Foreground, AttrValue::Color(fg));
62        self
63    }
64
65    pub fn background(mut self, bg: Color) -> Self {
66        self.attr(Attribute::Background, AttrValue::Color(bg));
67        self
68    }
69
70    pub fn sequence<S: Into<String>>(mut self, s: S) -> Self {
71        self.attr(Attribute::Text, AttrValue::String(s.into()));
72        self
73    }
74}
75
76impl MockComponent for Spinner {
77    fn view(&mut self, render: &mut Frame, area: Rect) {
78        // Make a Span
79        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
80            // Make text
81            let foreground = self
82                .props
83                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
84                .unwrap_color();
85            let background = self
86                .props
87                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
88                .unwrap_color();
89            // Get text
90            let text: Text = Text::from(Spans::from(TuiSpan::from(self.states.step().to_string())));
91            render.render_widget(
92                Paragraph::new(text)
93                    .alignment(Alignment::Left)
94                    .style(Style::default().bg(background).fg(foreground)),
95                area,
96            );
97        }
98    }
99
100    fn query(&self, attr: Attribute) -> Option<AttrValue> {
101        self.props.get(attr)
102    }
103
104    fn attr(&mut self, attr: Attribute, value: AttrValue) {
105        if matches!(attr, Attribute::Text) {
106            // Update sequence
107            self.states.reset(value.unwrap_string().as_str());
108        } else {
109            self.props.set(attr, value);
110        }
111    }
112
113    fn state(&self) -> State {
114        State::None
115    }
116
117    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
118        CmdResult::None
119    }
120}
121
122#[cfg(test)]
123mod tests {
124
125    use super::*;
126
127    use pretty_assertions::assert_eq;
128
129    #[test]
130    fn test_components_span() {
131        let component = Spinner::default()
132            .background(Color::Blue)
133            .foreground(Color::Red)
134            .sequence("⣾⣽⣻⢿⡿⣟⣯⣷");
135        // Get value
136        assert_eq!(component.state(), State::None);
137    }
138}