use std::cell::Cell;
use web_time::{Duration, Instant};
pub const BLINK_INTERVAL_MS: u64 = 500;
pub struct CursorAnimationState {
cursor_alpha: Cell<f32>,
next_blink_time: Cell<Option<Instant>>,
}
impl CursorAnimationState {
pub const BLINK_INTERVAL: Duration = Duration::from_millis(BLINK_INTERVAL_MS);
pub const fn new() -> Self {
Self {
cursor_alpha: Cell::new(1.0),
next_blink_time: Cell::new(None),
}
}
pub fn start(&self) -> bool {
let flipped = !self.is_visible();
self.cursor_alpha.set(1.0);
self.next_blink_time
.set(Some(Instant::now() + Self::BLINK_INTERVAL));
flipped
}
pub fn stop(&self) -> bool {
let flipped = !self.is_visible();
self.cursor_alpha.set(1.0);
self.next_blink_time.set(None);
flipped
}
#[cfg(test)]
pub fn is_active(&self) -> bool {
self.next_blink_time.get().is_some()
}
pub fn is_visible(&self) -> bool {
self.cursor_alpha.get() > 0.5
}
pub fn tick(&self, now: Instant) -> bool {
if let Some(next) = self.next_blink_time.get()
&& now >= next
{
let new_alpha = if self.cursor_alpha.get() > 0.5 {
0.0
} else {
1.0
};
self.cursor_alpha.set(new_alpha);
self.next_blink_time.set(Some(now + Self::BLINK_INTERVAL));
return true;
}
false
}
pub fn next_blink_time(&self) -> Option<Instant> {
self.next_blink_time.get()
}
}
pub fn start_cursor_blink() {
if crate::render_state::with_cursor_animation(CursorAnimationState::start) {
invalidate_focused_caret();
}
}
pub fn stop_cursor_blink() {
if crate::render_state::with_cursor_animation(CursorAnimationState::stop) {
invalidate_focused_caret();
}
}
fn invalidate_focused_caret() {
if let Some(node_id) = crate::text_field_focus::focused_field_node() {
crate::schedule_draw_repass(node_id);
}
crate::request_render_invalidation();
}
#[inline]
pub fn reset_cursor_blink() {
start_cursor_blink();
}
pub fn suspend_cursor_blink() {
if crate::render_state::with_cursor_animation(CursorAnimationState::stop) {
invalidate_focused_caret();
}
}
pub fn is_cursor_visible() -> bool {
crate::render_state::with_cursor_animation(CursorAnimationState::is_visible)
}
pub fn tick_cursor_blink() -> bool {
tick_cursor_blink_at(Instant::now())
}
pub(crate) fn tick_cursor_blink_at(now: Instant) -> bool {
let flipped = crate::render_state::with_cursor_animation(|state| state.tick(now));
if flipped {
invalidate_focused_caret();
}
flipped
}
pub fn next_cursor_blink_time() -> Option<Instant> {
crate::render_state::with_cursor_animation(CursorAnimationState::next_blink_time)
}
#[cfg(test)]
#[path = "tests/cursor_animation_tests.rs"]
mod tests;