1use crate::agent::ui::theme::RabTheme;
2use crate::tui::Component;
3
4#[derive(Clone)]
7pub struct IndicatorOptions {
8 pub frames: Vec<String>,
10 pub interval_ms: u64,
12}
13
14impl Default for IndicatorOptions {
15 fn default() -> Self {
16 Self {
17 frames: vec![
18 "⠋".into(),
19 "⠙".into(),
20 "⠹".into(),
21 "⠸".into(),
22 "⠼".into(),
23 "⠴".into(),
24 "⠦".into(),
25 "⠧".into(),
26 "⠇".into(),
27 "⠏".into(),
28 ],
29 interval_ms: 80,
30 }
31 }
32}
33
34pub struct WorkingIndicator {
37 options: IndicatorOptions,
38 frame: usize,
39 last_tick: std::time::Instant,
40 theme: RabTheme,
41 pub active: bool,
42 message: String,
43}
44
45impl WorkingIndicator {
46 pub fn new() -> Self {
47 let theme = crate::agent::ui::theme::current_theme().clone();
48 Self {
49 options: IndicatorOptions::default(),
50 frame: 0,
51 last_tick: std::time::Instant::now(),
52 theme,
53 active: false,
54 message: "Working...".into(),
55 }
56 }
57
58 pub fn start(&mut self) {
59 self.active = true;
60 self.last_tick = std::time::Instant::now();
61 }
62
63 pub fn stop(&mut self) {
64 self.active = false;
65 }
66
67 pub fn set_message(&mut self, message: String) {
70 self.message = message;
71 }
72
73 pub fn set_indicator(&mut self, options: Option<IndicatorOptions>) {
76 self.options = options.unwrap_or_default();
77 self.frame = 0;
78 }
79
80 pub fn tick(&mut self) -> bool {
82 if !self.active || self.options.frames.is_empty() {
83 return false;
84 }
85 let elapsed = self.last_tick.elapsed();
86 if elapsed.as_millis() >= self.options.interval_ms as u128 {
87 self.frame = (self.frame + 1) % self.options.frames.len();
88 self.last_tick = std::time::Instant::now();
89 return true;
90 }
91 false
92 }
93}
94
95impl Default for WorkingIndicator {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl Component for WorkingIndicator {
102 fn render(&self, _width: usize) -> Vec<String> {
103 if !self.active || self.options.frames.is_empty() {
104 return vec![];
105 }
106 let frame = &self.options.frames[self.frame % self.options.frames.len()];
107 let line = format!(
111 " {} {} ",
112 self.theme.accent(frame),
113 self.theme.muted(&self.message)
114 );
115 vec![String::new(), line]
117 }
118}