use std::time::{Duration, Instant};
use rdom_core::NodeId;
use crate::TuiDom;
pub const DEFAULT_CARET_BLINK: Duration = Duration::from_millis(530);
#[derive(Debug, Clone, Copy)]
struct Shown {
host: NodeId,
serial: rdom_core::SelectionSerial,
epoch: Instant,
off: bool,
}
#[derive(Debug)]
pub(crate) struct CaretBlink {
period: Option<Duration>,
terminal_focused: bool,
restart: bool,
shown: Option<Shown>,
next: Option<Instant>,
}
impl CaretBlink {
pub(crate) fn new(period: Option<Duration>) -> Self {
Self {
period: period.filter(|p| !p.is_zero()),
terminal_focused: true,
restart: false,
shown: None,
next: None,
}
}
pub(crate) fn set_period(&mut self, period: Option<Duration>) {
self.period = period.filter(|p| !p.is_zero());
self.restart = true;
}
pub(crate) fn note_input(&mut self) {
self.restart = true;
}
pub(crate) fn set_terminal_focused(&mut self, focused: bool) {
if self.terminal_focused != focused {
self.terminal_focused = focused;
self.restart = true;
}
}
pub(crate) fn next_deadline(&self) -> Option<Instant> {
self.next
}
pub(crate) fn update(&mut self, dom: &mut TuiDom, now: Instant) -> bool {
let host = caret_host(dom);
let serial = dom.selection_serial();
let prev = self.shown.take();
let mut changed = false;
if let Some(p) = prev
&& Some(p.host) != host
&& p.off
{
changed |= set_off(dom, p.host, false);
}
self.next = None;
if let Some(host) = host {
let restart = self.restart || prev.is_none_or(|p| p.host != host || p.serial != serial);
let epoch = match prev {
Some(p) if !restart => p.epoch,
_ => now,
};
let off = if !self.terminal_focused {
true
} else if let Some(period) = self.period {
let phases = now.saturating_duration_since(epoch).as_nanos() / period.as_nanos();
self.next = u32::try_from(phases + 1)
.ok()
.and_then(|k| period.checked_mul(k))
.map(|d| epoch + d);
phases % 2 == 1
} else {
false
};
changed |= set_off(dom, host, off);
self.shown = Some(Shown {
host,
serial,
epoch,
off,
});
}
self.restart = false;
changed
}
}
fn caret_host(dom: &TuiDom) -> Option<NodeId> {
let sel = dom.selection()?;
if !sel.is_collapsed() {
return None;
}
let focused = dom.focused()?;
crate::node::nearest_editable_ancestor(dom, focused)
}
fn set_off(dom: &mut TuiDom, host: NodeId, off: bool) -> bool {
if !dom.contains(host) {
return false;
}
match dom.node_mut(host).ext_mut() {
Some(ext) if ext.caret_blink_off != off => {
ext.caret_blink_off = off;
true
}
_ => false,
}
}
#[cfg(test)]
mod tests;