generated from PlexSheep/rs-base
155 lines
4.7 KiB
Rust
155 lines
4.7 KiB
Rust
use chrono::Duration;
|
|
use clap::Subcommand;
|
|
use crossterm::event::KeyCode;
|
|
use regex::Regex;
|
|
use tui::backend::Backend;
|
|
use tui::style::Color;
|
|
use tui::style::Style;
|
|
use tui::Frame;
|
|
|
|
use self::modes::Clock;
|
|
use self::modes::Stopwatch;
|
|
use self::modes::Timer;
|
|
|
|
pub(crate) mod modes;
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
pub(crate) enum Mode {
|
|
/// The clock mode displays the current time, the default mode.
|
|
Clock,
|
|
/// The timer mode displays the remaining time until the timer is finished.
|
|
Timer {
|
|
#[clap(short, long, value_parser = parse_duration, default_value = "5m")]
|
|
duration: Duration,
|
|
},
|
|
/// The stopwatch mode displays the elapsed time since it was started.
|
|
Stopwatch,
|
|
}
|
|
|
|
#[derive(clap::Parser)]
|
|
#[clap(about = "A clock app in terminal", long_about = None)]
|
|
pub(crate) struct App {
|
|
#[clap(subcommand)]
|
|
pub mode: Option<Mode>,
|
|
#[clap(short, long, value_parser = parse_color, default_value = "green")]
|
|
pub color: Color,
|
|
#[clap(short, long, value_parser, default_value = "1")]
|
|
pub size: u16,
|
|
|
|
#[clap(skip)]
|
|
clock: Option<Clock>,
|
|
#[clap(skip)]
|
|
timer: Option<Timer>,
|
|
#[clap(skip)]
|
|
stopwatch: Option<Stopwatch>,
|
|
}
|
|
|
|
impl App {
|
|
pub fn init_app(&mut self) {
|
|
let style = Style::default().fg(self.color);
|
|
let mode = self.mode.as_ref().unwrap_or(&Mode::Clock);
|
|
match mode {
|
|
Mode::Clock => {
|
|
self.clock = Some(Clock {
|
|
size: self.size,
|
|
style,
|
|
long: false,
|
|
});
|
|
}
|
|
Mode::Timer { duration } => {
|
|
self.timer = Some(Timer::new(duration.to_owned(), self.size, style));
|
|
}
|
|
Mode::Stopwatch => {
|
|
self.stopwatch = Some(Stopwatch::new(self.size, style));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn ui<B: Backend>(&self, f: &mut Frame<B>) {
|
|
if let Some(w) = self.clock.as_ref() {
|
|
f.render_widget(w, f.size());
|
|
} else if let Some(w) = self.timer.as_ref() {
|
|
f.render_widget(w, f.size());
|
|
} else if let Some(w) = self.stopwatch.as_ref() {
|
|
f.render_widget(w, f.size());
|
|
}
|
|
}
|
|
|
|
pub fn on_key(&mut self, key: KeyCode) {
|
|
if let Some(_w) = self.clock.as_mut() {
|
|
} else if let Some(w) = self.timer.as_mut() {
|
|
match key {
|
|
KeyCode::Char(' ') => {
|
|
if w.is_paused() {
|
|
w.resume();
|
|
} else {
|
|
w.pause();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
} else if let Some(w) = self.stopwatch.as_mut() {
|
|
match key {
|
|
KeyCode::Char(' ') => {
|
|
if w.is_paused() {
|
|
w.resume();
|
|
} else {
|
|
w.pause();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_duration(s: &str) -> Result<Duration, String> {
|
|
let reg = Regex::new(r"^(\d+)([smhdSMHD])$").unwrap();
|
|
let cap = reg
|
|
.captures(s)
|
|
.ok_or_else(|| format!("{} is not a valid duration", s))?;
|
|
|
|
let num = cap.get(1).unwrap().as_str().parse::<i64>().unwrap();
|
|
let unit = cap.get(2).unwrap().as_str().to_lowercase();
|
|
|
|
match unit.as_str() {
|
|
"s" => Ok(Duration::seconds(num)),
|
|
"m" => Ok(Duration::minutes(num)),
|
|
"h" => Ok(Duration::hours(num)),
|
|
"d" => Ok(Duration::days(num)),
|
|
_ => Err(format!("Invalid duration: {}", s).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_color(s: &str) -> Result<Color, String> {
|
|
let s = s.to_lowercase();
|
|
let reg = Regex::new(r"^#([0-9a-f]{6})$").unwrap();
|
|
match s.as_str() {
|
|
"black" => Ok(Color::Black),
|
|
"red" => Ok(Color::Red),
|
|
"green" => Ok(Color::Green),
|
|
"yellow" => Ok(Color::Yellow),
|
|
"blue" => Ok(Color::Blue),
|
|
"magenta" => Ok(Color::Magenta),
|
|
"cyan" => Ok(Color::Cyan),
|
|
"gray" => Ok(Color::Gray),
|
|
"darkgray" => Ok(Color::DarkGray),
|
|
"lightred" => Ok(Color::LightRed),
|
|
"lightGreen" => Ok(Color::LightGreen),
|
|
"lightYellow" => Ok(Color::LightYellow),
|
|
"lightBlue" => Ok(Color::LightBlue),
|
|
"lightMagenta" => Ok(Color::LightMagenta),
|
|
"lightCyan" => Ok(Color::LightCyan),
|
|
"white" => Ok(Color::White),
|
|
s => {
|
|
let cap = reg
|
|
.captures(s)
|
|
.ok_or_else(|| format!("Invalid color: {}", s))?;
|
|
let hex = cap.get(1).unwrap().as_str();
|
|
let r = hex[1..3].parse::<u8>().unwrap();
|
|
let g = hex[3..5].parse::<u8>().unwrap();
|
|
let b = hex[5..7].parse::<u8>().unwrap();
|
|
Ok(Color::Rgb(r, g, b))
|
|
}
|
|
}
|
|
}
|