1use rusty_bubbletea::commands;
11use rusty_bubbletea::model::{Cmd, Msg};
12use rusty_lipgloss::Style;
13use std::sync::atomic::{AtomicI64, Ordering};
14use std::time::{Duration, SystemTime};
15
16static LAST_ID: AtomicI64 = AtomicI64::new(0);
19
20fn next_id() -> i32 {
21 (LAST_ID.fetch_add(1, Ordering::SeqCst)) as i32
22}
23
24#[derive(Debug, Clone)]
26pub struct Spinner {
27 pub frames: Vec<String>,
29 pub fps: Duration,
31}
32
33pub fn line() -> Spinner {
35 Spinner {
36 frames: vec![
37 "|".to_string(),
38 "/".to_string(),
39 "-".to_string(),
40 "\\".to_string(),
41 ],
42 fps: Duration::from_millis(100),
43 }
44}
45
46pub fn dot() -> Spinner {
48 Spinner {
49 frames: vec![
50 "⣾ ".to_string(),
51 "⣽ ".to_string(),
52 "⣻ ".to_string(),
53 "⢿ ".to_string(),
54 "⡿ ".to_string(),
55 "⣟ ".to_string(),
56 "⣯ ".to_string(),
57 "⣷ ".to_string(),
58 ],
59 fps: Duration::from_millis(100),
60 }
61}
62
63pub fn mini_dot() -> Spinner {
65 Spinner {
66 frames: vec![
67 "⠋".to_string(),
68 "⠙".to_string(),
69 "⠹".to_string(),
70 "⠸".to_string(),
71 "⠼".to_string(),
72 "⠴".to_string(),
73 "⠦".to_string(),
74 "⠧".to_string(),
75 "⠇".to_string(),
76 "⠏".to_string(),
77 ],
78 fps: Duration::from_millis(83),
79 }
80}
81
82pub fn jump() -> Spinner {
84 Spinner {
85 frames: vec![
86 "⢄".to_string(),
87 "⢂".to_string(),
88 "⢁".to_string(),
89 "⡁".to_string(),
90 "⡈".to_string(),
91 "⡐".to_string(),
92 "⡠".to_string(),
93 ],
94 fps: Duration::from_millis(100),
95 }
96}
97
98pub fn pulse() -> Spinner {
100 Spinner {
101 frames: vec![
102 "█".to_string(),
103 "▓".to_string(),
104 "▒".to_string(),
105 "░".to_string(),
106 ],
107 fps: Duration::from_millis(125),
108 }
109}
110
111pub fn points() -> Spinner {
113 Spinner {
114 frames: vec![
115 "∙∙∙".to_string(),
116 "●∙∙".to_string(),
117 "∙●∙".to_string(),
118 "∙∙●".to_string(),
119 ],
120 fps: Duration::from_millis(142),
121 }
122}
123
124pub fn globe() -> Spinner {
126 Spinner {
127 frames: vec!["🌍".to_string(), "🌎".to_string(), "🌏".to_string()],
128 fps: Duration::from_millis(250),
129 }
130}
131
132pub fn moon() -> Spinner {
134 Spinner {
135 frames: vec![
136 "🌑".to_string(),
137 "🌒".to_string(),
138 "🌓".to_string(),
139 "🌔".to_string(),
140 "🌕".to_string(),
141 "🌖".to_string(),
142 "🌗".to_string(),
143 "🌘".to_string(),
144 ],
145 fps: Duration::from_millis(125),
146 }
147}
148
149pub fn monkey() -> Spinner {
151 Spinner {
152 frames: vec!["🙈".to_string(), "🙉".to_string(), "🙊".to_string()],
153 fps: Duration::from_millis(333),
154 }
155}
156
157pub fn meter() -> Spinner {
159 Spinner {
160 frames: vec![
161 "▱▱▱".to_string(),
162 "▰▱▱".to_string(),
163 "▰▰▱".to_string(),
164 "▰▰▰".to_string(),
165 "▰▰▱".to_string(),
166 "▰▱▱".to_string(),
167 "▱▱▱".to_string(),
168 ],
169 fps: Duration::from_millis(142),
170 }
171}
172
173pub fn hamburger() -> Spinner {
175 Spinner {
176 frames: vec![
177 "☱".to_string(),
178 "☲".to_string(),
179 "☴".to_string(),
180 "☲".to_string(),
181 ],
182 fps: Duration::from_millis(333),
183 }
184}
185
186pub fn ellipsis() -> Spinner {
188 Spinner {
189 frames: vec![
190 "".to_string(),
191 ".".to_string(),
192 "..".to_string(),
193 "...".to_string(),
194 ],
195 fps: Duration::from_millis(333),
196 }
197}
198
199pub struct Model {
202 pub spinner: Spinner,
204
205 pub style: Style,
208
209 frame: usize,
210 id: i32,
211 tag: i32,
212}
213
214impl std::fmt::Debug for Model {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.debug_struct("spinner::Model")
217 .field("id", &self.id)
218 .field("frame", &self.frame)
219 .finish()
220 }
221}
222
223impl Model {
225 pub fn id(&self) -> i32 {
227 self.id
228 }
229
230 pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
232 if let Some(m) = msg.as_any().downcast_ref::<TickMsg>() {
233 if m.id > 0 && m.id != self.id {
236 return None;
237 }
238
239 if m.tag > 0 && m.tag != self.tag {
243 return None;
244 }
245
246 self.frame += 1;
247 if self.frame >= self.spinner.frames.len() {
248 self.frame = 0;
249 }
250
251 self.tag += 1;
252 return self.tick(self.id, self.tag);
253 }
254 None
255 }
256
257 pub fn view(&self) -> String {
259 if self.frame >= self.spinner.frames.len() {
260 return "(error)".to_string();
261 }
262
263 self.style.render(&self.spinner.frames[self.frame])
264 }
265
266 pub fn tick_msg(&self) -> TickMsg {
269 TickMsg {
270 time: SystemTime::now(),
272
273 id: self.id,
278
279 tag: self.tag,
280 }
281 }
282
283 fn tick(&self, id: i32, tag: i32) -> Cmd {
284 let fps = self.spinner.fps;
285 commands::tick(fps, move |t| Some(Box::new(TickMsg { time: t, id, tag })))
286 }
287}
288
289#[derive(Debug, Clone)]
291pub struct TickMsg {
292 pub time: SystemTime,
294 tag: i32,
295 pub id: i32,
297}
298
299pub type Option = Box<dyn FnOnce(&mut Model)>;
306
307pub fn with_spinner(spinner: Spinner) -> Option {
309 Box::new(move |m: &mut Model| {
310 m.spinner = spinner;
311 })
312}
313
314pub fn with_style(style: Style) -> Option {
316 Box::new(move |m: &mut Model| {
317 m.style = style;
318 })
319}
320
321pub fn new(opts: Vec<Option>) -> Model {
323 let mut m = Model {
324 spinner: line(),
325 id: next_id(),
326 frame: 0,
327 tag: 0,
328 style: Style::new(),
329 };
330
331 for opt in opts {
332 opt(&mut m);
333 }
334
335 m
336}