diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 138 | ||||
| -rw-r--r-- | src/event.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/state.rs | 20 |
4 files changed, 149 insertions, 12 deletions
@@ -1,10 +1,16 @@ use std::{ error::Error, io::{stdout, Stdout}, + time::Duration, }; use crossterm::{ - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + cursor::{Hide, MoveTo, Show}, + style::{Color, SetForegroundColor}, + terminal::{ + disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, + LeaveAlternateScreen, + }, ExecutableCommand, }; use tokio::{ @@ -12,7 +18,10 @@ use tokio::{ sync::mpsc::{channel, Receiver, Sender}, }; -use crate::event::{handle_input, Event}; +use crate::{ + event::{handle_input, Event}, + state::State, +}; pub const TICK_RATE: u64 = 1000 / 20; @@ -22,7 +31,8 @@ pub struct App { event_rx: Receiver<Event>, running: bool, quote: Vec<String>, - current: (usize, usize), + state: State, + should_render: bool, } impl App { @@ -38,41 +48,145 @@ impl App { event_rx, event_tx, running: false, - current: (0, 0), + state: State::default(), + should_render: true, } } pub async fn run(&mut self) -> Result<(), Box<dyn Error>> { - self.stdout.execute(EnterAlternateScreen)?; + self.stdout.execute(EnterAlternateScreen)?.execute(Hide)?; enable_raw_mode()?; - let (ks_tx, ks_rx): (Sender<()>, Receiver<()>) = channel(1); + let (input_ks_tx, input_ks_rx): (Sender<()>, Receiver<()>) = channel(1); + let ev = self.event_tx.clone(); + spawn(async { + start_input_handler(ev, input_ks_rx).await; + }); + + let (tick_ks_tx, tick_ks_rx): (Sender<()>, Receiver<()>) = channel(1); let ev = self.event_tx.clone(); spawn(async { - start_input_handler(ev, ks_rx).await; + start_tick_generator(ev, tick_ks_rx).await; }); self.running = true; while self.running { self.process().await?; } - let _ = ks_tx.send(()); + input_ks_tx.send(()).await?; + tick_ks_tx.send(()).await?; disable_raw_mode()?; - self.stdout.execute(LeaveAlternateScreen)?; + self.stdout.execute(Show)?.execute(LeaveAlternateScreen)?; return Ok(()); } async fn process(&mut self) -> Result<(), Box<dyn Error>> { - match self.event_rx.recv().await.unwrap() { + let event = self.event_rx.recv().await.unwrap(); + match event { Event::Terminate => { self.running = false; } - Event::KeyPress(_) => todo!(), - Event::Backspace => todo!(), + Event::KeyPress(k) => self.handle_keypress(k).await, + Event::Backspace => self.handle_backspace().await, + Event::Render => self.render().await, + } + + if event != Event::Render { + self.should_render = true; } return Ok(()); } + + async fn handle_keypress(&mut self, k: char) { + if self.state.buffer == self.quote[self.state.current] && k == ' ' { + self.state.buffer.clear(); + self.state.current += 1; + if self.state.current == self.quote.len() { + // TODO Remove running set and set state to StateEnum::Result instead + self.running = false; + } + } else { + self.state.buffer.push(k); + } + } + + async fn handle_backspace(&mut self) { + if !self.state.buffer.is_empty() { + self.state.buffer.pop(); + } + } + + async fn render(&mut self) { + if !self.should_render { + return; + } + + self.stdout + .execute(Clear(ClearType::All)) + .unwrap() + .execute(SetForegroundColor(Color::Green)) + .unwrap() + .execute(MoveTo(0, 0)) + .unwrap(); + let done = self.quote[..self.state.current].join(" "); + print!("{}", done); + + if !done.trim().is_empty() { + print!(" "); + } + + for i in 0..self.state.buffer.len() { + if i >= self.quote[self.state.current].len() { + break; + } + + let c = self.quote[self.state.current].chars().nth(i).unwrap(); + if self.state.buffer.chars().nth(i).unwrap() == c { + self.stdout + .execute(SetForegroundColor(Color::Green)) + .unwrap(); + } else { + self.stdout.execute(SetForegroundColor(Color::Red)).unwrap(); + } + print!("{}", c); + } + + if self.state.buffer.len() < self.quote[self.state.current].len() { + self.stdout + .execute(SetForegroundColor(Color::Reset)) + .unwrap(); + let v = &self.quote[self.state.current][self.state.buffer.len()..]; + print!("{}", v); + } else if self.state.buffer.len() > self.quote[self.state.current].len() { + self.stdout + .execute(SetForegroundColor(Color::Yellow)) + .unwrap(); + let v = &self.state.buffer[self.quote[self.state.current].len()..]; + print!("{}", v); + } + + print!(" "); + + self.stdout + .execute(SetForegroundColor(Color::Reset)) + .unwrap(); + let to_do = self.quote[self.state.current + 1..].join(" "); + println!("{}", to_do); + self.should_render = false; + } +} + +async fn start_tick_generator(ev: Sender<Event>, mut kill_switch: Receiver<()>) { + loop { + tokio::select! { + _ = async { + tokio::time::sleep(Duration::from_millis(TICK_RATE)).await; + ev.send(Event::Render).await + } => (), + _ = kill_switch.recv() => return, + } + } } async fn start_input_handler(ev: Sender<Event>, mut kill_switch: Receiver<()>) { diff --git a/src/event.rs b/src/event.rs index d5cdabe..400fcb6 100644 --- a/src/event.rs +++ b/src/event.rs @@ -5,10 +5,12 @@ use tokio::sync::mpsc::Sender; use crate::app::TICK_RATE; +#[derive(PartialEq)] pub enum Event { Terminate, KeyPress(char), Backspace, + Render, } // TODO diff --git a/src/main.rs b/src/main.rs index d10b40c..520f0a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod app; pub mod event; +pub mod state; use std::{error::Error, fs::read_to_string, path::Path}; diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..18bcb68 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,20 @@ +pub struct State { + pub current: usize, + pub buffer: String, + pub state: StateEnum, +} + +pub enum StateEnum { + Typing, + Results(u8), +} + +impl Default for State { + fn default() -> Self { + State { + current: 0, + buffer: String::new(), + state: StateEnum::Typing, + } + } +} |
