Reported height was `20` instead of `21`, so `area.height >= MIN_ANIMATION_HEIGHT` was `false` and therefore `show_animation` was `false`, so the animation never displayed.
92 lines
2.7 KiB
Rust
92 lines
2.7 KiB
Rust
use ratatui::buffer::Buffer;
|
|
use ratatui::layout::Rect;
|
|
use ratatui::prelude::Widget;
|
|
use ratatui::style::Stylize;
|
|
use ratatui::text::Line;
|
|
use ratatui::widgets::Paragraph;
|
|
use ratatui::widgets::WidgetRef;
|
|
use ratatui::widgets::Wrap;
|
|
|
|
use crate::frames::FRAME_TICK_DEFAULT;
|
|
use crate::frames::FRAMES_DEFAULT;
|
|
use crate::onboarding::onboarding_screen::StepStateProvider;
|
|
use crate::tui::FrameRequester;
|
|
|
|
use super::onboarding_screen::StepState;
|
|
use std::time::Duration;
|
|
use std::time::Instant;
|
|
|
|
const FRAME_TICK: Duration = FRAME_TICK_DEFAULT;
|
|
const MIN_ANIMATION_HEIGHT: u16 = 20;
|
|
const MIN_ANIMATION_WIDTH: u16 = 60;
|
|
|
|
pub(crate) struct WelcomeWidget {
|
|
pub is_logged_in: bool,
|
|
pub request_frame: FrameRequester,
|
|
pub start: Instant,
|
|
}
|
|
|
|
impl WidgetRef for &WelcomeWidget {
|
|
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
|
let elapsed_ms = self.start.elapsed().as_millis();
|
|
|
|
// Align next draw to the next FRAME_TICK boundary to reduce jitter.
|
|
{
|
|
let tick_ms = FRAME_TICK.as_millis();
|
|
let rem_ms = elapsed_ms % tick_ms;
|
|
let delay_ms = if rem_ms == 0 {
|
|
tick_ms
|
|
} else {
|
|
tick_ms - rem_ms
|
|
};
|
|
// Safe cast: delay_ms < tick_ms and FRAME_TICK is small.
|
|
self.request_frame
|
|
.schedule_frame_in(Duration::from_millis(delay_ms as u64));
|
|
}
|
|
|
|
let frames = &FRAMES_DEFAULT;
|
|
let idx = ((elapsed_ms / FRAME_TICK.as_millis()) % frames.len() as u128) as usize;
|
|
// 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 {
|
|
let frame_line_count = frames[idx].lines().count();
|
|
lines.reserve(frame_line_count + 2);
|
|
lines.extend(frames[idx].lines().map(|l| l.into()));
|
|
lines.push("".into());
|
|
}
|
|
lines.push(Line::from(vec![
|
|
" ".into(),
|
|
"Welcome to ".into(),
|
|
"Codex".bold(),
|
|
", OpenAI's command-line coding agent".into(),
|
|
]));
|
|
|
|
Paragraph::new(lines)
|
|
.wrap(Wrap { trim: false })
|
|
.render(area, buf);
|
|
}
|
|
}
|
|
|
|
impl StepStateProvider for WelcomeWidget {
|
|
fn get_step_state(&self) -> StepState {
|
|
match self.is_logged_in {
|
|
true => StepState::Hidden,
|
|
false => StepState::Complete,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// A number of things break down if FRAME_TICK is zero.
|
|
#[test]
|
|
fn frame_tick_must_be_nonzero() {
|
|
assert!(FRAME_TICK.as_millis() > 0);
|
|
}
|
|
}
|