258 lines
6.6 KiB
Rust
258 lines
6.6 KiB
Rust
use clap::ValueEnum;
|
|
use ratatui::symbols::shade;
|
|
use serde::{Deserialize, Serialize};
|
|
use strum::EnumString;
|
|
use time::{OffsetDateTime, format_description};
|
|
|
|
#[derive(
|
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default, Serialize, Deserialize,
|
|
)]
|
|
pub enum Content {
|
|
#[default]
|
|
#[value(name = "countdown", alias = "c")]
|
|
Countdown,
|
|
#[value(name = "timer", alias = "t")]
|
|
Timer,
|
|
#[value(name = "pomodoro", alias = "p")]
|
|
Pomodoro,
|
|
#[value(name = "event", alias = "e")]
|
|
Event,
|
|
#[value(name = "localtime", alias = "l")]
|
|
LocalTime,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum ClockTypeId {
|
|
Countdown,
|
|
Timer,
|
|
Event,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, ValueEnum, Default, Serialize, Deserialize)]
|
|
pub enum Style {
|
|
#[default]
|
|
#[value(name = "full", alias = "f")]
|
|
Full,
|
|
#[value(name = "light", alias = "l")]
|
|
Light,
|
|
#[value(name = "medium", alias = "m")]
|
|
Medium,
|
|
#[value(name = "dark", alias = "d")]
|
|
Dark,
|
|
#[value(name = "thick", alias = "t")]
|
|
Thick,
|
|
#[value(name = "cross", alias = "c")]
|
|
Cross,
|
|
// https://en.wikipedia.org/wiki/Braille_Patterns
|
|
// Note: Might not be supported in all terminals
|
|
// see https://docs.rs/ratatui/latest/src/ratatui/symbols.rs.html#150
|
|
#[value(name = "braille", alias = "b")]
|
|
Braille,
|
|
}
|
|
|
|
impl Style {
|
|
pub fn next(&self) -> Self {
|
|
match self {
|
|
Style::Full => Style::Dark,
|
|
Style::Dark => Style::Medium,
|
|
Style::Medium => Style::Light,
|
|
Style::Light => Style::Braille,
|
|
Style::Braille => Style::Thick,
|
|
Style::Thick => Style::Cross,
|
|
Style::Cross => Style::Full,
|
|
}
|
|
}
|
|
|
|
pub fn get_digit_symbol(&self) -> &str {
|
|
match &self {
|
|
Style::Full => shade::FULL,
|
|
Style::Light => shade::LIGHT,
|
|
Style::Medium => shade::MEDIUM,
|
|
Style::Dark => shade::DARK,
|
|
Style::Cross => "╬",
|
|
Style::Thick => "┃",
|
|
Style::Braille => "⣿",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, EnumString, Serialize, Deserialize)]
|
|
pub enum AppTimeFormat {
|
|
/// `hh:mm:ss`
|
|
#[default]
|
|
HhMmSs,
|
|
/// `hh:mm`
|
|
HhMm,
|
|
/// `hh:mm AM` (or PM)
|
|
Hh12Mm,
|
|
}
|
|
|
|
impl AppTimeFormat {
|
|
pub const fn first() -> Self {
|
|
Self::HhMmSs
|
|
}
|
|
|
|
pub const fn last() -> Self {
|
|
Self::Hh12Mm
|
|
}
|
|
|
|
pub fn next(&self) -> Self {
|
|
match self {
|
|
AppTimeFormat::HhMmSs => AppTimeFormat::HhMm,
|
|
AppTimeFormat::HhMm => AppTimeFormat::Hh12Mm,
|
|
AppTimeFormat::Hh12Mm => AppTimeFormat::HhMmSs,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum AppTime {
|
|
Local(OffsetDateTime),
|
|
Utc(OffsetDateTime),
|
|
}
|
|
|
|
impl From<AppTime> for OffsetDateTime {
|
|
fn from(app_time: AppTime) -> Self {
|
|
match app_time {
|
|
AppTime::Local(t) => t,
|
|
AppTime::Utc(t) => t,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AppTime {
|
|
#[allow(clippy::new_without_default)]
|
|
pub fn new() -> Self {
|
|
match OffsetDateTime::now_local() {
|
|
Ok(t) => AppTime::Local(t),
|
|
Err(_) => AppTime::Utc(OffsetDateTime::now_utc()),
|
|
}
|
|
}
|
|
|
|
pub fn format(&self, app_format: &AppTimeFormat) -> String {
|
|
let parse_str = match app_format {
|
|
AppTimeFormat::HhMmSs => "[hour]:[minute]:[second]",
|
|
AppTimeFormat::HhMm => "[hour]:[minute]",
|
|
AppTimeFormat::Hh12Mm => "[hour repr:12 padding:none]:[minute] [period]",
|
|
};
|
|
|
|
format_description::parse(parse_str)
|
|
.map_err(|_| "parse error")
|
|
.and_then(|fd| {
|
|
OffsetDateTime::from(*self)
|
|
.format(&fd)
|
|
.map_err(|_| "format error")
|
|
})
|
|
.unwrap_or_else(|e| e.to_string())
|
|
}
|
|
|
|
pub fn format_date(&self) -> String {
|
|
format_description::parse("[year]-[month]-[day]")
|
|
.map_err(|_| "parse error")
|
|
.and_then(|fd| {
|
|
OffsetDateTime::from(*self)
|
|
.format(&fd)
|
|
.map_err(|_| "format error")
|
|
})
|
|
.unwrap_or_else(|e| e.to_string())
|
|
}
|
|
|
|
pub fn get_period(&self) -> String {
|
|
format_description::parse("[period]")
|
|
.map_err(|_| "parse error")
|
|
.and_then(|fd| {
|
|
OffsetDateTime::from(*self)
|
|
.format(&fd)
|
|
.map_err(|_| "format error")
|
|
})
|
|
.unwrap_or_else(|e| e.to_string())
|
|
}
|
|
|
|
/// Converts `AppTime` into a `Duration` representing elapsed time since midnight (today).
|
|
pub fn as_duration_of_today(&self) -> std::time::Duration {
|
|
let dt = OffsetDateTime::from(*self);
|
|
let time = dt.time();
|
|
|
|
let total_nanos = u64::from(time.hour()) * 3_600_000_000_000
|
|
+ u64::from(time.minute()) * 60_000_000_000
|
|
+ u64::from(time.second()) * 1_000_000_000
|
|
+ u64::from(time.nanosecond());
|
|
|
|
std::time::Duration::from_nanos(total_nanos)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AppEditMode {
|
|
None,
|
|
Clock,
|
|
Time,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Default, Serialize, Deserialize)]
|
|
pub enum Toggle {
|
|
#[value(name = "on")]
|
|
On,
|
|
#[default]
|
|
#[value(name = "off")]
|
|
Off,
|
|
}
|
|
|
|
impl From<bool> for Toggle {
|
|
fn from(value: bool) -> Self {
|
|
match value {
|
|
true => Toggle::On,
|
|
false => Toggle::Off,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use time::{Date, Month, PrimitiveDateTime, Time};
|
|
|
|
#[test]
|
|
fn test_format_app_time() {
|
|
let dt = PrimitiveDateTime::new(
|
|
Date::from_calendar_date(2025, Month::January, 6).unwrap(),
|
|
Time::from_hms(18, 6, 10).unwrap(),
|
|
)
|
|
.assume_utc();
|
|
// hh:mm:ss
|
|
assert_eq!(
|
|
AppTime::Utc(dt).format(&AppTimeFormat::HhMmSs),
|
|
"18:06:10",
|
|
"utc"
|
|
);
|
|
assert_eq!(
|
|
AppTime::Local(dt).format(&AppTimeFormat::HhMmSs),
|
|
"18:06:10",
|
|
"local"
|
|
);
|
|
// hh:mm
|
|
assert_eq!(
|
|
AppTime::Utc(dt).format(&AppTimeFormat::HhMm),
|
|
"18:06",
|
|
"utc"
|
|
);
|
|
assert_eq!(
|
|
AppTime::Local(dt).format(&AppTimeFormat::HhMm),
|
|
"18:06",
|
|
"local"
|
|
);
|
|
// hh:mm period
|
|
assert_eq!(
|
|
AppTime::Utc(dt).format(&AppTimeFormat::Hh12Mm),
|
|
"6:06 PM",
|
|
"utc"
|
|
);
|
|
assert_eq!(
|
|
AppTime::Local(dt).format(&AppTimeFormat::Hh12Mm),
|
|
"6:06 PM",
|
|
"local"
|
|
);
|
|
}
|
|
}
|