Add spinner animation to TUI status indicator (#1917)
## Summary - add a pulsing dot loader before the shimmering `Working` label in the status indicator widget and include a small test asserting the spinner character is rendered - also fix a small bug in the ran command header by adding a space between the ⚡ and `Ran command` https://github.com/user-attachments/assets/6768c9d2-e094-49cb-ad51-44bcac10aa6f ## Testing - `just fmt` - `just fix` *(failed: E0658 `let` expressions in core/src/client.rs)* - `cargo test --all-features` *(failed: E0658 `let` expressions in core/src/client.rs)* ------ https://chatgpt.com/codex/tasks/task_i_68941bffdb948322b0f4190bc9dbe7f6 --------- Co-authored-by: aibrahim-oai <aibrahim@openai.com>
This commit is contained in:
@@ -213,9 +213,20 @@ impl WidgetRef for StatusIndicatorWidget {
|
|||||||
// Plain rendering: no borders or padding so the live cell is visually indistinguishable from terminal scrollback.
|
// Plain rendering: no borders or padding so the live cell is visually indistinguishable from terminal scrollback.
|
||||||
let inner_width = area.width as usize;
|
let inner_width = area.width as usize;
|
||||||
|
|
||||||
// Compose a single status line like: "▌ Working (Xs • Ctrl c to interrupt) <logs>"
|
|
||||||
let mut spans: Vec<Span<'static>> = Vec::new();
|
let mut spans: Vec<Span<'static>> = Vec::new();
|
||||||
spans.push(Span::styled("▌ ", Style::default().fg(Color::Cyan)));
|
spans.push(Span::styled("▌ ", Style::default().fg(Color::Cyan)));
|
||||||
|
|
||||||
|
// Simple dim spinner to the left of the header.
|
||||||
|
let spinner_frames = ['·', '•', '●', '•'];
|
||||||
|
const SPINNER_SLOWDOWN: usize = 2;
|
||||||
|
let spinner_ch = spinner_frames[(idx / SPINNER_SLOWDOWN) % spinner_frames.len()];
|
||||||
|
spans.push(Span::styled(
|
||||||
|
spinner_ch.to_string(),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
));
|
||||||
|
spans.push(Span::raw(" "));
|
||||||
|
|
||||||
|
// Space after header
|
||||||
// Animated header after the left bar
|
// Animated header after the left bar
|
||||||
spans.extend(animated_spans);
|
spans.extend(animated_spans);
|
||||||
// Space between header and bracket block
|
// Space between header and bracket block
|
||||||
@@ -324,4 +335,23 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert!(row.contains("Working"), "expected Working header: {row:?}");
|
assert!(row.contains("Working"), "expected Working header: {row:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spinner_is_rendered() {
|
||||||
|
let (tx_raw, _rx) = channel::<AppEvent>();
|
||||||
|
let tx = AppEventSender::new(tx_raw);
|
||||||
|
let mut w = StatusIndicatorWidget::new(tx);
|
||||||
|
w.restart_with_text("Hello".to_string());
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(120));
|
||||||
|
|
||||||
|
let area = ratatui::layout::Rect::new(0, 0, 30, 1);
|
||||||
|
let mut buf = ratatui::buffer::Buffer::empty(area);
|
||||||
|
w.render_ref(area, &mut buf);
|
||||||
|
|
||||||
|
let ch = buf[(2, 0)].symbol().chars().next().unwrap_or(' ');
|
||||||
|
assert!(
|
||||||
|
matches!(ch, '·' | '•' | '●'),
|
||||||
|
"expected spinner char at col 2: {ch:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user