mirror of
https://github.com/openai/codex.git
synced 2026-10-21 05:22:21 +00:00
Centralize TUI loading glyphs and frame scheduling (#50112)
## What changed Move the voice connection spinner into shared helpers in `codex-rs/tui/src/motion.rs`, keeping its 100 ms animation cadence and static reduced-motion glyph. The helpers handle frame scheduling, and `loading_glyph_with_delay` supports hiding the animated spinner until a specified delay expires. GitOrigin-RevId: c4ebe5e6a33239999aab2f4432c9145432c8f4fd
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
use crate::key_hint::ShortcutHint;
|
||||
use crate::motion::MotionMode;
|
||||
use crate::motion::loading_glyph;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::tui::FrameRequester;
|
||||
use ratatui::buffer::Buffer;
|
||||
@@ -15,7 +16,6 @@ use ratatui::text::Line;
|
||||
use ratatui::text::Span;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::Widget;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
@@ -61,16 +61,6 @@ impl VoiceStrip {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn loading_glyph(started_at: Instant, mode: MotionMode) -> &'static str {
|
||||
const FRAMES: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
if mode == MotionMode::Reduced {
|
||||
"◌"
|
||||
} else {
|
||||
let frame = started_at.elapsed().as_millis() / 100;
|
||||
FRAMES[usize::try_from(frame).unwrap_or_default() % FRAMES.len()]
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for VoiceStrip {
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
if width == 0 {
|
||||
@@ -92,14 +82,13 @@ impl Renderable for VoiceStrip {
|
||||
let connecting = self.state.phase == VoiceStripPhase::Connecting;
|
||||
let mode =
|
||||
MotionMode::from_animations_enabled(self.state.animations && self.state.progress);
|
||||
if connecting && mode == MotionMode::Animated {
|
||||
self.frame_requester
|
||||
.schedule_frame_in(Duration::from_millis(100));
|
||||
}
|
||||
let marker = if connecting && self.state.microphone_live && !self.state.microphone_muted {
|
||||
loading_glyph(self.started_at, mode).red().bold()
|
||||
} else if connecting {
|
||||
loading_glyph(self.started_at, mode).cyan()
|
||||
let marker = if connecting {
|
||||
let glyph = loading_glyph(self.started_at, mode, &self.frame_requester);
|
||||
if self.state.microphone_live && !self.state.microphone_muted {
|
||||
glyph.red().bold()
|
||||
} else {
|
||||
glyph.cyan()
|
||||
}
|
||||
} else if self.state.microphone_live && !self.state.microphone_muted {
|
||||
"●".red().bold()
|
||||
} else {
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
//! Callers choose an explicit reduced-motion fallback here instead of reaching
|
||||
//! directly for time-varying spinner or shimmer helpers.
|
||||
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::tui::FrameRequester;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Span;
|
||||
|
||||
@@ -35,6 +37,38 @@ pub(crate) enum ReducedMotionIndicator {
|
||||
StaticBullet,
|
||||
}
|
||||
|
||||
/// Show the current loading glyph immediately and schedule the next frame.
|
||||
pub(crate) fn loading_glyph(
|
||||
started_at: Instant,
|
||||
mode: MotionMode,
|
||||
requester: &FrameRequester,
|
||||
) -> &'static str {
|
||||
loading_glyph_with_delay(started_at, Duration::ZERO, mode, requester)
|
||||
}
|
||||
|
||||
/// Show nothing during the animation delay, or a static glyph in reduced motion.
|
||||
/// Schedule the frame that makes the spinner visible and each animation frame.
|
||||
pub(crate) fn loading_glyph_with_delay(
|
||||
started_at: Instant,
|
||||
delay: Duration,
|
||||
mode: MotionMode,
|
||||
requester: &FrameRequester,
|
||||
) -> &'static str {
|
||||
const FRAME_DURATION: Duration = Duration::from_millis(100);
|
||||
const FRAMES: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
if mode == MotionMode::Reduced {
|
||||
return "◌";
|
||||
}
|
||||
let elapsed = started_at.elapsed();
|
||||
if elapsed < delay {
|
||||
requester.schedule_frame_in(delay - elapsed);
|
||||
return "";
|
||||
}
|
||||
requester.schedule_frame_in(FRAME_DURATION);
|
||||
let frame = elapsed.as_millis() / FRAME_DURATION.as_millis();
|
||||
FRAMES[usize::try_from(frame).unwrap_or_default() % FRAMES.len()]
|
||||
}
|
||||
|
||||
pub(crate) fn activity_indicator(
|
||||
start_time: Option<Instant>,
|
||||
motion_mode: MotionMode,
|
||||
|
||||
Reference in New Issue
Block a user