use rusty_bubbletea::model::{Cmd, Msg};
use rusty_lipgloss::Style;
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::Arc;
use std::time::Duration;
const DEFAULT_BLINK_SPEED: Duration = Duration::from_millis(530);
static LAST_ID: AtomicI64 = AtomicI64::new(0);
fn next_id() -> i32 {
(LAST_ID.fetch_add(1, Ordering::SeqCst)) as i32
}
#[derive(Debug)]
struct InitialBlinkMsg;
#[derive(Debug, Clone)]
pub struct BlinkMsg {
id: i32,
tag: i32,
}
#[derive(Debug)]
struct BlinkCanceled;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Blink,
Static,
Hide,
}
impl Mode {
pub fn to_string(&self) -> &'static str {
match self {
Mode::Blink => "blink",
Mode::Static => "static",
Mode::Hide => "hidden",
}
}
}
#[derive(Clone)]
pub struct Model {
pub style: Style,
pub text_style: Style,
pub blink_speed: Duration,
pub is_blinked: bool,
char: String,
id: i32,
focus: bool,
blink_cancel: Option<Arc<AtomicBool>>,
blink_tag: i32,
mode: Mode,
}
impl fmt::Debug for Model {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("cursor::Model")
.field("mode", &self.mode)
.field("focus", &self.focus)
.field("is_blinked", &self.is_blinked)
.finish()
}
}
pub fn new() -> Model {
Model {
id: next_id(),
blink_speed: DEFAULT_BLINK_SPEED,
is_blinked: true,
mode: Mode::Blink,
style: Style::new(),
text_style: Style::new(),
char: String::new(),
focus: false,
blink_cancel: None,
blink_tag: 0,
}
}
impl Model {
pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
if msg.as_any().downcast_ref::<InitialBlinkMsg>().is_some() {
if self.mode != Mode::Blink || !self.focus {
return None;
}
return self.blink();
}
if msg
.as_any()
.downcast_ref::<rusty_bubbletea::focus::FocusMsg>()
.is_some()
{
return self.focus();
}
if msg
.as_any()
.downcast_ref::<rusty_bubbletea::focus::BlurMsg>()
.is_some()
{
self.blur();
return None;
}
if let Some(m) = msg.as_any().downcast_ref::<BlinkMsg>() {
if self.mode != Mode::Blink || !self.focus {
return None;
}
if m.id != self.id || m.tag != self.blink_tag {
return None;
}
let mut cmd = None;
if self.mode == Mode::Blink {
self.is_blinked = !self.is_blinked;
cmd = self.blink();
}
return cmd;
}
if msg.as_any().downcast_ref::<BlinkCanceled>().is_some() {
return None;
}
None
}
pub fn mode(&self) -> Mode {
self.mode
}
pub fn set_mode(&mut self, mode: Mode) -> Cmd {
self.mode = mode;
self.is_blinked = mode == Mode::Hide || !self.focus;
if mode == Mode::Blink {
return Some(Box::new(|| Some(Box::new(InitialBlinkMsg))));
}
None
}
pub fn blink(&mut self) -> Cmd {
if self.mode != Mode::Blink {
return None;
}
if let Some(cancel) = &self.blink_cancel {
cancel.store(true, Ordering::SeqCst);
}
let cancel = Arc::new(AtomicBool::new(false));
self.blink_cancel = Some(cancel.clone());
self.blink_tag += 1;
let blink_msg = BlinkMsg {
id: self.id,
tag: self.blink_tag,
};
let speed = self.blink_speed;
Some(Box::new(move || {
std::thread::sleep(speed);
if cancel.load(Ordering::SeqCst) {
Some(Box::new(BlinkCanceled))
} else {
Some(Box::new(blink_msg))
}
}))
}
pub fn focus(&mut self) -> Cmd {
self.focus = true;
self.is_blinked = self.mode == Mode::Hide;
if self.mode == Mode::Blink && self.focus {
return self.blink();
}
None
}
pub fn blur(&mut self) {
self.focus = false;
self.is_blinked = true;
}
pub fn set_char(&mut self, char: &str) {
self.char = char.to_string();
}
pub fn view(&self) -> String {
if self.is_blinked {
self.text_style.clone().inline(true).render(&self.char)
} else {
self.style
.clone()
.inline(true)
.reverse(true)
.render(&self.char)
}
}
}
pub fn blink() -> Box<dyn Msg> {
Box::new(InitialBlinkMsg)
}