feat(event) blink effect at event time (#123)
Similar to `Countdown` and `Pomodoro` DONE effects.
This commit is contained in:
parent
4594bc722e
commit
6b6221803c
@ -213,7 +213,7 @@ impl App {
|
||||
event: EventState::new(EventStateArgs {
|
||||
app_time,
|
||||
event_time: time::PrimitiveDateTime::parse(
|
||||
"2025-10-09 19:54:29",
|
||||
"2025-10-09 21:32:30",
|
||||
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"),
|
||||
)
|
||||
.unwrap(),
|
||||
|
||||
@ -92,7 +92,7 @@ pub struct CalendarDuration {
|
||||
direction: CalendarDurationDirection,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum CalendarDurationDirection {
|
||||
Since,
|
||||
Until,
|
||||
@ -124,8 +124,12 @@ impl CalendarDuration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> &CalendarDurationDirection {
|
||||
&self.direction
|
||||
pub fn direction(&self) -> CalendarDurationDirection {
|
||||
self.direction
|
||||
}
|
||||
|
||||
pub fn is_since(&self) -> bool {
|
||||
self.direction == CalendarDurationDirection::Since
|
||||
}
|
||||
|
||||
pub fn start_time(&self) -> &OffsetDateTime {
|
||||
|
||||
@ -161,7 +161,7 @@ pub fn count_by_mode(times: u32, mode: &Mode) -> Duration {
|
||||
}
|
||||
|
||||
const RANGE_OF_DONE_COUNT: u64 = 4;
|
||||
const MAX_DONE_COUNT: u64 = RANGE_OF_DONE_COUNT * 5;
|
||||
pub const MAX_DONE_COUNT: u64 = RANGE_OF_DONE_COUNT * 5;
|
||||
|
||||
pub struct ClockState<T> {
|
||||
type_id: ClockTypeId,
|
||||
@ -447,18 +447,17 @@ impl<T> ClockState<T> {
|
||||
/// `tick` won't be called again after `Mode::Done` event (e.g. in `widget::Countdown`).
|
||||
/// That's why `update_done_count` is called from "outside".
|
||||
pub fn update_done_count(&mut self) {
|
||||
if let Some(count) = self.done_count {
|
||||
if count > 0 {
|
||||
let value = count - 1;
|
||||
self.done_count = Some(value)
|
||||
} else {
|
||||
// None means we are done and no counting anymore.
|
||||
self.done_count = None
|
||||
}
|
||||
}
|
||||
self.done_count = count_clock_done(self.done_count);
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe way to count a possible `done` value
|
||||
pub fn count_clock_done(value: Option<u64>) -> Option<u64> {
|
||||
// Safe substraction for `Some(value > 1)`
|
||||
// `None` means `done` == no counting anymore.
|
||||
value.and_then(|count| count.checked_sub(1))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Countdown {}
|
||||
|
||||
@ -649,17 +648,17 @@ where
|
||||
pub fn get_height(&self) -> u16 {
|
||||
DIGIT_HEIGHT
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether to blink the clock while rendering.
|
||||
/// Its logic is based on a given `count` value.
|
||||
fn should_blink(&self, count_value: &Option<u64>) -> bool {
|
||||
// Example:
|
||||
// if `RANGE_OF_DONE_COUNT` is 4
|
||||
// then for ranges `0..4`, `8..12` etc. it will return `true`
|
||||
count_value
|
||||
.map(|b| (b % (RANGE_OF_DONE_COUNT * 2)) < RANGE_OF_DONE_COUNT)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
/// Helper to check whether to blink the clock while rendering.
|
||||
/// Its logic is based on a given `count` value.
|
||||
pub fn should_blink(count_value: Option<u64>) -> bool {
|
||||
// Example:
|
||||
// if `RANGE_OF_DONE_COUNT` is 4
|
||||
// then for ranges `0..4`, `8..12` etc. it will return `true`
|
||||
count_value
|
||||
.map(|b| (b % (RANGE_OF_DONE_COUNT * 2)) < RANGE_OF_DONE_COUNT)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
// Helper to get horizontal lengths of a clock
|
||||
@ -1494,9 +1493,9 @@ where
|
||||
let format = state.format;
|
||||
let widths = clock_horizontal_lengths(&format, with_decis);
|
||||
|
||||
// to simulate a blink effect, just use an "empty" symbol (string)
|
||||
// to "empty" all digits and to have an "empty" render area
|
||||
let symbol = if self.blink && self.should_blink(&state.done_count) {
|
||||
// To simulate a blink effect, just use an "empty" symbol (string)
|
||||
// It's "empty" all digits and creates an "empty" render area
|
||||
let symbol = if self.blink && should_blink(state.done_count) {
|
||||
" "
|
||||
} else {
|
||||
self.style.get_digit_symbol()
|
||||
|
||||
@ -8,7 +8,7 @@ use time::{OffsetDateTime, macros::format_description};
|
||||
|
||||
use crate::{
|
||||
common::{AppTime, Style},
|
||||
duration::{CalendarDuration, CalendarDurationDirection},
|
||||
duration::CalendarDuration,
|
||||
events::{AppEventTx, TuiEvent, TuiEventHandler},
|
||||
utils::center,
|
||||
widgets::{clock, clock_elements::DIGIT_HEIGHT},
|
||||
@ -22,6 +22,9 @@ pub struct EventState {
|
||||
app_time: OffsetDateTime,
|
||||
start_time: OffsetDateTime,
|
||||
with_decis: bool,
|
||||
/// counter to simulate `DONE` state
|
||||
/// Default value: `None`
|
||||
done_count: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct EventStateArgs {
|
||||
@ -54,12 +57,17 @@ impl EventState {
|
||||
app_time: app_datetime,
|
||||
start_time: app_datetime,
|
||||
with_decis,
|
||||
done_count: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Sets `app_time`
|
||||
pub fn set_app_time(&mut self, app_time: AppTime) {
|
||||
let app_datetime = OffsetDateTime::from(app_time);
|
||||
self.app_time = app_datetime;
|
||||
|
||||
// Since updating `app_time` is like a `Tick`, we check `done` state here
|
||||
self.check_done();
|
||||
}
|
||||
|
||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||
@ -69,6 +77,25 @@ impl EventState {
|
||||
pub fn get_percentage_done(&self) -> u16 {
|
||||
get_percentage(self.start_time, self.event_time, self.app_time)
|
||||
}
|
||||
|
||||
pub fn get_duration(&mut self) -> CalendarDuration {
|
||||
CalendarDuration::from_start_end_times(self.event_time, self.app_time)
|
||||
}
|
||||
|
||||
fn check_done(&mut self) {
|
||||
let clock_duration = self.get_duration();
|
||||
if clock_duration.is_since() {
|
||||
let duration: Duration = clock_duration.into();
|
||||
// give some offset to make sure we are around `Duration::ZERO`
|
||||
// Without that we might miss it, because the app runs on its own FPS
|
||||
if duration < Duration::from_millis(100) {
|
||||
// reset `done_count`
|
||||
self.done_count = Some(clock::MAX_DONE_COUNT);
|
||||
}
|
||||
// count (possible) `done`
|
||||
self.done_count = clock::count_clock_done(self.done_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TuiEventHandler for EventState {
|
||||
@ -104,8 +131,7 @@ impl StatefulWidget for EventWidget {
|
||||
type State = EventState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let with_decis = state.with_decis;
|
||||
let clock_duration =
|
||||
CalendarDuration::from_start_end_times(state.event_time, state.app_time);
|
||||
let clock_duration = state.get_duration();
|
||||
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();
|
||||
@ -117,8 +143,7 @@ impl StatefulWidget for EventWidget {
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second]"
|
||||
))
|
||||
.unwrap_or_else(|e| format!("time format error: {}", e));
|
||||
|
||||
let time_prefix = if clock_duration.direction() == &CalendarDurationDirection::Since {
|
||||
let time_prefix = if clock_duration.is_since() {
|
||||
let duration: Duration = clock_duration.clone().into();
|
||||
// Show `done` for a short of time (1 sec)
|
||||
if duration < Duration::from_secs(1) {
|
||||
@ -150,8 +175,9 @@ impl StatefulWidget for EventWidget {
|
||||
]))
|
||||
.areas(area);
|
||||
|
||||
// TODO: Add logic to handle blink in `DONE` mode, similar to `ClockWidget<T>::should_blink`
|
||||
let symbol = if self.blink {
|
||||
// To simulate a blink effect, just use an "empty" symbol (string)
|
||||
// It's "empty" all digits and creates an "empty" render area
|
||||
let symbol = if self.blink && clock::should_blink(state.done_count) {
|
||||
" "
|
||||
} else {
|
||||
self.style.get_digit_symbol()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user