diff --git a/src/main.rs b/src/main.rs index 46b6764..ea01481 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,8 @@ fn main() { let mut domasi: Pomodoro = Default::default(); match get_alert() { - Some(alert) => domasi.start_with_alert(alert), - None => domasi.start(), + Some(alert) => domasi.start(Some(alert)).unwrap(), + None => domasi.start(None).unwrap(), } } diff --git a/src/pomodoro.rs b/src/pomodoro.rs index a2108e6..0194a02 100644 --- a/src/pomodoro.rs +++ b/src/pomodoro.rs @@ -1,8 +1,9 @@ use super::Alert; +use crossterm::event::{self, Event, KeyCode, KeyModifiers}; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use std::error::Error; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::{self, Write}; -use std::thread; use std::time::{Duration, Instant}; #[derive(Debug, Copy, Clone, Default)] @@ -18,34 +19,49 @@ static WORK_TIME: u64 = 1500; // Default: 1500 (25min) static SHORT_BREAK_TIME: u64 = 300; // Default: 300 (5min) static LONG_BREAK_TIME: u64 = 600; // Default: 600 (10min) -static POLLING_RATE: Duration = Duration::from_millis(300); +static POLLING_RATE: Duration = Duration::from_millis(100); impl Pomodoro { pub fn new() -> Self { Self::default() } - pub fn start_with_alert(&mut self, alert: Alert) { + pub fn start(&mut self, maybe_alert: Option) -> crossterm::Result<()> { + enable_raw_mode()?; + loop { - if let Status::Complete = self.poll(Some(&alert)) { + if let Status::Complete = self.poll(maybe_alert.as_ref()) { break; } - Self::sleep(); - } - } + if event::poll(POLLING_RATE)? { + if let Event::Key(key_event) = event::read()? { + match key_event.code { + KeyCode::Char('p') | KeyCode::Char('P') => { + self.paused.toggle(); - pub fn start(&mut self) { - loop { - if let Status::Complete = self.poll(None) { - break; + if !self.paused.value { + // Apply the elapsed time + let duration = self.paused.duration; + match self.wait_start { + Some(instant) => self.wait_start = Some(instant + duration), + None => { + unreachable!("Unable to resume from a non-existent timer.") + } + } + + self.paused = Default::default(); + } + } + KeyCode::Char('q') | KeyCode::Char('Q') => break, + KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => break, + _ => {} + } + } } - - Self::sleep() } - } - fn sleep() { - thread::sleep(POLLING_RATE); + disable_raw_mode()?; + Ok(()) } fn notify_complete(&self) -> Result<(), Box> { @@ -116,6 +132,7 @@ impl Pomodoro { }; self.paused.previous = Some(now); + self.display_paused(); } Status::Active => self.display_time(), Status::NextState => { @@ -140,7 +157,6 @@ impl Pomodoro { } Status::Complete => { println!("\rPomodoro cycle complete!"); - let _ = self.notify_complete(); } Status::Inactive => {} @@ -150,10 +166,18 @@ impl Pomodoro { } fn display_time(&self) { - if let Some(earlier) = self.wait_start { - let wait = Instant::now() - earlier; - let left: Clock = (Self::wait_times(self.state) - wait).into(); - let _ = Self::print(&format!("\r{} {}", self.state, left)); + if let Some(start) = self.wait_start { + let remainder: Clock = (Self::wait_times(self.state) - (Instant::now() - start)).into(); + let _ = Self::print(&format!("\r{} {}", self.state, remainder)); + } + } + + fn display_paused(&self) { + if let Some(start) = self.wait_start { + let stop_time: Clock = (Self::wait_times(self.state) + - ((Instant::now() - self.paused.duration) - start)) + .into(); + let _ = Self::print(&format!("\r[PAUSED] {} {}", self.state, stop_time)); } } @@ -163,7 +187,10 @@ impl Pomodoro { // Empty String so that we can clear line before writing // from the most-left side of the terminal again. - handle.write_all(b"\r ")?; + // + // We write 24 spaces so that the entire line is always + // written over + handle.write_all(b"\r ")?; handle.write_all(text.as_bytes())?; handle.flush()?; Ok(()) @@ -274,6 +301,12 @@ struct PausedState { previous: Option, } +impl PausedState { + pub fn toggle(&mut self) { + self.value = !self.value; + } +} + #[derive(Debug, Copy, Clone)] enum Count { Increase, @@ -306,10 +339,10 @@ impl Default for State { impl Display for State { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match *self { - State::Work => f.write_str("Working: "), - State::ShortBreak => f.write_str("Resting: "), - State::LongBreak => f.write_str("REALLY Resting: "), - State::Inactive => f.write_str("Inactive: "), + State::Work => f.write_str("Working:"), + State::ShortBreak => f.write_str("Resting:"), + State::LongBreak => f.write_str("REALLY Resting:"), + State::Inactive => f.write_str("Inactive:"), } } }