feat(countdown): rocket countdown (#45)
This commit is contained in:
parent
468b4a5abf
commit
c8af76c9e5
BIN
demo/rocket-countdown.gif
Normal file
BIN
demo/rocket-countdown.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
22
demo/rocket-countdown.tape
Normal file
22
demo/rocket-countdown.tape
Normal 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
|
||||
5
justfile
5
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
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user