use std::{ io, marker::PhantomData, time::{Duration, Instant}, }; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ buffer::Buffer, layout::{Constraint, Direction, Layout, Margin, Rect}, style::{Color, Stylize}, symbols::border, text::Line, widgets::{Block, Clear, Widget}, DefaultTerminal, Frame, }; use tui_rain::Rain; fn main() -> io::Result<()> { let mut terminal = ratatui::init(); let app_result = App::default().run(&mut terminal); ratatui::restore(); app_result } #[derive(Debug)] pub struct App<'a> { start_time: Instant, last_draw: Instant, exit: bool, focus: Focus, _marker: PhantomData<&'a ()>, } #[derive(Debug)] enum Focus { Username, Password, } impl Default for App<'_> { fn default() -> Self { let start_time = Instant::now(); let last_draw = Instant::now(); let exit = false; let focus = Focus::Username; Self { start_time, last_draw, exit, focus, _marker: PhantomData, } } } impl App<'_> { pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { while !self.exit { let draw_time = Instant::now(); terminal.draw(|frame| self.draw(frame))?; self.last_draw = draw_time; self.handle_events()?; } Ok(()) } fn draw(&mut self, frame: &mut Frame) { let rain_widget = Rain::new_matrix(self.start_time.elapsed()) .with_character_set(tui_rain::CharacterSet::UnicodeRange { start: 0x21, len: 94, }) .with_bold_dim_effect(false) .with_rain_density(tui_rain::RainDensity::Relative { sparseness: 60 }) .with_rain_speed(tui_rain::RainSpeed::Absolute { speed: 0.002 }) .with_rain_speed_variance(10000.0) .with_tail_lifespan(Duration::from_millis(2500)) .with_color(Color::Green) .with_head_color(Color::White) .with_noise_interval(Duration::from_millis(2000)); let layout = Layout::default() .direction(Direction::Horizontal) .constraints(vec![Constraint::Percentage(40), Constraint::Percentage(20)]) .split(frame.area()); let layout = Layout::default() .direction(Direction::Vertical) .constraints(vec![Constraint::Percentage(40), Constraint::Percentage(20)]) .split(layout[1]); let prompt_area = layout[1]; let inner_area = prompt_area.inner(Margin::new(1, 1)); let prompt_areas = Layout::default() .direction(Direction::Vertical) .constraints(vec![Constraint::Length(1), Constraint::Length(1)]) .split(inner_area); frame.render_widget(rain_widget, frame.area()); frame.render_widget(Clear, prompt_area); let title = Line::from(" Hewwo! >w< :3 ".bold()); let instructions = Line::from(vec![ " Decrement ".into(), "".blue().bold(), " Increment ".into(), "".blue().bold(), " Quit ".into(), " ".blue().bold(), ]); let prompt_block = Block::bordered() .title(title.left_aligned()) .title_bottom(instructions.centered()) .border_set(border::PLAIN); frame.render_widget(prompt_block, prompt_area); // TextPrompt::from("User").draw(frame, prompt_areas[0], &mut self.username_state); // TextPrompt::from("Password") // .with_render_style(TextRenderStyle::Password) // .draw(frame, prompt_areas[1], &mut self.password_state); } fn handle_events(&mut self) -> io::Result<()> { if event::poll(Duration::from_millis(10))? { match event::read()? { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { self.handle_key_event(key_event) } _ => {} } }; Ok(()) } fn handle_key_event(&mut self, key_event: KeyEvent) { match key_event.code { KeyCode::Esc => self.exit(), KeyCode::Tab => match self.focus { Focus::Username => self.focus = Focus::Password, Focus::Password => self.focus = Focus::Username, }, KeyCode::BackTab => match self.focus { Focus::Username => self.focus = Focus::Password, Focus::Password => self.focus = Focus::Username, }, _ => self.focus_handle_key_event(key_event), } } fn focus_handle_key_event(&mut self, key_event: KeyEvent) { // let state = match self.focus { // Focus::Username => &mut self.username_state, // Focus::Password => &mut self.password_state, // }; // state.handle_key_event(key_event); } fn exit(&mut self) { self.exit = true; } } impl Widget for &mut App<'_> { fn render(self, area: Rect, buf: &mut Buffer) { let title = Line::from(" Hewwo >w< :3 ".bold()); let instructions = Line::from(vec![ " Decrement ".into(), "".blue().bold(), " Increment ".into(), "".blue().bold(), " Quit ".into(), " ".blue().bold(), ]); let block = Block::bordered() .title(title.left_aligned()) .title_bottom(instructions.centered()) .border_set(border::PLAIN); block.render(area, buf); } }