use std::time::Duration;
use web_time::Instant;
use frus_widgets::{Edit, WidgetId};
pub(crate) const HALF_PERIOD: Duration = Duration::from_millis(500);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Signature {
pub field: WidgetId,
pub edit: Edit,
pub value: u64,
}
pub(crate) struct CaretBlink {
since: Option<Instant>,
signature: Option<Signature>,
}
impl CaretBlink {
pub fn new() -> Self {
Self {
since: None,
signature: None,
}
}
pub fn observe(&mut self, now: Instant, signature: Option<Signature>) {
if signature != self.signature {
self.since = signature.map(|_| now);
self.signature = signature;
}
}
pub fn hidden(&self, now: Instant) -> bool {
self.since.is_some_and(|since| {
now.saturating_duration_since(since).as_millis() / HALF_PERIOD.as_millis() % 2 == 1
})
}
pub fn next_toggle(&self, now: Instant) -> Option<Instant> {
let since = self.since?;
let half = HALF_PERIOD.as_millis();
let turns = now.saturating_duration_since(since).as_millis() / half + 1;
Some(since + Duration::from_millis((turns * half) as u64))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
fn at(cursor: usize) -> Option<Signature> {
Some(Signature {
field: WidgetId::from_u64(1),
edit: Edit {
cursor,
anchor: None,
composing: None,
},
value: 7,
})
}
#[test]
fn the_caret_is_shown_then_hidden_each_half_second() {
let t0 = Instant::now();
let mut blink = CaretBlink::new();
blink.observe(t0, at(3));
assert!(!blink.hidden(t0), "shown the moment it arrives");
assert!(!blink.hidden(t0 + ms(499)));
assert!(
blink.hidden(t0 + ms(500)),
"hidden for the second half second"
);
assert!(blink.hidden(t0 + ms(999)));
assert!(!blink.hidden(t0 + ms(1000)), "and shown again");
}
#[test]
fn a_change_starts_it_again_shown() {
let t0 = Instant::now();
let mut blink = CaretBlink::new();
blink.observe(t0, at(3));
blink.observe(t0 + ms(600), at(3));
assert!(
blink.hidden(t0 + ms(700)),
"nothing changed: still in its hidden half"
);
blink.observe(t0 + ms(700), at(4));
assert!(!blink.hidden(t0 + ms(700)), "a key: shown");
assert!(
!blink.hidden(t0 + ms(1100)),
"for a whole half second after it"
);
let mut typed = at(4);
typed.as_mut().unwrap().value = 8;
blink.observe(t0 + ms(1150), typed);
assert!(!blink.hidden(t0 + ms(1600)), "a new value restarts it too");
}
#[test]
fn with_no_caret_there_is_no_blink_and_no_wake() {
let t0 = Instant::now();
let mut blink = CaretBlink::new();
assert!(!blink.hidden(t0));
assert_eq!(blink.next_toggle(t0), None);
blink.observe(t0, at(3));
blink.observe(t0 + ms(200), None);
assert!(!blink.hidden(t0 + ms(700)));
assert_eq!(blink.next_toggle(t0 + ms(700)), None);
}
#[test]
fn the_loop_wakes_at_the_next_turn() {
let t0 = Instant::now();
let mut blink = CaretBlink::new();
blink.observe(t0, at(3));
assert_eq!(blink.next_toggle(t0), Some(t0 + ms(500)));
assert_eq!(blink.next_toggle(t0 + ms(499)), Some(t0 + ms(500)));
assert_eq!(blink.next_toggle(t0 + ms(500)), Some(t0 + ms(1000)));
assert_eq!(blink.next_toggle(t0 + ms(1234)), Some(t0 + ms(1500)));
}
}