diff --git a/src/app.rs b/src/app.rs index 7023f06..f972055 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,15 @@ use crate::{ clock::{self, Clock}, constants::TICK_VALUE_MS, - events::{Event, Events}, + events::{Event, EventHandler, Events}, terminal::Terminal, utils::center, widgets::{ - countdown::Countdown, footer::Footer, header::Header, pomodoro::Pomodoro, timer::Timer, + countdown::{Countdown, CountdownWidget}, + footer::Footer, + header::Header, + pomodoro::Pomodoro, + timer::{Timer, TimerWidget}, }, }; use color_eyre::Result; @@ -13,7 +17,7 @@ use ratatui::{ buffer::Buffer, crossterm::event::{KeyCode, KeyEvent}, layout::{Constraint, Layout, Rect}, - widgets::{Block, Widget}, + widgets::{Block, StatefulWidget, Widget}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -34,8 +38,8 @@ pub struct App { content: Content, mode: Mode, show_menu: bool, - clock_countdown: Clock, - clock_timer: Clock, + countdown: Countdown, + timer: Timer, } impl Default for App { @@ -44,11 +48,14 @@ impl Default for App { mode: Mode::Running, content: Content::Countdown, show_menu: false, - clock_countdown: Clock::::new( - 10 * 60 * 1000, /* 10min in milliseconds */ - TICK_VALUE_MS, + countdown: Countdown::new( + "Countdown".into(), + Clock::::new( + 10 * 60 * 1000, /* 10min in milliseconds */ + TICK_VALUE_MS, + ), ), - clock_timer: Clock::::new(0, TICK_VALUE_MS), + timer: Timer::new("Timer".into(), Clock::::new(0, TICK_VALUE_MS)), } } } @@ -61,13 +68,15 @@ 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 { + match self.content { + Content::Countdown => self.countdown.update(event.clone()), + Content::Timer => self.timer.update(event.clone()), + _ => {} + }; match event { - Event::Render | Event::Resize(_, _) => { + Event::Render | Event::Resize => { self.draw(&mut terminal)?; } - Event::Tick => { - self.tick(); - } Event::Key(key) => self.handle_key_event(key), _ => {} } @@ -84,73 +93,48 @@ impl App { match key.code { KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit, KeyCode::Char('c') => self.content = Content::Countdown, - KeyCode::Char('s') => self.toggle(), KeyCode::Char('t') => self.content = Content::Timer, KeyCode::Char('p') => self.content = Content::Pomodoro, KeyCode::Char('m') => self.show_menu = !self.show_menu, - KeyCode::Char('r') => self.reset(), _ => {} }; } - fn draw(&self, terminal: &mut Terminal) -> Result<()> { + fn draw(&mut self, terminal: &mut Terminal) -> Result<()> { terminal.draw(|frame| { - frame.render_widget(self, frame.area()); + frame.render_stateful_widget(AppWidget, frame.area(), self); })?; Ok(()) } +} - fn render_content(&self, area: Rect, buf: &mut Buffer) { +struct AppWidget; + +impl AppWidget { + fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut App) { // center content let area = center(area, Constraint::Length(50), Constraint::Length(2)); - match self.content { - Content::Timer => { - Timer::new("Timer".into(), self.clock_timer.clone()).render(area, buf) - } - Content::Countdown => { - Countdown::new("Countdown".into(), self.clock_countdown.clone()).render(area, buf) - } + 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), }; } - - fn reset(&mut self) { - match self.content { - Content::Timer => self.clock_timer.reset(), - Content::Countdown => self.clock_countdown.reset(), - _ => {} - }; - } - - fn toggle(&mut self) { - match self.content { - Content::Timer => self.clock_timer.toggle_pause(), - Content::Countdown => self.clock_countdown.toggle_pause(), - _ => {} - }; - } - - fn tick(&mut self) { - match self.content { - Content::Timer => self.clock_timer.tick(), - Content::Countdown => self.clock_countdown.tick(), - _ => {} - }; - } } -impl Widget for &App { - fn render(self, area: Rect, buf: &mut Buffer) { +impl StatefulWidget for AppWidget { + type State = App; + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let vertical = Layout::vertical([ Constraint::Length(1), Constraint::Fill(0), - Constraint::Length(if self.show_menu { 2 } else { 1 }), + Constraint::Length(if state.show_menu { 2 } else { 1 }), ]); let [v0, v1, v4] = vertical.areas(area); Block::new().render(area, buf); Header::new(true).render(v0, buf); - self.render_content(v1, buf); - Footer::new(self.show_menu, self.content).render(v4, buf); + self.render_content(v1, buf, state); + Footer::new(state.show_menu, state.content).render(v4, buf); } } diff --git a/src/events.rs b/src/events.rs index 7ac6ea6..e3f7d2d 100644 --- a/src/events.rs +++ b/src/events.rs @@ -14,13 +14,11 @@ enum StreamKey { } #[derive(Clone, Debug)] pub enum Event { - Init, - Quit, Error, Tick, Render, Key(KeyEvent), - Resize(u16, u16), + Resize, } pub struct Events { @@ -69,10 +67,14 @@ fn crossterm_stream() -> Pin>> { Ok(CrosstermEvent::Key(key)) if key.kind == KeyEventKind::Press => { Some(Event::Key(key)) } - Ok(CrosstermEvent::Resize(x, y)) => Some(Event::Resize(x, y)), + Ok(CrosstermEvent::Resize(_, _)) => Some(Event::Resize), Err(_) => Some(Event::Error), _ => None, } }), ) } + +pub trait EventHandler { + fn update(&mut self, _: Event); +} diff --git a/src/widgets/countdown.rs b/src/widgets/countdown.rs index 8f7fba4..4459b11 100644 --- a/src/widgets/countdown.rs +++ b/src/widgets/countdown.rs @@ -1,12 +1,16 @@ use ratatui::{ buffer::Buffer, + crossterm::event::KeyCode, layout::{Constraint, Layout, Rect}, - widgets::{Paragraph, Widget}, + widgets::{Paragraph, StatefulWidget, Widget}, }; -use crate::clock::{self, Clock}; +use crate::{ + clock::{self, Clock}, + events::{Event, EventHandler}, +}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Countdown { headline: String, clock: Clock, @@ -18,13 +22,33 @@ impl Countdown { } } -impl Widget for Countdown { - fn render(mut self, area: Rect, buf: &mut Buffer) { - let h = Paragraph::new(self.headline).centered(); - let c = Paragraph::new(self.clock.format()).centered(); +impl EventHandler for Countdown { + fn update(&mut self, event: Event) { + match event { + Event::Tick => { + self.clock.tick(); + } + Event::Key(key) if key.code == KeyCode::Char('s') => { + self.clock.toggle_pause(); + } + Event::Key(key) if key.code == KeyCode::Char('r') => { + self.clock.reset(); + } + _ => {} + } + } +} + +pub struct CountdownWidget; + +impl StatefulWidget for &CountdownWidget { + type State = Countdown; + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + let h = Paragraph::new(state.headline.clone()).centered(); + let c = Paragraph::new(state.clock.format()).centered(); let [v1, v2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area); h.render(v1, buf); - c.render(v2, buf) + c.render(v2, buf); } } diff --git a/src/widgets/timer.rs b/src/widgets/timer.rs index 037046c..114270d 100644 --- a/src/widgets/timer.rs +++ b/src/widgets/timer.rs @@ -1,12 +1,16 @@ use ratatui::{ buffer::Buffer, + crossterm::event::KeyCode, layout::{Constraint, Layout, Rect}, - widgets::{Paragraph, Widget}, + widgets::{Paragraph, StatefulWidget, Widget}, }; -use crate::clock::{self, Clock}; +use crate::{ + clock::{self, Clock}, + events::{Event, EventHandler}, +}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Timer { headline: String, clock: Clock, @@ -18,10 +22,30 @@ impl Timer { } } -impl Widget for Timer { - fn render(mut self, area: Rect, buf: &mut Buffer) { - let h = Paragraph::new(self.headline).centered(); - let c = Paragraph::new(self.clock.format()).centered(); +impl EventHandler for Timer { + fn update(&mut self, event: Event) { + match event { + Event::Tick => { + self.clock.tick(); + } + Event::Key(key) if key.code == KeyCode::Char('s') => { + self.clock.toggle_pause(); + } + Event::Key(key) if key.code == KeyCode::Char('r') => { + self.clock.reset(); + } + _ => {} + } + } +} + +pub struct TimerWidget; + +impl StatefulWidget for TimerWidget { + type State = Timer; + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + let h = Paragraph::new(state.headline.clone()).centered(); + let c = Paragraph::new(state.clock.format()).centered(); let [v1, v2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area); h.render(v1, buf);