From 0e12a7f5320c44d214122c94c11048f18f57e626 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 8 Sep 2022 09:32:49 +0800 Subject: [PATCH] support mupltiple durations for timer (#24) --- clock-tui/src/app.rs | 21 ++++++++-- clock-tui/src/app/modes/timer.rs | 70 +++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/clock-tui/src/app.rs b/clock-tui/src/app.rs index bca702e..fb4eeac 100644 --- a/clock-tui/src/app.rs +++ b/clock-tui/src/app.rs @@ -42,8 +42,17 @@ pub enum Mode { /// The timer mode displays the remaining time until the timer is finished. Timer { /// 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")] - duration: Duration, + /// Also accept mulitple duration value and run the timers sequentially, eg. 25m 5m + #[clap(short, long="duration", value_parser = parse_duration, min_values=1, default_value = "5m")] + durations: Vec, + + /// Set the title for the timer, also accept mulitple titles for each durations correspondingly + #[clap(short, long = "title", min_values = 0)] + titles: Vec, + + /// Restart the timer when timer is over + #[clap(long, short, takes_value = false)] + repeat: bool, /// Hide milliseconds #[clap(long = "no-millis", short = 'M', takes_value = false)] @@ -150,7 +159,9 @@ impl App { }); } Mode::Timer { - duration, + durations, + titles, + repeat, no_millis, paused, execute, @@ -161,9 +172,11 @@ impl App { DurationFormat::HourMinSecDeci }; self.timer = Some(Timer::new( - *duration, self.size, style, + durations.to_owned(), + titles.to_owned(), + *repeat, format, *paused, execute.to_owned(), diff --git a/clock-tui/src/app/modes/timer.rs b/clock-tui/src/app/modes/timer.rs index dd567a8..f97938f 100644 --- a/clock-tui/src/app/modes/timer.rs +++ b/clock-tui/src/app/modes/timer.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, process::Command}; +use std::{cell::RefCell, cmp::min, process::Command}; use crate::clock_text::BricksText; use chrono::{DateTime, Duration, Local}; @@ -11,40 +11,60 @@ use super::{format_duration, render_centered, DurationFormat}; pub struct Timer { pub size: u16, pub style: Style, + pub repeat: bool, + pub durations: Vec, + pub titles: Vec, pub execute: Vec, format: DurationFormat, - duration: Duration, - ended_at: Option>, + passed: Duration, + started_at: Option>, execute_result: RefCell>, } impl Timer { + #[allow(clippy::too_many_arguments)] pub(crate) fn new( - duration: Duration, size: u16, style: Style, + durations: Vec, + titles: Vec, + repeat: bool, format: DurationFormat, paused: bool, execute: Vec, ) -> Self { Self { - duration, size, - execute, style, + durations, + titles, + repeat, + execute, format, - ended_at: (!paused).then(|| Local::now() + duration), + passed: Duration::zero(), + started_at: (!paused).then(Local::now), execute_result: RefCell::new(None), } } - pub(crate) fn remaining_time(&self) -> Duration { - if let Some(end_at) = self.ended_at { - let now = Local::now(); - end_at.signed_duration_since(now) + pub(crate) fn remaining_time(&self) -> (Duration, usize) { + let total_passed = if let Some(started_at) = self.started_at { + self.passed + (Local::now() - started_at) } 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 { 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() { if !self.execute.is_empty() && self.execute_result.borrow().is_none() { let result = execute(&self.execute); @@ -85,35 +105,37 @@ impl Widget for &Timer { 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 footer = if self.is_paused() { Some("PAUSED (press to resume)".to_string()) } else { self.execute_result.borrow().clone() }; - render_centered(area, buf, &text, None, footer); + render_centered(area, buf, &text, header, footer); } } impl Pause for Timer { fn is_paused(&self) -> bool { - self.ended_at.is_none() + self.started_at.is_none() } fn pause(&mut self) { - if let Some(end_at) = self.ended_at { - if end_at <= Local::now() { - self.duration = Duration::zero(); - } else { - self.duration = end_at - Local::now(); - self.ended_at = None; - } + if let Some(started_at) = self.started_at { + self.passed = self.passed + (Local::now() - started_at); + self.started_at = None; } } fn resume(&mut self) { - if self.ended_at.is_none() { - self.ended_at = Some(Local::now() + self.duration); + if self.started_at.is_none() { + self.started_at = Some(Local::now()); } } }