diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index e1a0e162..7d4e41d0 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -869,6 +869,7 @@ dependencies = [ "shlex", "strum 0.27.2", "strum_macros 0.27.2", + "supports-color", "textwrap 0.16.2", "tokio", "tracing", @@ -2337,6 +2338,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -4378,6 +4385,15 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index 823fd142..a571b32c 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -48,6 +48,7 @@ serde_json = { version = "1", features = ["preserve_order"] } shlex = "1.3.0" strum = "0.27.2" strum_macros = "0.27.2" +supports-color = "3.0.2" textwrap = "0.16.2" tokio = { version = "1", features = [ "io-std", diff --git a/codex-rs/tui/src/status_indicator_widget.rs b/codex-rs/tui/src/status_indicator_widget.rs index 7e6d2674..aa18ac6f 100644 --- a/codex-rs/tui/src/status_indicator_widget.rs +++ b/codex-rs/tui/src/status_indicator_widget.rs @@ -57,7 +57,7 @@ impl StatusIndicatorWidget { thread::spawn(move || { let mut counter = 0usize; while running_clone.load(Ordering::Relaxed) { - std::thread::sleep(Duration::from_millis(200)); + std::thread::sleep(Duration::from_millis(100)); counter = counter.wrapping_add(1); frame_idx_clone.store(counter, Ordering::Relaxed); app_event_tx_clone.send(AppEvent::RequestRedraw); @@ -98,46 +98,51 @@ impl WidgetRef for StatusIndicatorWidget { .borders(Borders::LEFT) .border_type(BorderType::QuadrantOutside) .border_style(widget_style.dim()); - // Animated 3‑dot pattern inside brackets. The *active* dot is bold - // white, the others are dim. - const DOT_COUNT: usize = 3; let idx = self.frame_idx.load(std::sync::atomic::Ordering::Relaxed); - let phase = idx % (DOT_COUNT * 2 - 2); - let active = if phase < DOT_COUNT { - phase - } else { - (DOT_COUNT * 2 - 2) - phase - }; + let header_text = "Working"; + let header_chars: Vec = header_text.chars().collect(); + + let padding = 4usize; // virtual padding around the word for smoother loop + let period = header_chars.len() + padding * 2; + let pos = idx % period; + + let has_true_color = supports_color::on_cached(supports_color::Stream::Stdout) + .map(|level| level.has_16m) + .unwrap_or(false); + + // Width of the bright band (in characters). + let band_half_width = 2.0; let mut header_spans: Vec> = Vec::new(); + for (i, ch) in header_chars.iter().enumerate() { + let i_pos = i as isize + padding as isize; + let pos = pos as isize; + let dist = (i_pos - pos).abs() as f32; - header_spans.push(Span::styled( - "Working ", - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), - )); + let t = if dist <= band_half_width { + let x = std::f32::consts::PI * (dist / band_half_width); + 0.5 * (1.0 + x.cos()) + } else { + 0.0 + }; - header_spans.push(Span::styled( - "[", - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), - )); - - for i in 0..DOT_COUNT { - let style = if i == active { + let brightness = 0.4 + 0.6 * t; + let level = (brightness * 255.0).clamp(0.0, 255.0) as u8; + let style = if has_true_color { Style::default() - .fg(Color::White) + .fg(Color::Rgb(level, level, level)) .add_modifier(Modifier::BOLD) } else { - Style::default().dim() + // Bold makes dark gray and gray look the same, so don't use it + // when true color is not supported. + Style::default().fg(color_for_level(level)) }; - header_spans.push(Span::styled(".", style)); + + header_spans.push(Span::styled(ch.to_string(), style)); } header_spans.push(Span::styled( - "] ", + " ", Style::default() .fg(Color::White) .add_modifier(Modifier::BOLD), @@ -189,3 +194,13 @@ impl WidgetRef for StatusIndicatorWidget { paragraph.render_ref(area, buf); } } + +fn color_for_level(level: u8) -> Color { + if level < 128 { + Color::DarkGray + } else if level < 192 { + Color::Gray + } else { + Color::White + } +}