diff --git a/src/main.rs b/src/main.rs index c7cd600..b29f008 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,26 @@ +use std::{ + io::{self, stdout}, + sync::mpsc, + thread, + time::Duration, +}; + +use crossterm::{ + ExecutableCommand, + event::{self, Event, KeyCode, KeyModifiers}, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, +}; +use ratatui::{ + Terminal, + backend::CrosstermBackend, + layout::Alignment, + style::{Color, Style}, + widgets::Paragraph, +}; + const SECONDS_PER_MIN: i64 = 60; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] enum TimerState { Work, Break, @@ -30,6 +50,17 @@ impl Termato { } } + fn toggle_running(&mut self) { + if self.is_running { + self.prior_state = self.state.clone(); + self.state = TimerState::Paused; + self.is_running = false; + } else { + self.state = self.prior_state.clone(); + self.is_running = true; + } + } + fn toggle_mode(&mut self) { let is_on_break = self.state == TimerState::Break || (self.state == TimerState::Paused && self.prior_state == TimerState::Break); @@ -58,17 +89,58 @@ impl Termato { } } -fn main() { - let mut termato = Termato::new(1, 1); +fn main() -> Result<(), io::Error> { + enable_raw_mode()?; + stdout().execute(EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout()); + let mut terminal = Terminal::new(backend)?; + let mut termato = Termato::new(1, 1); + + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { loop { - termato.is_running = true; - - termato.tick(); - - println!("Time remaining: {}", termato.time_remaining_in_sec); - println!("State: {:?}", termato.state); + thread::sleep(Duration::from_secs(1)); + if tx.send(()).is_err() { + break; + } } + }); + + loop { + terminal.draw(|f| { + let size = f.area(); + let minutes = termato.time_remaining_in_sec / SECONDS_PER_MIN; + let seconds = termato.time_remaining_in_sec % SECONDS_PER_MIN; + let time_str = format!("{:02}:{:02}", minutes, seconds); + + let paragraph = Paragraph::new(time_str) + .style(Style::default().fg(Color::White)) + .alignment(Alignment::Center); + + f.render_widget(paragraph, size); + })?; + + if event::poll(Duration::from_millis(100))? + && let Event::Key(key) = event::read()? + && key.kind == event::KeyEventKind::Press + { + match key.code { + KeyCode::Char('q') | KeyCode::Char('Q') => break, + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => break, + KeyCode::Char(' ') => termato.toggle_running(), + _ => {} + } + } + + if rx.try_recv().is_ok() { + termato.tick(); + } + } + + disable_raw_mode()?; + stdout().execute(LeaveAlternateScreen)?; + Ok(()) } #[cfg(test)] @@ -160,4 +232,27 @@ mod tests { assert_eq!(termato.duration_in_secs, 120); assert_eq!(termato.time_remaining_in_sec, 120); } + + #[test] + fn termato_toggle_running_when_called_and_already_running_it_should_pause() { + let mut termato = Termato::new(1, 1); + termato.is_running = true; + + termato.toggle_running(); + + assert_eq!(termato.state, TimerState::Paused); + assert_eq!(termato.prior_state, TimerState::Work); + assert!(!termato.is_running) + } + + #[test] + fn termato_toggle_running_when_called_and_already_paused_it_should_run() { + let mut termato = Termato::new(1, 1); + + termato.toggle_running(); + + assert_eq!(termato.state, TimerState::Work); + assert_eq!(termato.prior_state, TimerState::Work); + assert!(termato.is_running) + } }