1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! The caret's **blink** (milestone 513): when a focused field's caret is shown, and when
//! the loop has to wake to show or hide it.
//!
//! Pure, like the recogniser in `gesture.rs`: instants come in as parameters. The rule is
//! the reference's — half a second shown, half a second hidden (`editable_text.dart:109`) —
//! and every change to the field, typed or moved, starts it again from shown, so a caret
//! never blinks out under a finger that is typing (`editable_text.dart:3718`, `:4552`).
//!
//! It runs on the wall clock, not the animation clock: that one is clamped per frame and
//! stands still between frames, and a caret at rest is exactly the case where there are
//! no frames.
use std::time::Duration;
use web_time::Instant;
use frus_widgets::{Edit, WidgetId};
/// How long the caret stays shown, and then hidden.
pub(crate) const HALF_PERIOD: Duration = Duration::from_millis(500);
/// What a caret's blink starts again on: which field has it, where the caret and the
/// selection are, and a digest of what the field holds.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Signature {
pub field: WidgetId,
pub edit: Edit,
pub value: u64,
}
/// The blink of the one caret on screen.
pub(crate) struct CaretBlink {
/// When the blink last started, shown; `None` with no caret.
since: Option<Instant>,
signature: Option<Signature>,
}
impl CaretBlink {
pub fn new() -> Self {
Self {
since: None,
signature: None,
}
}
/// This frame's caret, `None` when nothing with a caret has focus. A different field,
/// caret, selection or value starts the blink again from shown.
pub fn observe(&mut self, now: Instant, signature: Option<Signature>) {
if signature != self.signature {
self.since = signature.map(|_| now);
self.signature = signature;
}
}
/// Is the caret in its hidden half?
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
})
}
/// When the caret next turns on or off — the loop's next wake for it — or `None` with no
/// caret, which is no wake at all.
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");
}
/// Typing, or moving the caret, starts the blink again from shown — the same field
/// looked at again, unchanged, does not.
#[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");
}
/// With nothing to blink there is nothing hidden and nothing to wake for: a field
/// losing focus must not leave the loop waking twice a second for no caret.
#[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)));
}
}