rustlens_lib/ui/
splash.rs1use crossterm::event::{self, Event, KeyEventKind};
4use ratatui::{
5 backend::CrosstermBackend,
6 layout::{Alignment, Constraint, Direction, Layout, Rect},
7 style::Modifier,
8 text::{Line, Span},
9 widgets::{Block, Borders, Paragraph, Widget},
10 Frame, Terminal,
11};
12use std::io;
13use std::time::{Duration, Instant};
14
15use crate::ui::theme::Theme;
16
17const SPLASH_DURATION: Duration = Duration::from_millis(2200);
18const WAVE_ROWS: usize = 8;
19const WAVE_CHARS: &str = "~∿∼〜〰︴";
20
21fn draw_waves(frame: &mut Frame, area: Rect, phase: f64, theme: &Theme) {
23 let wave_len = 24usize;
24 let width = area.width as usize;
25 let height = area.height.saturating_sub(2) as usize;
26 if height == 0 || width == 0 {
27 return;
28 }
29 for row in 0..height.min(WAVE_ROWS) {
30 let y = area.y + 2 + row as u16;
31 let mut line = String::with_capacity(width);
32 let row_phase = phase + row as f64 * 0.2;
33 for col in 0..width {
34 let t = (col as f64 / wave_len as f64) + row_phase;
35 let wave = (t * std::f64::consts::TAU).sin();
36 let idx = ((wave + 1.0) * 0.5 * (WAVE_CHARS.len() - 1) as f64) as usize;
37 let idx = idx.min(WAVE_CHARS.len() - 1);
38 let c = WAVE_CHARS.chars().nth(idx).unwrap_or('~');
39 line.push(c);
40 }
41 let style = if row % 2 == 0 {
42 theme.style_accent()
43 } else {
44 theme.style_dim()
45 };
46 let span = Span::styled(line, style);
47 frame.render_widget(Paragraph::new(span), Rect::new(area.x, y, area.width, 1));
48 }
49}
50
51pub fn run_splash(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> anyhow::Result<()> {
53 let theme = Theme::default();
54 let start = Instant::now();
55
56 loop {
57 let elapsed = start.elapsed();
58 if elapsed >= SPLASH_DURATION {
59 break;
60 }
61 let phase = elapsed.as_secs_f64() * 2.0;
62
63 terminal.draw(|frame| {
64 let area = frame.area();
65 let chunks = Layout::default()
66 .direction(Direction::Vertical)
67 .constraints([
68 Constraint::Length(3),
69 Constraint::Min(10),
70 Constraint::Length(3),
71 ])
72 .split(area);
73
74 let title = Paragraph::new(vec![
75 Line::from(""),
76 Line::from(vec![
77 Span::styled(
78 "RUSTLENS",
79 theme.style_accent_bold().add_modifier(Modifier::BOLD),
80 ),
81 Span::styled(" · ", theme.style_muted()),
82 Span::styled("Rust Code Inspector", theme.style_dim()),
83 ]),
84 ])
85 .alignment(Alignment::Center)
86 .block(
87 Block::default()
88 .borders(Borders::BOTTOM)
89 .border_style(theme.style_border()),
90 );
91 title.render(chunks[0], frame.buffer_mut());
92
93 draw_waves(frame, chunks[1], phase, &theme);
94
95 let hint = Paragraph::new(Line::from(vec![
96 Span::styled("Starting... ", theme.style_muted()),
97 Span::styled("(press any key to skip)", theme.style_dim()),
98 ]))
99 .alignment(Alignment::Center);
100 hint.render(chunks[2], frame.buffer_mut());
101 })?;
102
103 if event::poll(Duration::from_millis(50))? {
104 if let Event::Key(key) = event::read()? {
105 if key.kind == KeyEventKind::Press {
106 break;
107 }
108 }
109 }
110 }
111
112 Ok(())
113}