Introduce CalendarDuration (#120)
* trait ClockDuration, CalendarDuration, tests * make clock rendering more generic * remove `should_blink` from `RenderClockState` * pass less down: `mode` -> `editable_time` * simplify `event` duration states * remove deprecated `DirectedDuration` * fix comments
This commit is contained in:
parent
99032834be
commit
e2cd536079
@ -415,7 +415,8 @@ impl App {
|
||||
Content::Countdown => self.countdown.is_running(),
|
||||
Content::Timer => self.timer.get_clock().is_running(),
|
||||
Content::Pomodoro => self.pomodoro.get_clock().is_running(),
|
||||
Content::Event => self.event.get_clock().is_running(),
|
||||
// Event clock runs forever
|
||||
Content::Event => true,
|
||||
// `LocalTime` does not use a `Clock`
|
||||
Content::LocalTime => false,
|
||||
}
|
||||
@ -426,7 +427,7 @@ impl App {
|
||||
Content::Countdown => Some(self.countdown.get_clock().get_percentage_done()),
|
||||
Content::Timer => None,
|
||||
Content::Pomodoro => Some(self.pomodoro.get_clock().get_percentage_done()),
|
||||
Content::Event => Some(self.event.get_percentage_done()),
|
||||
Content::Event => None,
|
||||
Content::LocalTime => None,
|
||||
}
|
||||
}
|
||||
|
||||
530
src/duration.rs
530
src/duration.rs
@ -7,8 +7,6 @@ use std::fmt;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::common::AppTime;
|
||||
|
||||
// unstable
|
||||
// https://doc.rust-lang.org/src/core/time.rs.html#32
|
||||
pub const SECS_PER_MINUTE: u64 = 60;
|
||||
@ -38,35 +36,194 @@ pub const MAX_DURATION: Duration = ONE_YEAR
|
||||
.saturating_mul(1000)
|
||||
.saturating_sub(ONE_DECI_SECOND);
|
||||
|
||||
/// `Duration` with direction in time (past or future)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DirectedDuration {
|
||||
/// Time `until` a future moment (positive `Duration`)
|
||||
Until(Duration),
|
||||
/// Time `since` a past moment (negative duration, but still represented as positive `Duration`)
|
||||
Since(Duration),
|
||||
/// Trait for duration types that can be displayed in clock widgets.
|
||||
///
|
||||
/// This trait abstracts over different duration calculation strategies:
|
||||
/// - `DurationEx`: Uses fixed 365-day years (fast, simple)
|
||||
/// - `CalendarDuration`: Uses actual calendar dates (accounts for leap years)
|
||||
pub trait ClockDuration {
|
||||
/// Total years
|
||||
fn years(&self) -> u64;
|
||||
|
||||
/// Total days
|
||||
fn days(&self) -> u64;
|
||||
|
||||
/// Days within the current year (0-364 or 0-365 for leap years)
|
||||
fn days_mod(&self) -> u64;
|
||||
|
||||
/// Total hours
|
||||
fn hours(&self) -> u64;
|
||||
|
||||
/// Hours within the current day (0-23)
|
||||
fn hours_mod(&self) -> u64;
|
||||
|
||||
/// Hours as 12-hour clock (1-12)
|
||||
fn hours_mod_12(&self) -> u64;
|
||||
|
||||
/// Total minutes
|
||||
fn minutes(&self) -> u64;
|
||||
|
||||
/// Minutes within the current hour (0-59)
|
||||
fn minutes_mod(&self) -> u64;
|
||||
|
||||
/// Total seconds
|
||||
fn seconds(&self) -> u64;
|
||||
|
||||
/// Seconds within the current minute (0-59)
|
||||
fn seconds_mod(&self) -> u64;
|
||||
|
||||
/// Deciseconds (tenths of a second, 0-9)
|
||||
fn decis(&self) -> u64;
|
||||
|
||||
/// Total milliseconds
|
||||
fn millis(&self) -> u128;
|
||||
}
|
||||
|
||||
impl From<DirectedDuration> for Duration {
|
||||
fn from(directed: DirectedDuration) -> Self {
|
||||
match directed {
|
||||
DirectedDuration::Until(d) => d,
|
||||
DirectedDuration::Since(d) => d,
|
||||
/// Calendar-aware duration that accounts for leap years.
|
||||
///
|
||||
/// Unlike `DurationEx` which uses fixed 365-day years, this calculates
|
||||
/// years and days based on actual calendar dates, properly handling leap years.
|
||||
///
|
||||
/// All calculations are performed on-demand from the stored dates.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CalendarDuration {
|
||||
earlier: OffsetDateTime,
|
||||
later: OffsetDateTime,
|
||||
direction: CalendarDurationDirection,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum CalendarDurationDirection {
|
||||
Since,
|
||||
Until,
|
||||
}
|
||||
|
||||
impl CalendarDuration {
|
||||
/// Create a new CalendarDuration by given two `OffsetDateTime`.
|
||||
///
|
||||
/// The order of arguments matters:
|
||||
/// First: `start_time` - `OffsetDateTime` to start from
|
||||
/// Second: `end_time` - `OffsetDateTime` for expected end
|
||||
pub fn from_start_end_times(start_time: OffsetDateTime, end_time: OffsetDateTime) -> Self {
|
||||
// To avoid negative values by calculating differences of `start` and `end` times,
|
||||
// we might switch those values internally by storing it as `earlier` and `later` values
|
||||
// It simplifies all calculations in `ClockDuration` trait later.
|
||||
// And `direction` will still help to still get original `start` and `end` times later.
|
||||
if start_time <= end_time {
|
||||
Self {
|
||||
earlier: start_time,
|
||||
later: end_time,
|
||||
direction: CalendarDurationDirection::Since,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
earlier: end_time,
|
||||
later: start_time,
|
||||
direction: CalendarDurationDirection::Until,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> &CalendarDurationDirection {
|
||||
&self.direction
|
||||
}
|
||||
|
||||
pub fn start_time(&self) -> &OffsetDateTime {
|
||||
match self.direction {
|
||||
CalendarDurationDirection::Since => &self.earlier,
|
||||
CalendarDurationDirection::Until => &self.later,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_time(&self) -> &OffsetDateTime {
|
||||
match self.direction {
|
||||
CalendarDurationDirection::Since => &self.later,
|
||||
CalendarDurationDirection::Until => &self.earlier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectedDuration {
|
||||
pub fn from_offset_date_times(value_a: OffsetDateTime, value_b: OffsetDateTime) -> Self {
|
||||
let diff = value_a - value_b;
|
||||
impl From<CalendarDuration> for Duration {
|
||||
fn from(cal_duration: CalendarDuration) -> Self {
|
||||
let diff = cal_duration.later - cal_duration.earlier;
|
||||
Duration::from_millis(diff.whole_milliseconds().max(0) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
if diff.is_negative() {
|
||||
Self::Since(Duration::from_millis(
|
||||
diff.whole_milliseconds().unsigned_abs() as u64,
|
||||
))
|
||||
} else {
|
||||
Self::Until(Duration::from_millis(diff.whole_milliseconds() as u64))
|
||||
impl ClockDuration for CalendarDuration {
|
||||
fn years(&self) -> u64 {
|
||||
let mut years = (self.later.year() - self.earlier.year()) as i64;
|
||||
|
||||
// Check if we've completed a full year by comparing month/day/time
|
||||
let intermediate = self
|
||||
.earlier
|
||||
.replace_year(self.later.year())
|
||||
.unwrap_or(self.earlier);
|
||||
|
||||
if intermediate > self.later {
|
||||
years -= 1;
|
||||
}
|
||||
|
||||
years.max(0) as u64
|
||||
}
|
||||
|
||||
fn days_mod(&self) -> u64 {
|
||||
let year_count = self.years();
|
||||
|
||||
// Calculate intermediate date after adding complete years
|
||||
let target_year = self.earlier.year() + year_count as i32;
|
||||
let intermediate = self
|
||||
.earlier
|
||||
.replace_year(target_year)
|
||||
.unwrap_or(self.earlier);
|
||||
|
||||
let remaining = self.later - intermediate;
|
||||
remaining.whole_days().max(0) as u64
|
||||
}
|
||||
|
||||
fn days(&self) -> u64 {
|
||||
(self.later - self.earlier).whole_days().max(0) as u64
|
||||
}
|
||||
|
||||
fn hours_mod(&self) -> u64 {
|
||||
let total_hours = (self.later - self.earlier).whole_hours();
|
||||
(total_hours % 24).max(0) as u64
|
||||
}
|
||||
|
||||
fn hours(&self) -> u64 {
|
||||
(self.later - self.earlier).whole_hours().max(0) as u64
|
||||
}
|
||||
|
||||
fn hours_mod_12(&self) -> u64 {
|
||||
let hours = self.hours_mod();
|
||||
(hours + 11) % 12 + 1
|
||||
}
|
||||
|
||||
fn minutes_mod(&self) -> u64 {
|
||||
let total_minutes = (self.later - self.earlier).whole_minutes();
|
||||
(total_minutes % 60).max(0) as u64
|
||||
}
|
||||
|
||||
fn minutes(&self) -> u64 {
|
||||
(self.later - self.earlier).whole_minutes().max(0) as u64
|
||||
}
|
||||
|
||||
fn seconds_mod(&self) -> u64 {
|
||||
let total_seconds = (self.later - self.earlier).whole_seconds();
|
||||
(total_seconds % 60).max(0) as u64
|
||||
}
|
||||
|
||||
fn seconds(&self) -> u64 {
|
||||
(self.later - self.earlier).whole_seconds().max(0) as u64
|
||||
}
|
||||
|
||||
fn decis(&self) -> u64 {
|
||||
let total_millis = (self.later - self.earlier).whole_milliseconds();
|
||||
((total_millis % 1000) / 100).max(0) as u64
|
||||
}
|
||||
|
||||
fn millis(&self) -> u128 {
|
||||
(self.later - self.earlier).whole_milliseconds().max(0) as u128
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,62 +250,60 @@ impl From<DurationEx> for Duration {
|
||||
}
|
||||
}
|
||||
|
||||
impl DurationEx {
|
||||
pub fn years(&self) -> u64 {
|
||||
impl ClockDuration for DurationEx {
|
||||
fn years(&self) -> u64 {
|
||||
self.days() / DAYS_PER_YEAR
|
||||
}
|
||||
|
||||
pub fn days(&self) -> u64 {
|
||||
fn days(&self) -> u64 {
|
||||
self.hours() / HOURS_PER_DAY
|
||||
}
|
||||
|
||||
/// Days in a year
|
||||
pub fn days_mod(&self) -> u64 {
|
||||
fn days_mod(&self) -> u64 {
|
||||
self.days() % DAYS_PER_YEAR
|
||||
}
|
||||
|
||||
pub fn hours(&self) -> u64 {
|
||||
fn hours(&self) -> u64 {
|
||||
self.seconds() / (SECS_PER_MINUTE * MINS_PER_HOUR)
|
||||
}
|
||||
|
||||
/// Hours as 24-hour clock
|
||||
pub fn hours_mod(&self) -> u64 {
|
||||
fn hours_mod(&self) -> u64 {
|
||||
self.hours() % HOURS_PER_DAY
|
||||
}
|
||||
|
||||
/// Hours as 12-hour clock
|
||||
pub fn hours_mod_12(&self) -> u64 {
|
||||
fn hours_mod_12(&self) -> u64 {
|
||||
// 0 => 12,
|
||||
// 1..=12 => hours,
|
||||
// 13..=23 => hours - 12,
|
||||
(self.hours_mod() + 11) % 12 + 1
|
||||
}
|
||||
|
||||
pub fn minutes(&self) -> u64 {
|
||||
fn minutes(&self) -> u64 {
|
||||
self.seconds() / MINS_PER_HOUR
|
||||
}
|
||||
|
||||
pub fn minutes_mod(&self) -> u64 {
|
||||
fn minutes_mod(&self) -> u64 {
|
||||
self.minutes() % SECS_PER_MINUTE
|
||||
}
|
||||
|
||||
pub fn seconds(&self) -> u64 {
|
||||
fn seconds(&self) -> u64 {
|
||||
self.inner.as_secs()
|
||||
}
|
||||
|
||||
pub fn seconds_mod(&self) -> u64 {
|
||||
fn seconds_mod(&self) -> u64 {
|
||||
self.seconds() % SECS_PER_MINUTE
|
||||
}
|
||||
|
||||
// deciseconds
|
||||
pub fn decis(&self) -> u64 {
|
||||
fn decis(&self) -> u64 {
|
||||
(self.inner.subsec_millis() / 100) as u64
|
||||
}
|
||||
// milliseconds
|
||||
pub fn millis(&self) -> u128 {
|
||||
|
||||
fn millis(&self) -> u128 {
|
||||
self.inner.as_millis()
|
||||
}
|
||||
}
|
||||
|
||||
impl DurationEx {
|
||||
pub fn saturating_add(&self, ex: DurationEx) -> Self {
|
||||
let inner = self.inner.saturating_add(ex.inner);
|
||||
Self { inner }
|
||||
@ -166,6 +321,7 @@ impl DurationEx {
|
||||
|
||||
impl fmt::Display for DurationEx {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use ClockDuration as _; // Import trait methods
|
||||
if self.years() >= 1 {
|
||||
write!(
|
||||
f,
|
||||
@ -233,85 +389,6 @@ fn parse_hours(h: &str) -> Result<u8, Report> {
|
||||
Ok(hours)
|
||||
}
|
||||
|
||||
/// Parses `DirectedDuration` from following formats:
|
||||
/// - `yyyy-mm-dd hh:mm:ss`
|
||||
/// - `yyyy-mm-dd hh:mm`
|
||||
/// - `hh:mm:ss`
|
||||
/// - `hh:mm`
|
||||
/// - `mm`
|
||||
///
|
||||
/// Returns `DirectedDuration::Until` for future times, `DirectedDuration::Since` for past times
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_duration_by_time(arg: &str) -> Result<DirectedDuration, Report> {
|
||||
use time::{OffsetDateTime, PrimitiveDateTime, macros::format_description};
|
||||
|
||||
let now: OffsetDateTime = AppTime::new().into();
|
||||
|
||||
let target_time = if arg.contains('-') {
|
||||
// First: `YYYY-MM-DD HH:MM:SS`
|
||||
// Then: `YYYY-MM-DD HH:MM`
|
||||
let format_with_seconds =
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
let format_without_seconds = format_description!("[year]-[month]-[day] [hour]:[minute]");
|
||||
|
||||
let pdt = PrimitiveDateTime::parse(arg, format_with_seconds)
|
||||
.or_else(|_| PrimitiveDateTime::parse(arg, format_without_seconds))
|
||||
.map_err(|e| {
|
||||
eyre!("Invalid datetime '{}'. Use format 'yyyy-mm-dd hh:mm:ss' or 'yyyy-mm-dd hh:mm'. Error: {}", arg, e)
|
||||
})?;
|
||||
pdt.assume_offset(now.offset())
|
||||
} else {
|
||||
// Parse time parts: interpret as HH:MM:SS, HH:MM, or SS
|
||||
let parts: Vec<&str> = arg.split(':').collect();
|
||||
|
||||
let (hour, minute, second) = match parts.as_slice() {
|
||||
[mm] => {
|
||||
// Single part: treat as minutes in current hour
|
||||
let m = parse_minutes(mm)?;
|
||||
(now.hour(), m, 0)
|
||||
}
|
||||
[hh, mm] => {
|
||||
// Two parts: treat as HH:MM (time of day)
|
||||
let h = parse_hours(hh)?;
|
||||
let m = parse_minutes(mm)?;
|
||||
(h, m, 0)
|
||||
}
|
||||
[hh, mm, ss] => {
|
||||
// Three parts: HH:MM:SS
|
||||
let h = parse_hours(hh)?;
|
||||
let m = parse_minutes(mm)?;
|
||||
let s = parse_seconds(ss)?;
|
||||
(h, m, s)
|
||||
}
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Invalid time format. Use 'hh:mm:ss', 'hh:mm', or 'mm'"
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
now.replace_time(
|
||||
time::Time::from_hms(hour, minute, second).map_err(|_| eyre!("Invalid time"))?,
|
||||
)
|
||||
};
|
||||
|
||||
let mut duration_secs = (target_time - now).whole_seconds();
|
||||
|
||||
// `Since` for past times
|
||||
if duration_secs < 0 {
|
||||
duration_secs *= -1;
|
||||
Ok(DirectedDuration::Since(Duration::from_secs(
|
||||
duration_secs as u64,
|
||||
)))
|
||||
} else
|
||||
// `Until` for future times,
|
||||
{
|
||||
Ok(DirectedDuration::Until(Duration::from_secs(
|
||||
duration_secs as u64,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses `Duration` from `hh:mm:ss`, `mm:ss` or `ss`
|
||||
pub fn parse_duration(arg: &str) -> Result<Duration, Report> {
|
||||
let parts: Vec<&str> = arg.split(':').collect();
|
||||
@ -396,6 +473,7 @@ pub fn parse_long_duration(arg: &str) -> Result<Duration, Report> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::ClockDuration;
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -497,31 +575,6 @@ mod tests {
|
||||
assert_eq!(result, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_offset_date_times() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// Future time (Until)
|
||||
let now = datetime!(2024-01-01 12:00:00).assume_utc();
|
||||
let future = datetime!(2024-01-01 13:00:00).assume_utc();
|
||||
assert!(matches!(
|
||||
DirectedDuration::from_offset_date_times(future, now),
|
||||
DirectedDuration::Until(_)
|
||||
));
|
||||
|
||||
// Past time (Since)
|
||||
assert!(matches!(
|
||||
DirectedDuration::from_offset_date_times(now, future),
|
||||
DirectedDuration::Since(_)
|
||||
));
|
||||
|
||||
// Same time (Until with 0 duration)
|
||||
assert!(matches!(
|
||||
DirectedDuration::from_offset_date_times(now, now),
|
||||
DirectedDuration::Until(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_duration() {
|
||||
// ss
|
||||
@ -543,42 +596,6 @@ mod tests {
|
||||
assert!(parse_duration("01:02:03:04").is_err()); // too many parts
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_duration_by_time() {
|
||||
// YYYY-MM-DD HH:MM:SS - future
|
||||
assert!(matches!(
|
||||
parse_duration_by_time("2050-06-15 14:30:45"),
|
||||
Ok(DirectedDuration::Until(_))
|
||||
));
|
||||
|
||||
// YYYY-MM-DD HH:MM - future
|
||||
assert!(matches!(
|
||||
parse_duration_by_time("2050-06-15 14:30"),
|
||||
Ok(DirectedDuration::Until(_))
|
||||
));
|
||||
|
||||
// HH:MM:SS - past
|
||||
assert!(matches!(
|
||||
parse_duration_by_time("2000-01-01 23:59:59"),
|
||||
Ok(DirectedDuration::Since(_))
|
||||
));
|
||||
|
||||
// HH:MM - Until or Since depending on current time
|
||||
assert!(parse_duration_by_time("18:00").is_ok());
|
||||
|
||||
// MM - Until or Since depending on current time
|
||||
assert!(parse_duration_by_time("45").is_ok());
|
||||
|
||||
// errors
|
||||
assert!(parse_duration_by_time("60").is_err()); // invalid minutes
|
||||
assert!(parse_duration_by_time("24:00").is_err()); // invalid hours
|
||||
assert!(parse_duration_by_time("24:00:00").is_err()); // invalid hours
|
||||
assert!(parse_duration_by_time("2030-13-01 12:00:00").is_err()); // invalid month
|
||||
assert!(parse_duration_by_time("2030-06-32 12:00:00").is_err()); // invalid day
|
||||
assert!(parse_duration_by_time("abc").is_err()); // invalid input
|
||||
assert!(parse_duration_by_time("01:02:03:04").is_err()); // too many parts
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_long_duration() {
|
||||
// `Yy`
|
||||
@ -677,4 +694,155 @@ mod tests {
|
||||
assert!(parse_long_duration("1y 2d 3d 4:00").is_err()); // too many parts (4 parts)
|
||||
assert!(parse_long_duration("1y 2d 3h 4m 5s").is_err()); // too many parts (5 parts)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_leap_year() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// 2024 is a leap year (366 days)
|
||||
let start = datetime!(2024-01-01 00:00:00 UTC);
|
||||
let end = datetime!(2025-01-01 00:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 1, "Should be exactly 1 year");
|
||||
assert_eq!(cal_dur.days_mod(), 0, "Should be 0 remaining days");
|
||||
assert_eq!(cal_dur.days(), 366, "2024 has 366 days (leap year)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_non_leap_year() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// 2023 is not a leap year (365 days)
|
||||
let start = datetime!(2023-01-01 00:00:00 UTC);
|
||||
let end = datetime!(2024-01-01 00:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 1, "Should be exactly 1 year");
|
||||
assert_eq!(cal_dur.days_mod(), 0, "Should be 0 remaining days");
|
||||
assert_eq!(cal_dur.days(), 365, "2023 has 365 days (non-leap year)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_partial_year_with_leap_day() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// Span including Feb 29, 2024
|
||||
let start = datetime!(2024-02-01 00:00:00 UTC);
|
||||
let end = datetime!(2024-03-15 00:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 0, "Should be 0 years");
|
||||
// Feb 2024 has 29 days, so: 29 days (rest of Feb) + 15 days (March) = 44 days
|
||||
assert_eq!(
|
||||
cal_dur.days(),
|
||||
43,
|
||||
"Should be 43 days (29 in Feb + 14 partial March)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_partial_year_without_leap_day() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// Same dates but in 2023 (non-leap year)
|
||||
let start = datetime!(2023-02-01 00:00:00 UTC);
|
||||
let end = datetime!(2023-03-15 00:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 0, "Should be 0 years");
|
||||
// Feb 2023 has 28 days, so: 28 days (rest of Feb) + 15 days (March) = 43 days
|
||||
assert_eq!(
|
||||
cal_dur.days(),
|
||||
42,
|
||||
"Should be 42 days (28 in Feb + 14 partial March)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_multiple_years_spanning_leap_years() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// From 2023 (non-leap) through 2024 (leap) to 2025
|
||||
let start = datetime!(2023-03-01 10:00:00 UTC);
|
||||
let end = datetime!(2025-03-01 10:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 2, "Should be exactly 2 years");
|
||||
assert_eq!(cal_dur.days_mod(), 0, "Should be 0 remaining days");
|
||||
// Total days: 365 (2023 partial + 2024 partial) + 366 (full 2024 year conceptually included)
|
||||
// Actually: From 2023-03-01 to 2025-03-01 = 365 + 366 = 731 days
|
||||
assert_eq!(cal_dur.days(), 731, "Should be 731 total days");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_year_boundary() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// Test incomplete year - just before year boundary
|
||||
let start = datetime!(2024-01-01 00:00:00 UTC);
|
||||
let end = datetime!(2024-12-31 23:59:59 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 0, "Should be 0 years (not complete)");
|
||||
assert_eq!(cal_dur.days(), 365, "Should be 365 days");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_hours_minutes_seconds() {
|
||||
use time::macros::datetime;
|
||||
|
||||
let start = datetime!(2024-01-01 10:30:45 UTC);
|
||||
let end = datetime!(2024-01-02 14:25:50 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(cal_dur.years(), 0);
|
||||
assert_eq!(cal_dur.days(), 1);
|
||||
assert_eq!(cal_dur.hours_mod(), 3, "Should be 3 hours past midnight");
|
||||
assert_eq!(cal_dur.minutes_mod(), 55, "Should be 55 minutes");
|
||||
assert_eq!(cal_dur.seconds_mod(), 5, "Should be 5 seconds");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_reversed_dates() {
|
||||
use time::macros::datetime;
|
||||
|
||||
// CalendarDuration::between should handle reversed order
|
||||
let later = datetime!(2025-01-01 00:00:00 UTC);
|
||||
let earlier = datetime!(2024-01-01 00:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(later, earlier);
|
||||
|
||||
assert_eq!(cal_dur.years(), 1, "Should still calculate 1 year");
|
||||
assert_eq!(cal_dur.days(), 366, "Should still be 366 days");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_same_date() {
|
||||
use time::macros::datetime;
|
||||
|
||||
let date = datetime!(2024-06-15 12:00:00 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(date, date);
|
||||
|
||||
assert_eq!(cal_dur.years(), 0);
|
||||
assert_eq!(cal_dur.days(), 0);
|
||||
assert_eq!(cal_dur.hours(), 0);
|
||||
assert_eq!(cal_dur.minutes(), 0);
|
||||
assert_eq!(cal_dur.seconds(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calendar_duration_deciseconds() {
|
||||
use time::macros::datetime;
|
||||
|
||||
let start = datetime!(2024-01-01 00:00:00.000 UTC);
|
||||
let end = datetime!(2024-01-01 00:00:00.750 UTC);
|
||||
let cal_dur = CalendarDuration::from_start_end_times(start, end);
|
||||
|
||||
assert_eq!(
|
||||
cal_dur.decis(),
|
||||
7,
|
||||
"Should be 7 deciseconds (750ms = 7.5 decis, truncated to 7)"
|
||||
);
|
||||
assert_eq!(cal_dur.millis(), 750, "Should be 750 milliseconds");
|
||||
}
|
||||
}
|
||||
|
||||
1644
src/widgets/clock.rs
1644
src/widgets/clock.rs
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
common::ClockTypeId,
|
||||
duration::{
|
||||
MAX_DURATION, ONE_DAY, ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND, ONE_YEAR,
|
||||
DurationEx, MAX_DURATION, ONE_DAY, ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND,
|
||||
ONE_YEAR,
|
||||
},
|
||||
widgets::clock::*,
|
||||
};
|
||||
@ -76,102 +77,129 @@ fn test_get_format_hours() {
|
||||
#[test]
|
||||
fn test_format_by_duration_boundaries() {
|
||||
// S
|
||||
assert_eq!(format_by_duration(&(ONE_SECOND * 9).into()), Format::S);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&(ONE_SECOND * 9).into()),
|
||||
Format::S
|
||||
);
|
||||
// Ss
|
||||
assert_eq!(format_by_duration(&(10 * ONE_SECOND).into()), Format::Ss);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_SECOND).into()),
|
||||
Format::Ss
|
||||
);
|
||||
// Ss
|
||||
assert_eq!(format_by_duration(&(59 * ONE_SECOND).into()), Format::Ss);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&(59 * ONE_SECOND).into()),
|
||||
Format::Ss
|
||||
);
|
||||
// MSs
|
||||
assert_eq!(format_by_duration(&ONE_MINUTE.into()), Format::MSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&ONE_MINUTE.into()),
|
||||
Format::MSs
|
||||
);
|
||||
// HhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_DAY.saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(&(ONE_DAY.saturating_sub(ONE_SECOND)).into()),
|
||||
Format::HhMmSs
|
||||
);
|
||||
// DHhMmSs
|
||||
assert_eq!(format_by_duration(&ONE_DAY.into()), Format::DHhMmSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&ONE_DAY.into()),
|
||||
Format::DHhMmSs
|
||||
);
|
||||
// DHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&((10 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(&((10 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
Format::DHhMmSs
|
||||
);
|
||||
// DdHhMmSs
|
||||
assert_eq!(format_by_duration(&(10 * ONE_DAY).into()), Format::DdHhMmSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_DAY).into()),
|
||||
Format::DdHhMmSs
|
||||
);
|
||||
// DdHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&((100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(&((100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
Format::DdHhMmSs
|
||||
);
|
||||
// DddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_DAY).into()),
|
||||
Format::DddHhMmSs
|
||||
);
|
||||
// DddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_YEAR.saturating_sub(ONE_SECOND).into())),
|
||||
format_by_duration::<DurationEx>(&(ONE_YEAR.saturating_sub(ONE_SECOND).into())),
|
||||
Format::DddHhMmSs
|
||||
);
|
||||
// YDHhMmSs
|
||||
assert_eq!(format_by_duration(&ONE_YEAR.into()), Format::YDHhMmSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&ONE_YEAR.into()),
|
||||
Format::YDHhMmSs
|
||||
);
|
||||
// YDdHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(
|
||||
&(ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()
|
||||
),
|
||||
Format::YDdHhMmSs
|
||||
);
|
||||
// YDddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
Format::YDddHhMmSs
|
||||
);
|
||||
// YDddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&((10 * ONE_YEAR).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(&((10 * ONE_YEAR).saturating_sub(ONE_SECOND)).into()),
|
||||
Format::YDddHhMmSs
|
||||
);
|
||||
// YyDHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR).into()),
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_YEAR).into()),
|
||||
Format::YyDHhMmSs
|
||||
);
|
||||
// YyDdHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
Format::YyDdHhMmSs
|
||||
);
|
||||
// YyDdHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(
|
||||
&(10 * ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()
|
||||
),
|
||||
Format::YyDdHhMmSs
|
||||
);
|
||||
// YyDddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
Format::YyDddHhMmSs
|
||||
);
|
||||
// YyDddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&((100 * ONE_YEAR).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(&((100 * ONE_YEAR).saturating_sub(ONE_SECOND)).into()),
|
||||
Format::YyDddHhMmSs
|
||||
);
|
||||
// YyyDHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_YEAR).into()),
|
||||
Format::YyyDHhMmSs
|
||||
);
|
||||
// YyyDdHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
Format::YyyDdHhMmSs
|
||||
);
|
||||
// YyyDdHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()),
|
||||
format_by_duration::<DurationEx>(
|
||||
&(100 * ONE_YEAR + (100 * ONE_DAY).saturating_sub(ONE_SECOND)).into()
|
||||
),
|
||||
Format::YyyDdHhMmSs
|
||||
);
|
||||
// YyyDddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
Format::YyyDddHhMmSs
|
||||
);
|
||||
}
|
||||
@ -179,12 +207,18 @@ fn test_format_by_duration_boundaries() {
|
||||
#[test]
|
||||
fn test_format_by_duration_days() {
|
||||
// DHhMmSs
|
||||
assert_eq!(format_by_duration(&ONE_DAY.into()), Format::DHhMmSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&ONE_DAY.into()),
|
||||
Format::DHhMmSs
|
||||
);
|
||||
// DdHhMmSs
|
||||
assert_eq!(format_by_duration(&(10 * ONE_DAY).into()), Format::DdHhMmSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_DAY).into()),
|
||||
Format::DdHhMmSs
|
||||
);
|
||||
// DddHhMmSs
|
||||
assert_eq!(
|
||||
format_by_duration(&(101 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(101 * ONE_DAY).into()),
|
||||
Format::DddHhMmSs
|
||||
);
|
||||
}
|
||||
@ -192,59 +226,62 @@ fn test_format_by_duration_days() {
|
||||
#[test]
|
||||
fn test_format_by_duration_years() {
|
||||
// YDHhMmSs (1 year, 0 days)
|
||||
assert_eq!(format_by_duration(&ONE_YEAR.into()), Format::YDHhMmSs);
|
||||
assert_eq!(
|
||||
format_by_duration::<DurationEx>(&ONE_YEAR.into()),
|
||||
Format::YDHhMmSs
|
||||
);
|
||||
|
||||
// YDHhMmSs (1 year, 1 day)
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_YEAR + ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(ONE_YEAR + ONE_DAY).into()),
|
||||
Format::YDHhMmSs
|
||||
);
|
||||
|
||||
// YDdHhMmSs (1 year, 10 days)
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
Format::YDdHhMmSs
|
||||
);
|
||||
|
||||
// YDddHhMmSs (1 year, 100 days)
|
||||
assert_eq!(
|
||||
format_by_duration(&(ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
Format::YDddHhMmSs
|
||||
);
|
||||
|
||||
// YyDHhMmSs (10 years)
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR).into()),
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_YEAR).into()),
|
||||
Format::YyDHhMmSs
|
||||
);
|
||||
|
||||
// YyDdHhMmSs (10 years, 10 days)
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
Format::YyDdHhMmSs
|
||||
);
|
||||
|
||||
// YyDddHhMmSs (10 years, 100 days)
|
||||
assert_eq!(
|
||||
format_by_duration(&(10 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(10 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
Format::YyDddHhMmSs
|
||||
);
|
||||
|
||||
// YyyDHhMmSs (100 years)
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_YEAR).into()),
|
||||
Format::YyyDHhMmSs
|
||||
);
|
||||
|
||||
// YyyDdHhMmSs (100 years, 10 days)
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_YEAR + 10 * ONE_DAY).into()),
|
||||
Format::YyyDdHhMmSs
|
||||
);
|
||||
|
||||
// YyyDddHhMmSs (100 years, 100 days)
|
||||
assert_eq!(
|
||||
format_by_duration(&(100 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
format_by_duration::<DurationEx>(&(100 * ONE_YEAR + 100 * ONE_DAY).into()),
|
||||
Format::YyyDddHhMmSs
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,11 +8,10 @@ use time::{OffsetDateTime, macros::format_description};
|
||||
|
||||
use crate::{
|
||||
common::{AppTime, Style},
|
||||
constants::TICK_VALUE_MS,
|
||||
duration::DirectedDuration,
|
||||
duration::{CalendarDuration, CalendarDurationDirection},
|
||||
events::{AppEventTx, TuiEvent, TuiEventHandler},
|
||||
utils::center,
|
||||
widgets::clock::{self, ClockState, ClockStateArgs, ClockWidget},
|
||||
widgets::{clock, clock_elements::DIGIT_HEIGHT},
|
||||
};
|
||||
use std::{cmp::max, time::Duration};
|
||||
|
||||
@ -20,8 +19,8 @@ use std::{cmp::max, time::Duration};
|
||||
pub struct EventState {
|
||||
title: String,
|
||||
event_time: OffsetDateTime,
|
||||
clock: ClockState<clock::Countdown>,
|
||||
directed_duration: DirectedDuration,
|
||||
app_time: OffsetDateTime,
|
||||
with_decis: bool,
|
||||
}
|
||||
|
||||
pub struct EventStateArgs {
|
||||
@ -42,52 +41,27 @@ impl EventState {
|
||||
app_tx,
|
||||
} = args;
|
||||
|
||||
// TODO: Handle app Events
|
||||
let _ = app_tx;
|
||||
let app_datetime = OffsetDateTime::from(app_time);
|
||||
// assume event has as same `offset` as `app_time`
|
||||
let event_offset = event_time.assume_offset(app_datetime.offset());
|
||||
let directed_duration =
|
||||
DirectedDuration::from_offset_date_times(event_offset, app_datetime);
|
||||
let current_value = directed_duration.into();
|
||||
|
||||
let clock = ClockState::<clock::Countdown>::new(ClockStateArgs {
|
||||
initial_value: current_value,
|
||||
current_value,
|
||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||
with_decis,
|
||||
app_tx: Some(app_tx.clone()),
|
||||
});
|
||||
|
||||
Self {
|
||||
title: event_title,
|
||||
event_time: event_offset,
|
||||
directed_duration,
|
||||
clock,
|
||||
app_time: app_datetime,
|
||||
with_decis,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_clock(&self) -> &ClockState<clock::Countdown> {
|
||||
&self.clock
|
||||
}
|
||||
|
||||
pub fn set_app_time(&mut self, app_time: AppTime) {
|
||||
// update `directed_duration`
|
||||
let app_datetime = OffsetDateTime::from(app_time);
|
||||
self.directed_duration =
|
||||
DirectedDuration::from_offset_date_times(self.event_time, app_datetime);
|
||||
// update clock
|
||||
let duration: Duration = self.directed_duration.into();
|
||||
self.clock.set_current_value(duration.into());
|
||||
self.app_time = app_datetime;
|
||||
}
|
||||
|
||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||
self.clock.with_decis = with_decis;
|
||||
}
|
||||
|
||||
pub fn get_percentage_done(&self) -> u16 {
|
||||
match self.directed_duration {
|
||||
DirectedDuration::Since(_) => 100,
|
||||
DirectedDuration::Until(_) => self.clock.get_percentage_done(),
|
||||
}
|
||||
self.with_decis = with_decis;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,8 +80,13 @@ pub struct EventWidget {
|
||||
impl StatefulWidget for EventWidget {
|
||||
type State = EventState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let clock = &mut state.clock;
|
||||
let clock_widget = ClockWidget::new(self.style, self.blink);
|
||||
let with_decis = state.with_decis;
|
||||
let clock_duration =
|
||||
CalendarDuration::from_start_end_times(state.event_time, state.app_time);
|
||||
let clock_format = clock::format_by_duration(&clock_duration);
|
||||
let clock_widths = clock::clock_horizontal_lengths(&clock_format, with_decis);
|
||||
let clock_width = clock_widths.iter().sum();
|
||||
|
||||
let label_event = Line::raw(state.title.to_uppercase());
|
||||
let time_str = state
|
||||
.event_time
|
||||
@ -115,17 +94,19 @@ impl StatefulWidget for EventWidget {
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second]"
|
||||
))
|
||||
.unwrap_or_else(|e| format!("time format error: {}", e));
|
||||
let time_prefix = match state.directed_duration {
|
||||
DirectedDuration::Since(d) => {
|
||||
// Show `done` for a short of time (1 sec.)
|
||||
if d < Duration::from_secs(1) {
|
||||
"Done"
|
||||
} else {
|
||||
"Since"
|
||||
}
|
||||
|
||||
let time_prefix = if clock_duration.direction() == &CalendarDurationDirection::Since {
|
||||
let duration: Duration = clock_duration.clone().into();
|
||||
// Show `done` for a short of time (1 sec)
|
||||
if duration < Duration::from_secs(1) {
|
||||
"Done"
|
||||
} else {
|
||||
"Since"
|
||||
}
|
||||
DirectedDuration::Until(_) => "Until",
|
||||
} else {
|
||||
"Until"
|
||||
};
|
||||
|
||||
let label_time = Line::raw(format!(
|
||||
"{} {}",
|
||||
time_prefix.to_uppercase(),
|
||||
@ -135,21 +116,34 @@ impl StatefulWidget for EventWidget {
|
||||
|
||||
let area = center(
|
||||
area,
|
||||
Constraint::Length(max(
|
||||
clock_widget.get_width(clock.get_format(), clock.with_decis),
|
||||
max_label_width,
|
||||
)),
|
||||
Constraint::Length(clock_widget.get_height() + 3 /* height of label */),
|
||||
Constraint::Length(max(clock_width, max_label_width)),
|
||||
Constraint::Length(DIGIT_HEIGHT + 3 /* height of label */),
|
||||
);
|
||||
let [_, v1, v2, v3] = Layout::vertical(Constraint::from_lengths([
|
||||
1, // empty (offset) to keep everything centered vertically comparing to "clock" widgets with one label only
|
||||
clock_widget.get_height(),
|
||||
DIGIT_HEIGHT,
|
||||
1, // event date
|
||||
1, // event title
|
||||
]))
|
||||
.areas(area);
|
||||
|
||||
clock_widget.render(v1, buf, clock);
|
||||
// TODO: Add logic to handle blink in `DONE` mode, similar to `ClockWidget<T>::should_blink`
|
||||
let symbol = if self.blink {
|
||||
" "
|
||||
} else {
|
||||
self.style.get_digit_symbol()
|
||||
};
|
||||
|
||||
let render_clock_state = clock::RenderClockState {
|
||||
with_decis,
|
||||
duration: clock_duration,
|
||||
editable_time: None,
|
||||
format: clock_format,
|
||||
symbol,
|
||||
widths: clock_widths,
|
||||
};
|
||||
|
||||
clock::render_clock(v1, buf, render_clock_state);
|
||||
label_time.centered().render(v2, buf);
|
||||
label_event.centered().render(v3, buf);
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ use ratatui::{
|
||||
|
||||
use crate::{
|
||||
common::{AppTime, AppTimeFormat, Style as DigitStyle},
|
||||
duration::DurationEx,
|
||||
duration::{ClockDuration, DurationEx},
|
||||
events::{TuiEvent, TuiEventHandler},
|
||||
utils::center,
|
||||
widgets::clock_elements::{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user