tui_realm_stdlib/components/
spinner.rs1use 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#[derive(Default)]
18pub struct SpinnerStates {
19 pub sequence: Vec<char>,
20 pub step: usize,
21}
22
23impl SpinnerStates {
24 pub fn reset(&mut self, sequence: &str) {
28 self.sequence = sequence.chars().collect();
29 self.step = 0;
30 }
31
32 pub fn step(&mut self) -> char {
36 let ch = self.sequence.get(self.step).copied().unwrap_or(' ');
37 if self.step + 1 >= self.sequence.len() {
39 self.step = 0;
40 } else {
41 self.step += 1;
42 }
43 ch
44 }
45}
46
47#[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 if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
80 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 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 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 assert_eq!(component.state(), State::None);
137 }
138}