mirror of
https://github.com/openai/codex.git
synced 2026-05-03 04:42:20 +03:00
Added feature switch to disable animations in TUI (#6870)
This PR adds support for a new feature flag `tui.animations`. By default, the TUI uses animations in its welcome screen, "working" spinners, and "shimmer" effects. This animations can interfere with screen readers, so it's good to provide a way to disable them. This change is inspired by [a PR](https://github.com/openai/codex/pull/4014) contributed by @Orinks. That PR has faltered a bit, but I think the core idea is sound. This version incorporates feedback from @aibrahim-oai. In particular: 1. It uses a feature flag (`tui.animations`) rather than the unqualified CLI key `no-animations`. Feature flags are the preferred way to expose boolean switches. They are also exposed via CLI command switches. 2. It includes more complete documentation. 3. It disables a few animations that the other PR omitted.
This commit is contained in:
@@ -155,6 +155,7 @@ pub(crate) struct AuthModeWidget {
|
||||
pub auth_manager: Arc<AuthManager>,
|
||||
pub forced_chatgpt_workspace_id: Option<String>,
|
||||
pub forced_login_method: Option<ForcedLoginMethod>,
|
||||
pub animations_enabled: bool,
|
||||
}
|
||||
|
||||
impl AuthModeWidget {
|
||||
@@ -260,10 +261,14 @@ impl AuthModeWidget {
|
||||
|
||||
fn render_continue_in_browser(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut spans = vec![" ".into()];
|
||||
// Schedule a follow-up frame to keep the shimmer animation going.
|
||||
self.request_frame
|
||||
.schedule_frame_in(std::time::Duration::from_millis(100));
|
||||
spans.extend(shimmer_spans("Finish signing in via your browser"));
|
||||
if self.animations_enabled {
|
||||
// Schedule a follow-up frame to keep the shimmer animation going.
|
||||
self.request_frame
|
||||
.schedule_frame_in(std::time::Duration::from_millis(100));
|
||||
spans.extend(shimmer_spans("Finish signing in via your browser"));
|
||||
} else {
|
||||
spans.push("Finish signing in via your browser".into());
|
||||
}
|
||||
let mut lines = vec![spans.into(), "".into()];
|
||||
|
||||
let sign_in_state = self.sign_in_state.read().unwrap();
|
||||
@@ -670,6 +675,7 @@ mod tests {
|
||||
),
|
||||
forced_chatgpt_workspace_id: None,
|
||||
forced_login_method: Some(ForcedLoginMethod::Chatgpt),
|
||||
animations_enabled: true,
|
||||
};
|
||||
(widget, codex_home)
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ impl OnboardingScreen {
|
||||
steps.push(Step::Welcome(WelcomeWidget::new(
|
||||
!matches!(login_status, LoginStatus::NotAuthenticated),
|
||||
tui.frame_requester(),
|
||||
config.animations,
|
||||
)));
|
||||
if show_login_screen {
|
||||
let highlighted_mode = match forced_login_method {
|
||||
@@ -105,6 +106,7 @@ impl OnboardingScreen {
|
||||
auth_manager,
|
||||
forced_chatgpt_workspace_id,
|
||||
forced_login_method,
|
||||
animations_enabled: config.animations,
|
||||
}))
|
||||
}
|
||||
let is_git_repo = get_git_repo_root(&cwd).is_some();
|
||||
|
||||
@@ -25,10 +25,14 @@ const MIN_ANIMATION_WIDTH: u16 = 60;
|
||||
pub(crate) struct WelcomeWidget {
|
||||
pub is_logged_in: bool,
|
||||
animation: AsciiAnimation,
|
||||
animations_enabled: bool,
|
||||
}
|
||||
|
||||
impl KeyboardHandler for WelcomeWidget {
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
if !self.animations_enabled {
|
||||
return;
|
||||
}
|
||||
if key_event.kind == KeyEventKind::Press
|
||||
&& key_event.code == KeyCode::Char('.')
|
||||
&& key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
@@ -40,10 +44,15 @@ impl KeyboardHandler for WelcomeWidget {
|
||||
}
|
||||
|
||||
impl WelcomeWidget {
|
||||
pub(crate) fn new(is_logged_in: bool, request_frame: FrameRequester) -> Self {
|
||||
pub(crate) fn new(
|
||||
is_logged_in: bool,
|
||||
request_frame: FrameRequester,
|
||||
animations_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
is_logged_in,
|
||||
animation: AsciiAnimation::new(request_frame),
|
||||
animations_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,17 +60,17 @@ impl WelcomeWidget {
|
||||
impl WidgetRef for &WelcomeWidget {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Clear.render(area, buf);
|
||||
self.animation.schedule_next_frame();
|
||||
if self.animations_enabled {
|
||||
self.animation.schedule_next_frame();
|
||||
}
|
||||
|
||||
// Skip the animation entirely when the viewport is too small so we don't clip frames.
|
||||
let show_animation =
|
||||
area.height >= MIN_ANIMATION_HEIGHT && area.width >= MIN_ANIMATION_WIDTH;
|
||||
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
if show_animation {
|
||||
if show_animation && self.animations_enabled {
|
||||
let frame = self.animation.current_frame();
|
||||
// let frame_line_count = frame.lines().count();
|
||||
// lines.reserve(frame_line_count + 2);
|
||||
lines.extend(frame.lines().map(Into::into));
|
||||
lines.push("".into());
|
||||
}
|
||||
@@ -99,7 +108,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn welcome_renders_animation_on_first_draw() {
|
||||
let widget = WelcomeWidget::new(false, FrameRequester::test_dummy());
|
||||
let widget = WelcomeWidget::new(false, FrameRequester::test_dummy(), true);
|
||||
let area = Rect::new(0, 0, MIN_ANIMATION_WIDTH, MIN_ANIMATION_HEIGHT);
|
||||
let mut buf = Buffer::empty(area);
|
||||
(&widget).render(area, &mut buf);
|
||||
@@ -129,6 +138,7 @@ mod tests {
|
||||
let mut widget = WelcomeWidget {
|
||||
is_logged_in: false,
|
||||
animation: AsciiAnimation::with_variants(FrameRequester::test_dummy(), &VARIANTS, 0),
|
||||
animations_enabled: true,
|
||||
};
|
||||
|
||||
let before = widget.animation.current_frame();
|
||||
|
||||
Reference in New Issue
Block a user