From c8af76c9e51a11ac0ba9a81a931e7adad8217e25 Mon Sep 17 00:00:00 2001 From: Jens Krause <47693+sectore@users.noreply.github.com> Date: Wed, 8 Jan 2025 18:52:18 +0100 Subject: [PATCH] feat(countdown): rocket countdown (#45) --- demo/rocket-countdown.gif | Bin 0 -> 46218 bytes demo/rocket-countdown.tape | 22 +++++++++++ justfile | 5 +++ src/app.rs | 2 +- src/duration.rs | 4 ++ src/widgets/clock.rs | 12 ++++++ src/widgets/countdown.rs | 77 ++++++++++++++++++++++++++++++++----- 7 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 demo/rocket-countdown.gif create mode 100644 demo/rocket-countdown.tape diff --git a/demo/rocket-countdown.gif b/demo/rocket-countdown.gif new file mode 100644 index 0000000000000000000000000000000000000000..f0b448deb5c09b3c9ea5e512484fec94664b5932 GIT binary patch literal 46218 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ð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 literal 0 HcmV?d00001 diff --git a/demo/rocket-countdown.tape b/demo/rocket-countdown.tape new file mode 100644 index 0000000..e562a5c --- /dev/null +++ b/demo/rocket-countdown.tape @@ -0,0 +1,22 @@ +Output demo/rocket-countdown.gif + +# https://github.com/charmbracelet/vhs/blob/main/THEMES.md +Set Theme "AtomOneLight" + +Set FontSize 14 +Set Width 800 +Set Height 400 +Set Padding 0 +Set Margin 1 + +# --- START --- +Set LoopOffset 4 +Hide +Type "cargo run -- -m c -c 3" +Enter +Sleep 0.2 +Show +Type "s" +Sleep 6 +Type "r" +Sleep 1 diff --git a/justfile b/justfile index 15f7a1d..d08d9fa 100644 --- a/justfile +++ b/justfile @@ -68,3 +68,8 @@ alias dlt := demo-local-time demo-local-time: vhs demo/local-time.tape + +alias drc := demo-rocket-countdown + +demo-rocket-countdown: + vhs demo/rocket-countdown.tape diff --git a/src/app.rs b/src/app.rs index 7113711..bc693e3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -179,7 +179,7 @@ impl App { fn clock_is_running(&self) -> bool { match self.content { - Content::Countdown => self.countdown.get_clock().is_running(), + Content::Countdown => self.countdown.is_running(), Content::Timer => self.timer.get_clock().is_running(), Content::Pomodoro => self.pomodoro.get_clock().is_running(), } diff --git a/src/duration.rs b/src/duration.rs index 2dc388e..77a4821 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -86,6 +86,10 @@ impl DurationEx { let inner = self.inner.saturating_sub(ex.inner); Self { inner } } + + pub fn to_string_with_decis(self) -> String { + format!("{}.{}", self, self.decis()) + } } impl fmt::Display for DurationEx { diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 827d498..759badb 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -207,10 +207,18 @@ impl ClockState { &self.mode } + pub fn run(&mut self) { + self.mode = Mode::Tick + } + pub fn is_running(&self) -> bool { self.mode == Mode::Tick } + pub fn is_initial(&self) -> bool { + self.mode == Mode::Initial + } + pub fn is_edit_mode(&self) -> bool { matches!(self.mode, Mode::Editable(_, _)) } @@ -294,6 +302,10 @@ impl ClockState { self.update_format(); } + pub fn is_done(&self) -> bool { + self.mode == Mode::Done + } + fn update_format(&mut self) { self.format = self.get_format(); } diff --git a/src/widgets/countdown.rs b/src/widgets/countdown.rs index f1e9b62..967b890 100644 --- a/src/widgets/countdown.rs +++ b/src/widgets/countdown.rs @@ -5,32 +5,50 @@ use ratatui::{ text::Line, widgets::{StatefulWidget, Widget}, }; -use std::cmp::max; +use std::{cmp::max, time::Duration}; use crate::{ common::Style, + constants::TICK_VALUE_MS, events::{Event, EventHandler}, utils::center, - widgets::clock::{self, ClockState, ClockWidget}, + widgets::clock::{self, ClockState, ClockStateArgs, ClockWidget}, }; +/// State for Countdown Widget #[derive(Debug, Clone)] pub struct CountdownState { + /// clock to count down clock: ClockState, + /// clock to count up afterwards + timer: ClockState, } impl CountdownState { - pub const fn new(clock: ClockState) -> Self { - Self { clock } + pub fn new(clock: ClockState) -> Self { + Self { + clock, + timer: ClockState::::new(ClockStateArgs { + initial_value: Duration::ZERO, + current_value: Duration::ZERO, + tick_value: Duration::from_millis(TICK_VALUE_MS), + with_decis: false, + }), + } } pub fn set_with_decis(&mut self, with_decis: bool) { self.clock.with_decis = with_decis; + self.timer.with_decis = with_decis; } pub fn get_clock(&self) -> &ClockState { &self.clock } + + pub fn is_running(&self) -> bool { + self.clock.is_running() || self.timer.is_running() + } } impl EventHandler for CountdownState { @@ -38,20 +56,35 @@ impl EventHandler for CountdownState { let edit_mode = self.clock.is_edit_mode(); match event { Event::Tick => { - self.clock.tick(); - } - Event::Key(key) if key.code == KeyCode::Char('r') => { - self.clock.reset(); + if !self.clock.is_done() { + self.clock.tick(); + } else { + self.timer.tick(); + if self.timer.is_initial() { + self.timer.run(); + } + } } Event::Key(key) => match key.code { KeyCode::Char('r') => { + // reset both clocks self.clock.reset(); + self.timer.reset(); } KeyCode::Char('s') => { - self.clock.toggle_pause(); + // toggle pause status depending on who is running + if !self.clock.is_done() { + self.clock.toggle_pause(); + } else { + self.timer.toggle_pause(); + } } KeyCode::Char('e') => { self.clock.toggle_edit(); + // stop + reset timer entering `edit` mode + if self.timer.is_running() { + self.timer.toggle_pause(); + } } KeyCode::Left if edit_mode => { self.clock.edit_next(); @@ -61,9 +94,13 @@ impl EventHandler for CountdownState { } KeyCode::Up if edit_mode => { self.clock.edit_up(); + // whenever clock value is changed, reset timer + self.timer.reset(); } KeyCode::Down if edit_mode => { self.clock.edit_down(); + // whenever clock value is changed, reset timer + self.timer.reset(); } _ => return Some(event), }, @@ -81,7 +118,27 @@ impl StatefulWidget for Countdown { type State = CountdownState; fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let clock = ClockWidget::new(self.style); - let label = Line::raw((format!("Countdown {}", state.clock.get_mode())).to_uppercase()); + + let label = Line::raw( + if state.clock.is_done() { + if state.clock.with_decis { + format!( + "Countdown {} +{}", + state.clock.get_mode(), + state.timer.get_current_value().to_string_with_decis() + ) + } else { + format!( + "Countdown {} +{}", + state.clock.get_mode(), + state.timer.get_current_value() + ) + } + } else { + format!("Countdown {}", state.clock.get_mode()) + } + .to_uppercase(), + ); let area = center( area,