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).cloned().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)]
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 if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
79 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 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 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 assert_eq!(component.state(), State::None);
136 }
137}