feat(notification): Animate (blink) clock entering done mode (#65)
Optional.
This commit is contained in:
@@ -66,6 +66,9 @@ pub enum Format {
|
||||
HhMmSs,
|
||||
}
|
||||
|
||||
const RANGE_OF_DONE_COUNT: u64 = 4;
|
||||
const MAX_DONE_COUNT: u64 = RANGE_OF_DONE_COUNT * 5;
|
||||
|
||||
pub struct ClockState<T> {
|
||||
type_id: ClockTypeId,
|
||||
name: Option<String>,
|
||||
@@ -76,6 +79,11 @@ pub struct ClockState<T> {
|
||||
format: Format,
|
||||
pub with_decis: bool,
|
||||
app_tx: Option<AppEventTx>,
|
||||
/// Tick counter starting whenever `Mode::DONE` has been reached.
|
||||
/// Initial value is set in `done()`.
|
||||
/// Updates happened in `update_done_count`
|
||||
/// Default value: `None`
|
||||
done_count: Option<u64>,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -335,6 +343,7 @@ impl<T> ClockState<T> {
|
||||
if let Some(tx) = &self.app_tx {
|
||||
_ = tx.send(AppEvent::ClockDone(type_id, name));
|
||||
};
|
||||
self.done_count = Some(MAX_DONE_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +366,23 @@ impl<T> ClockState<T> {
|
||||
Format::S
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates inner value of `done_count`.
|
||||
/// It should be called whenever `TuiEvent::Tick` is handled.
|
||||
/// At first glance it might happen in `Clock::tick`, but sometimes
|
||||
/// `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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -387,6 +413,7 @@ impl ClockState<Countdown> {
|
||||
format: Format::S,
|
||||
with_decis,
|
||||
app_tx,
|
||||
done_count: None,
|
||||
phantom: PhantomData,
|
||||
};
|
||||
// update format once
|
||||
@@ -459,6 +486,7 @@ impl ClockState<Timer> {
|
||||
format: Format::S,
|
||||
with_decis,
|
||||
app_tx,
|
||||
done_count: None,
|
||||
phantom: PhantomData,
|
||||
};
|
||||
// update format once
|
||||
@@ -502,6 +530,7 @@ where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
style: Style,
|
||||
blink: bool,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -509,9 +538,10 @@ impl<T> ClockWidget<T>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
pub fn new(style: Style) -> Self {
|
||||
pub fn new(style: Style, blink: bool) -> Self {
|
||||
Self {
|
||||
style,
|
||||
blink,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -604,6 +634,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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StatefulWidget for ClockWidget<T>
|
||||
@@ -615,7 +656,13 @@ where
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let with_decis = state.with_decis;
|
||||
let format = state.format;
|
||||
let symbol = self.style.get_digit_symbol();
|
||||
// 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) {
|
||||
" "
|
||||
} else {
|
||||
self.style.get_digit_symbol()
|
||||
};
|
||||
let widths = self.get_horizontal_lengths(&format, with_decis);
|
||||
let area = center_horizontal(
|
||||
area,
|
||||
|
||||
@@ -151,6 +151,7 @@ impl TuiEventHandler for CountdownState {
|
||||
if !self.clock.is_done() {
|
||||
self.clock.tick();
|
||||
} else {
|
||||
self.clock.update_done_count();
|
||||
self.elapsed_clock.tick();
|
||||
if self.elapsed_clock.is_initial() {
|
||||
self.elapsed_clock.run();
|
||||
@@ -278,6 +279,7 @@ impl TuiEventHandler for CountdownState {
|
||||
|
||||
pub struct Countdown {
|
||||
pub style: Style,
|
||||
pub blink: bool,
|
||||
}
|
||||
|
||||
fn human_days_diff(a: &OffsetDateTime, b: &OffsetDateTime) -> String {
|
||||
@@ -337,7 +339,7 @@ impl StatefulWidget for Countdown {
|
||||
}
|
||||
.to_uppercase(),
|
||||
);
|
||||
let widget = ClockWidget::new(self.style);
|
||||
let widget = ClockWidget::new(self.style, self.blink);
|
||||
let area = center(
|
||||
area,
|
||||
Constraint::Length(max(
|
||||
|
||||
@@ -130,6 +130,7 @@ impl TuiEventHandler for PomodoroState {
|
||||
match event {
|
||||
TuiEvent::Tick => {
|
||||
self.get_clock_mut().tick();
|
||||
self.get_clock_mut().update_done_count();
|
||||
}
|
||||
TuiEvent::Key(key) => match key.code {
|
||||
KeyCode::Char('s') => {
|
||||
@@ -170,12 +171,13 @@ impl TuiEventHandler for PomodoroState {
|
||||
|
||||
pub struct PomodoroWidget {
|
||||
pub style: Style,
|
||||
pub blink: bool,
|
||||
}
|
||||
|
||||
impl StatefulWidget for PomodoroWidget {
|
||||
type State = PomodoroState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let clock_widget = ClockWidget::new(self.style);
|
||||
let clock_widget = ClockWidget::new(self.style, self.blink);
|
||||
let label = Line::raw(
|
||||
(format!(
|
||||
"Pomodoro {} {}",
|
||||
|
||||
@@ -37,6 +37,7 @@ impl TuiEventHandler for TimerState {
|
||||
match event {
|
||||
TuiEvent::Tick => {
|
||||
self.clock.tick();
|
||||
self.clock.update_done_count();
|
||||
}
|
||||
TuiEvent::Key(key) => match key.code {
|
||||
KeyCode::Char('s') => {
|
||||
@@ -70,13 +71,14 @@ impl TuiEventHandler for TimerState {
|
||||
|
||||
pub struct Timer {
|
||||
pub style: Style,
|
||||
pub blink: bool,
|
||||
}
|
||||
|
||||
impl StatefulWidget for Timer {
|
||||
type State = TimerState;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let clock = &mut state.clock;
|
||||
let clock_widget = ClockWidget::new(self.style);
|
||||
let clock_widget = ClockWidget::new(self.style, self.blink);
|
||||
let label = Line::raw((format!("Timer {}", clock.get_mode())).to_uppercase());
|
||||
|
||||
let area = center(
|
||||
|
||||
Reference in New Issue
Block a user