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 show_once: bool,
48}
49
50impl WorkingIndicator {
51 pub fn new() -> Self {
52 let theme = crate::agent::ui::theme::current_theme().clone();
53 Self {
54 options: IndicatorOptions::default(),
55 frame: 0,
56 last_tick: std::time::Instant::now(),
57 theme,
58 active: false,
59 show_once: false,
60 message: "Working...".into(),
61 }
62 }
63
64 pub fn start(&mut self) {
65 self.active = true;
66 self.show_once = true;
67 self.last_tick = std::time::Instant::now();
68 }
69
70 pub fn stop(&mut self) {
71 self.active = false;
72 }
74
75 pub fn set_message(&mut self, message: String) {
78 self.message = message;
79 }
80
81 pub fn should_show(&self) -> bool {
83 (self.active || self.show_once) && !self.options.frames.is_empty()
84 }
85
86 pub fn set_indicator(&mut self, options: Option<IndicatorOptions>) {
89 self.options = options.unwrap_or_default();
90 self.frame = 0;
91 }
92
93 pub fn tick(&mut self) -> bool {
95 if !self.active || self.options.frames.is_empty() {
96 return false;
97 }
98 let elapsed = self.last_tick.elapsed();
99 if elapsed.as_millis() >= self.options.interval_ms as u128 {
100 self.frame = (self.frame + 1) % self.options.frames.len();
101 self.last_tick = std::time::Instant::now();
102 return true;
103 }
104 false
105 }
106}
107
108impl Default for WorkingIndicator {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl Component for WorkingIndicator {
115 fn render(&mut self, _width: usize) -> Vec<String> {
116 if (!self.active && !self.show_once) || self.options.frames.is_empty() {
117 return vec![];
118 }
119 let frame = &self.options.frames[self.frame % self.options.frames.len()];
120 let line = format!(
124 " {} {} ",
125 self.theme.accent(frame),
126 self.theme.muted(&self.message)
127 );
128 self.show_once = false;
129 vec![String::new(), line]
131 }
132}