feat(countdown): rocket countdown (#45)

This commit is contained in:
Jens Krause 2025-01-08 18:52:18 +01:00 committed by GitHub
parent 468b4a5abf
commit c8af76c9e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 111 additions and 11 deletions

BIN
demo/rocket-countdown.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -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

View File

@ -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

View File

@ -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(),
}

View File

@ -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 {

View File

@ -207,10 +207,18 @@ impl<T> ClockState<T> {
&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<T> ClockState<T> {
self.update_format();
}
pub fn is_done(&self) -> bool {
self.mode == Mode::Done
}
fn update_format(&mut self) {
self.format = self.get_format();
}

View File

@ -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::Countdown>,
/// clock to count up afterwards
timer: ClockState<clock::Timer>,
}
impl CountdownState {
pub const fn new(clock: ClockState<clock::Countdown>) -> Self {
Self { clock }
pub fn new(clock: ClockState<clock::Countdown>) -> Self {
Self {
clock,
timer: ClockState::<clock::Timer>::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<clock::Countdown> {
&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,