A small typing test program in Rust
| 1 | use std::io; |
| 2 | |
| 3 | use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; |
| 4 | use ratatui::{ |
| 5 | buffer::Buffer, |
| 6 | layout::{Rect, Layout, Direction, Constraint}, |
| 7 | style::Stylize, |
| 8 | symbols::border, |
| 9 | text::{Line, Text}, |
| 10 | widgets::{Block, Paragraph, Widget}, |
| 11 | DefaultTerminal, Frame, |
| 12 | }; |
| 13 | |
| 14 | #[derive(Debug, PartialEq, Eq)] |
| 15 | enum States { |
| 16 | Running, |
| 17 | Paused, |
| 18 | Quit, |
| 19 | } |
| 20 | |
| 21 | impl Default for States { |
| 22 | fn default() -> Self { |
| 23 | States::Running |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | #[derive(Debug, Default)] |
| 28 | pub struct App { |
| 29 | counter: u8, |
| 30 | state: States, |
| 31 | } |
| 32 | |
| 33 | impl Widget for &App { |
| 34 | fn render(self, area: Rect, buf: &mut Buffer) { |
| 35 | |
| 36 | let title = Line::from(" Counter App Tutorial ".bold()); |
| 37 | let instructions = Line::from(vec![ |
| 38 | " Decrement ".into(), |
| 39 | "<Left>".blue().bold(), |
| 40 | " Increment ".into(), |
| 41 | "<Right>".blue().bold(), |
| 42 | " Pause ".into(), |
| 43 | "<P>".blue().bold(), |
| 44 | " Quit".into(), |
| 45 | "<Q> ".blue().bold(), |
| 46 | ]); |
| 47 | let block = Block::bordered() |
| 48 | .title(title.centered()) |
| 49 | .title_bottom(instructions.centered()) |
| 50 | .border_set(border::THICK); |
| 51 | |
| 52 | let counter_text = Text::from(vec![Line::from(vec![ |
| 53 | "Value: ".into(), |
| 54 | self.counter.to_string().yellow(), |
| 55 | ])]); |
| 56 | |
| 57 | Paragraph::new(counter_text) |
| 58 | .centered() |
| 59 | .block(block) |
| 60 | .render(area, buf); |
| 61 | |
| 62 | if self.state == States::Paused { |
| 63 | |
| 64 | let pause_area = area.centered( |
| 65 | Constraint::Length(20), |
| 66 | Constraint::Length(5), |
| 67 | ); |
| 68 | |
| 69 | let pause_block = Block::bordered() |
| 70 | .border_set(border::ROUNDED); |
| 71 | |
| 72 | let pause_text = Text::from("PAUSE"); |
| 73 | |
| 74 | let pause_text_area = pause_area.centered( |
| 75 | Constraint::Length(pause_text.width() as u16), |
| 76 | Constraint::Length(1), |
| 77 | ); |
| 78 | |
| 79 | pause_block.render(pause_area, buf); |
| 80 | |
| 81 | Paragraph::new(pause_text) |
| 82 | .render(pause_text_area, buf); |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | impl App { |
| 88 | |
| 89 | pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { |
| 90 | while self.state != States::Quit { |
| 91 | terminal.draw(|frame| self.draw(frame))?; |
| 92 | self.handle_events()?; |
| 93 | } |
| 94 | Ok(()) |
| 95 | } |
| 96 | |
| 97 | fn draw(&self, frame: &mut Frame) { |
| 98 | frame.render_widget(self, frame.area()); |
| 99 | } |
| 100 | |
| 101 | fn handle_events(&mut self) -> io::Result<()> { |
| 102 | match event::read()? { |
| 103 | Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { |
| 104 | self.handle_key_event(key_event) |
| 105 | }, |
| 106 | _ => {}, |
| 107 | }; |
| 108 | Ok(()) |
| 109 | } |
| 110 | |
| 111 | fn handle_key_event(&mut self, key_event: KeyEvent) { |
| 112 | match self.state { |
| 113 | States::Running => |
| 114 | match key_event.code { |
| 115 | KeyCode::Char('p') => self.pause(), |
| 116 | KeyCode::Char('q') => self.exit(), |
| 117 | KeyCode::Left => self.decrement_counter(), |
| 118 | KeyCode::Right => self.increment_counter(), |
| 119 | _ => {} |
| 120 | }, |
| 121 | States::Paused => |
| 122 | match key_event.code { |
| 123 | KeyCode::Char('p') => self.pause(), |
| 124 | KeyCode::Char('q') => self.exit(), |
| 125 | _ => {}, |
| 126 | }, |
| 127 | |
| 128 | States::Quit => {}, |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | fn pause(&mut self) { |
| 133 | if self.state == States::Paused { |
| 134 | self.state = States::Running; |
| 135 | } else { |
| 136 | self.state = States::Paused; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | fn exit(&mut self) { |
| 141 | self.state = States::Quit; |
| 142 | } |
| 143 | |
| 144 | fn increment_counter(&mut self) { |
| 145 | self.counter += 1; |
| 146 | } |
| 147 | |
| 148 | fn decrement_counter(&mut self) { |
| 149 | self.counter -= 1; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | |
| 154 | fn main() -> io::Result<()> { |
| 155 | ratatui::run(|terminal| App::default().run(terminal)) |
| 156 | } |