support mupltiple durations for timer (#24)

This commit is contained in:
Jimmy 2022-09-08 09:32:49 +08:00 committed by GitHub
parent aacb453fdb
commit 0e12a7f532
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 28 deletions

View file

@ -42,8 +42,17 @@ pub enum Mode {
/// The timer mode displays the remaining time until the timer is finished. /// The timer mode displays the remaining time until the timer is finished.
Timer { Timer {
/// Initial duration for timer, value can be 10s for 10 seconds, 1m for 1 minute, etc. /// Initial duration for timer, value can be 10s for 10 seconds, 1m for 1 minute, etc.
#[clap(short, long, value_parser = parse_duration, default_value = "5m")] /// Also accept mulitple duration value and run the timers sequentially, eg. 25m 5m
duration: Duration, #[clap(short, long="duration", value_parser = parse_duration, min_values=1, default_value = "5m")]
durations: Vec<Duration>,
/// Set the title for the timer, also accept mulitple titles for each durations correspondingly
#[clap(short, long = "title", min_values = 0)]
titles: Vec<String>,
/// Restart the timer when timer is over
#[clap(long, short, takes_value = false)]
repeat: bool,
/// Hide milliseconds /// Hide milliseconds
#[clap(long = "no-millis", short = 'M', takes_value = false)] #[clap(long = "no-millis", short = 'M', takes_value = false)]
@ -150,7 +159,9 @@ impl App {
}); });
} }
Mode::Timer { Mode::Timer {
duration, durations,
titles,
repeat,
no_millis, no_millis,
paused, paused,
execute, execute,
@ -161,9 +172,11 @@ impl App {
DurationFormat::HourMinSecDeci DurationFormat::HourMinSecDeci
}; };
self.timer = Some(Timer::new( self.timer = Some(Timer::new(
*duration,
self.size, self.size,
style, style,
durations.to_owned(),
titles.to_owned(),
*repeat,
format, format,
*paused, *paused,
execute.to_owned(), execute.to_owned(),

View file

@ -1,4 +1,4 @@
use std::{cell::RefCell, process::Command}; use std::{cell::RefCell, cmp::min, process::Command};
use crate::clock_text::BricksText; use crate::clock_text::BricksText;
use chrono::{DateTime, Duration, Local}; use chrono::{DateTime, Duration, Local};
@ -11,40 +11,60 @@ use super::{format_duration, render_centered, DurationFormat};
pub struct Timer { pub struct Timer {
pub size: u16, pub size: u16,
pub style: Style, pub style: Style,
pub repeat: bool,
pub durations: Vec<Duration>,
pub titles: Vec<String>,
pub execute: Vec<String>, pub execute: Vec<String>,
format: DurationFormat, format: DurationFormat,
duration: Duration, passed: Duration,
ended_at: Option<DateTime<Local>>, started_at: Option<DateTime<Local>>,
execute_result: RefCell<Option<String>>, execute_result: RefCell<Option<String>>,
} }
impl Timer { impl Timer {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new( pub(crate) fn new(
duration: Duration,
size: u16, size: u16,
style: Style, style: Style,
durations: Vec<Duration>,
titles: Vec<String>,
repeat: bool,
format: DurationFormat, format: DurationFormat,
paused: bool, paused: bool,
execute: Vec<String>, execute: Vec<String>,
) -> Self { ) -> Self {
Self { Self {
duration,
size, size,
execute,
style, style,
durations,
titles,
repeat,
execute,
format, format,
ended_at: (!paused).then(|| Local::now() + duration), passed: Duration::zero(),
started_at: (!paused).then(Local::now),
execute_result: RefCell::new(None), execute_result: RefCell::new(None),
} }
} }
pub(crate) fn remaining_time(&self) -> Duration { pub(crate) fn remaining_time(&self) -> (Duration, usize) {
if let Some(end_at) = self.ended_at { let total_passed = if let Some(started_at) = self.started_at {
let now = Local::now(); self.passed + (Local::now() - started_at)
end_at.signed_duration_since(now)
} else { } else {
self.duration self.passed
};
let mut idx = 0;
let mut next_checkpoint = self.durations[idx];
while next_checkpoint < total_passed {
if idx >= self.durations.len() - 1 && !self.repeat {
break;
}
idx = (idx + 1) % self.durations.len();
next_checkpoint = next_checkpoint + self.durations[idx];
} }
(next_checkpoint - total_passed, idx)
} }
} }
@ -70,7 +90,7 @@ fn execute(execute: &[String]) -> String {
impl Widget for &Timer { impl Widget for &Timer {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let remaining_time = self.remaining_time(); let (remaining_time, idx) = self.remaining_time();
let time_str = if remaining_time < Duration::zero() { let time_str = if remaining_time < Duration::zero() {
if !self.execute.is_empty() && self.execute_result.borrow().is_none() { if !self.execute.is_empty() && self.execute_result.borrow().is_none() {
let result = execute(&self.execute); let result = execute(&self.execute);
@ -85,35 +105,37 @@ impl Widget for &Timer {
format_duration(remaining_time, self.format) format_duration(remaining_time, self.format)
}; };
let header = if self.titles.is_empty() {
None
} else {
Some(self.titles[min(idx, self.titles.len() - 1)].clone())
};
let text = BricksText::new(time_str.as_str(), self.size, self.size, self.style); let text = BricksText::new(time_str.as_str(), self.size, self.size, self.style);
let footer = if self.is_paused() { let footer = if self.is_paused() {
Some("PAUSED (press <SPACE> to resume)".to_string()) Some("PAUSED (press <SPACE> to resume)".to_string())
} else { } else {
self.execute_result.borrow().clone() self.execute_result.borrow().clone()
}; };
render_centered(area, buf, &text, None, footer); render_centered(area, buf, &text, header, footer);
} }
} }
impl Pause for Timer { impl Pause for Timer {
fn is_paused(&self) -> bool { fn is_paused(&self) -> bool {
self.ended_at.is_none() self.started_at.is_none()
} }
fn pause(&mut self) { fn pause(&mut self) {
if let Some(end_at) = self.ended_at { if let Some(started_at) = self.started_at {
if end_at <= Local::now() { self.passed = self.passed + (Local::now() - started_at);
self.duration = Duration::zero(); self.started_at = None;
} else {
self.duration = end_at - Local::now();
self.ended_at = None;
}
} }
} }
fn resume(&mut self) { fn resume(&mut self) {
if self.ended_at.is_none() { if self.started_at.is_none() {
self.ended_at = Some(Local::now() + self.duration); self.started_at = Some(Local::now());
} }
} }
} }