1use rusty_bubbletea::model::{Cmd, Msg};
11use rusty_lipgloss::Style;
12use std::fmt;
13use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
14use std::sync::Arc;
15use std::time::Duration;
16
17const DEFAULT_BLINK_SPEED: Duration = Duration::from_millis(530);
18
19static LAST_ID: AtomicI64 = AtomicI64::new(0);
22
23fn next_id() -> i32 {
24 (LAST_ID.fetch_add(1, Ordering::SeqCst)) as i32
25}
26
27#[derive(Debug)]
29struct InitialBlinkMsg;
30
31#[derive(Debug, Clone)]
34pub struct BlinkMsg {
35 id: i32,
36 tag: i32,
37}
38
39#[derive(Debug)]
41struct BlinkCanceled;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum Mode {
46 Blink,
48 Static,
50 Hide,
52}
53
54impl Mode {
55 pub fn to_string(&self) -> &'static str {
58 match self {
59 Mode::Blink => "blink",
60 Mode::Static => "static",
61 Mode::Hide => "hidden",
62 }
63 }
64}
65
66#[derive(Clone)]
68pub struct Model {
69 pub style: Style,
71
72 pub text_style: Style,
75
76 pub blink_speed: Duration,
79
80 pub is_blinked: bool,
83
84 char: String,
86
87 id: i32,
89
90 focus: bool,
92
93 blink_cancel: Option<Arc<AtomicBool>>,
95
96 blink_tag: i32,
98
99 mode: Mode,
101}
102
103impl fmt::Debug for Model {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 f.debug_struct("cursor::Model")
106 .field("mode", &self.mode)
107 .field("focus", &self.focus)
108 .field("is_blinked", &self.is_blinked)
109 .finish()
110 }
111}
112
113pub fn new() -> Model {
115 Model {
116 id: next_id(),
117 blink_speed: DEFAULT_BLINK_SPEED,
118 is_blinked: true,
119 mode: Mode::Blink,
120 style: Style::new(),
121 text_style: Style::new(),
122 char: String::new(),
123 focus: false,
124 blink_cancel: None,
125 blink_tag: 0,
126 }
127}
128
129impl Model {
130 pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
132 if msg.as_any().downcast_ref::<InitialBlinkMsg>().is_some() {
133 if self.mode != Mode::Blink || !self.focus {
134 return None;
135 }
136 return self.blink();
137 }
138
139 if msg
140 .as_any()
141 .downcast_ref::<rusty_bubbletea::focus::FocusMsg>()
142 .is_some()
143 {
144 return self.focus();
145 }
146
147 if msg
148 .as_any()
149 .downcast_ref::<rusty_bubbletea::focus::BlurMsg>()
150 .is_some()
151 {
152 self.blur();
153 return None;
154 }
155
156 if let Some(m) = msg.as_any().downcast_ref::<BlinkMsg>() {
157 if self.mode != Mode::Blink || !self.focus {
162 return None;
163 }
164
165 if m.id != self.id || m.tag != self.blink_tag {
167 return None;
168 }
169
170 let mut cmd = None;
171 if self.mode == Mode::Blink {
172 self.is_blinked = !self.is_blinked;
173 cmd = self.blink();
174 }
175 return cmd;
176 }
177
178 if msg.as_any().downcast_ref::<BlinkCanceled>().is_some() {
179 return None;
181 }
182
183 None
184 }
185
186 pub fn mode(&self) -> Mode {
189 self.mode
190 }
191
192 pub fn set_mode(&mut self, mode: Mode) -> Cmd {
196 self.mode = mode;
197 self.is_blinked = mode == Mode::Hide || !self.focus;
198 if mode == Mode::Blink {
199 return Some(Box::new(|| Some(Box::new(InitialBlinkMsg))));
200 }
201 None
202 }
203
204 pub fn blink(&mut self) -> Cmd {
206 if self.mode != Mode::Blink {
207 return None;
208 }
209
210 if let Some(cancel) = &self.blink_cancel {
211 cancel.store(true, Ordering::SeqCst);
212 }
213
214 let cancel = Arc::new(AtomicBool::new(false));
215 self.blink_cancel = Some(cancel.clone());
216
217 self.blink_tag += 1;
218 let blink_msg = BlinkMsg {
219 id: self.id,
220 tag: self.blink_tag,
221 };
222 let speed = self.blink_speed;
223
224 Some(Box::new(move || {
225 std::thread::sleep(speed);
226 if cancel.load(Ordering::SeqCst) {
227 Some(Box::new(BlinkCanceled))
228 } else {
229 Some(Box::new(blink_msg))
230 }
231 }))
232 }
233
234 pub fn focus(&mut self) -> Cmd {
236 self.focus = true;
237 self.is_blinked = self.mode == Mode::Hide;
239
240 if self.mode == Mode::Blink && self.focus {
241 return self.blink();
242 }
243 None
244 }
245
246 pub fn blur(&mut self) {
248 self.focus = false;
249 self.is_blinked = true;
250 }
251
252 pub fn set_char(&mut self, char: &str) {
254 self.char = char.to_string();
255 }
256
257 pub fn view(&self) -> String {
259 if self.is_blinked {
260 self.text_style.clone().inline(true).render(&self.char)
261 } else {
262 self.style
263 .clone()
264 .inline(true)
265 .reverse(true)
266 .render(&self.char)
267 }
268 }
269}
270
271pub fn blink() -> Box<dyn Msg> {
273 Box::new(InitialBlinkMsg)
274}