diff --git a/src/app.rs b/src/app.rs index a990b8b..57a606d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use crate::{ countdown::{Countdown, CountdownWidget}, footer::Footer, header::Header, - pomodoro::Pomodoro, + pomodoro::{Pomodoro, PomodoroWidget}, timer::{Timer, TimerWidget}, }, }; @@ -18,6 +18,7 @@ use ratatui::{ layout::{Constraint, Layout, Rect}, widgets::{StatefulWidget, Widget}, }; +use std::time::Duration; use tracing::debug; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -40,22 +41,27 @@ pub struct App { show_menu: bool, countdown: Countdown, timer: Timer, + pomodoro: Pomodoro, } impl Default for App { fn default() -> Self { Self { mode: Mode::Running, - content: Content::Countdown, + content: Content::Pomodoro, show_menu: false, countdown: Countdown::new( "Countdown".into(), Clock::::new( - 10 * 60 * 1000, /* 10min in milliseconds */ - TICK_VALUE_MS, + Duration::from_secs(10 * 60 /* 10min */), + Duration::from_millis(TICK_VALUE_MS), ), ), - timer: Timer::new("Timer".into(), Clock::::new(0, TICK_VALUE_MS)), + timer: Timer::new( + "Timer".into(), + Clock::::new(Duration::ZERO, Duration::from_millis(TICK_VALUE_MS)), + ), + pomodoro: Pomodoro::new(), } } } @@ -68,10 +74,11 @@ impl App { pub async fn run(&mut self, mut terminal: Terminal, mut events: Events) -> Result<()> { while self.is_running() { if let Some(event) = events.next().await { + // Pipe events into subviews and handle 'rest' events only match self.content { Content::Countdown => self.countdown.update(event.clone()), Content::Timer => self.timer.update(event.clone()), - _ => {} + Content::Pomodoro => self.pomodoro.update(event.clone()), }; match event { Event::Render | Event::Resize => { @@ -89,6 +96,14 @@ impl App { self.mode != Mode::Quit } + fn is_edit_mode(&mut self) -> bool { + match self.content { + Content::Countdown => self.countdown.is_edit_mode(), + Content::Pomodoro => self.pomodoro.is_edit_mode(), + _ => false, + } + } + fn handle_key_event(&mut self, key: KeyEvent) { debug!("Received key {:?}", key.code); match key.code { @@ -97,6 +112,18 @@ impl App { KeyCode::Char('t') => self.content = Content::Timer, KeyCode::Char('p') => self.content = Content::Pomodoro, KeyCode::Char('m') => self.show_menu = !self.show_menu, + KeyCode::Up => { + // TODO: Pipe events into subviews properly + if !self.is_edit_mode() { + self.show_menu = true + } + } + KeyCode::Down => { + // TODO: Pipe events into subviews properly + if !self.is_edit_mode() { + self.show_menu = false + } + } _ => {} }; } @@ -116,7 +143,7 @@ impl AppWidget { match state.content { Content::Timer => TimerWidget.render(area, buf, &mut state.timer), Content::Countdown => CountdownWidget.render(area, buf, &mut state.countdown), - Content::Pomodoro => Pomodoro::new("Pomodoro".into()).render(area, buf), + Content::Pomodoro => PomodoroWidget.render(area, buf, &mut state.pomodoro), }; } } diff --git a/src/constants.rs b/src/constants.rs index da9dd3e..e42ce47 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,4 +1,5 @@ pub static APP_NAME: &str = env!("CARGO_PKG_NAME"); +// TODO: Grab those values from `Args` pub static TICK_VALUE_MS: u64 = 1000 / 10; // 0.1 sec in milliseconds pub static FPS_VALUE_MS: u64 = 1000 / 60; // 60 FPS in milliseconds diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 6a55950..f540baf 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -5,27 +5,45 @@ use strum::Display; use ratatui::{ buffer::Buffer, - layout::{Constraint, Direction, Layout, Position, Rect}, - widgets::StatefulWidget, + layout::{Constraint, Layout, Position, Rect, Size}, + symbols, + widgets::{Block, Borders, StatefulWidget, Widget}, }; +use crate::utils::center_horizontal; + #[derive(Debug, Copy, Clone, Display, PartialEq, Eq)] +pub enum Time { + Seconds, + Minutes, + // TODO: Handle hours + // Hours, +} + +#[derive(Debug, Clone, Display, PartialEq, Eq)] pub enum Mode { Initial, Tick, Pause, + Editable( + Time, + Box, /* previous mode before starting editing */ + ), Done, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct Clock { - initial_value: u64, - tick_value: u64, - current_value: u64, + initial_value: Duration, + tick_value: Duration, + current_value: Duration, mode: Mode, phantom: PhantomData, } +// TODO: Change it to 23:59:59 after supporting `hours` +const MAX_EDITABLE_DURATION: Duration = Duration::from_secs(60 * 60); // 1 hour + impl Clock { pub fn toggle_pause(&mut self) { self.mode = if self.mode == Mode::Tick { @@ -35,31 +53,134 @@ impl Clock { } } + pub fn toggle_edit(&mut self) { + self.mode = match self.mode.clone() { + Mode::Editable(_, prev) => { + let p = *prev; + // special cases: Should `Mode` be updated? + // 1. `Done` -> `Initial` ? + if p == Mode::Done && self.current_value.gt(&Duration::ZERO) { + Mode::Initial + } + // 2. `_` -> `Done` ? + else if p != Mode::Done && self.current_value.eq(&Duration::ZERO) { + Mode::Done + } + // 3. `_` -> `_` (no change) + else { + p + } + } + mode => Mode::Editable(Time::Minutes, Box::new(mode)), + }; + } + + pub fn edit_up(&mut self) { + self.current_value = match self.mode { + Mode::Editable(Time::Seconds, _) => { + if self + .current_value + // TODO: Change it to 24:59:59 after supporting `hours` + // At the meantime: < 59:59 + .lt(&MAX_EDITABLE_DURATION.saturating_sub(Duration::from_secs(1))) + { + self.current_value.saturating_add(Duration::from_secs(1)) + } else { + self.current_value + } + } + Mode::Editable(Time::Minutes, _) => { + if self + .current_value + // TODO: Change it to 24:59:00 after supporting `hours` + // At the meantime: < 59:00 + .lt(&MAX_EDITABLE_DURATION.saturating_sub(Duration::from_secs(60))) + { + self.current_value.saturating_add(Duration::new(60, 0)) + } else { + self.current_value + } + } + _ => self.current_value, + }; + } + pub fn edit_down(&mut self) { + self.current_value = match self.mode { + Mode::Editable(Time::Seconds, _) => { + self.current_value.saturating_sub(Duration::new(1, 0)) + } + Mode::Editable(Time::Minutes, _) => { + self.current_value.saturating_sub(Duration::new(60, 0)) + } + _ => self.current_value, + }; + } + + pub fn get_mode(&mut self) -> Mode { + self.mode.clone() + } + + pub fn is_edit_mode(&mut self) -> bool { + matches!(self.mode, Mode::Editable(_, _)) + } + + pub fn edit_mode(&mut self) -> Option