aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/tui/spinner.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/atuin-ai/src/tui/spinner.rs')
-rw-r--r--crates/atuin-ai/src/tui/spinner.rs99
1 files changed, 99 insertions, 0 deletions
diff --git a/crates/atuin-ai/src/tui/spinner.rs b/crates/atuin-ai/src/tui/spinner.rs
new file mode 100644
index 00000000..138e0269
--- /dev/null
+++ b/crates/atuin-ai/src/tui/spinner.rs
@@ -0,0 +1,99 @@
+//! Spinner styles and configuration for TUI animations
+//!
+//! To experiment with different spinners, change `ACTIVE_SPINNER` below.
+
+use std::time::Duration;
+
+/// Active spinner style - change this to experiment with different styles
+pub const ACTIVE_SPINNER: SpinnerStyle = SpinnerStyle::Dots;
+
+/// Spinner style definitions
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SpinnerStyle {
+ /// Classic ASCII line spinner: / - \ |
+ Line,
+ /// Braille dots pattern
+ Dots,
+ /// Growing/shrinking dots
+ Pulse,
+ /// Simple arrow rotation
+ Arrow,
+ /// Block building
+ Block,
+}
+
+impl SpinnerStyle {
+ /// Get the frames for this spinner style
+ pub const fn frames(&self) -> &'static [&'static str] {
+ match self {
+ SpinnerStyle::Line => &["/", "-", "\\", "|"],
+ SpinnerStyle::Dots => &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
+ SpinnerStyle::Pulse => &["·", "•", "●", "•"],
+ SpinnerStyle::Arrow => &["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
+ SpinnerStyle::Block => &[
+ "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏",
+ ],
+ }
+ }
+
+ /// Get the recommended tick interval for this spinner style
+ /// Faster spinners need shorter intervals to look smooth
+ pub const fn tick_interval(&self) -> Duration {
+ match self {
+ SpinnerStyle::Line => Duration::from_millis(150),
+ SpinnerStyle::Dots => Duration::from_millis(80),
+ SpinnerStyle::Pulse => Duration::from_millis(200),
+ SpinnerStyle::Arrow => Duration::from_millis(100),
+ SpinnerStyle::Block => Duration::from_millis(80),
+ }
+ }
+
+ /// Get the frame at the given index (wraps around)
+ pub fn frame_at(&self, index: usize) -> &'static str {
+ let frames = self.frames();
+ frames[index % frames.len()]
+ }
+
+ /// Get the number of frames in this spinner
+ pub fn frame_count(&self) -> usize {
+ self.frames().len()
+ }
+}
+
+/// Get the active spinner's frame at the given index
+pub fn active_frame(index: usize) -> &'static str {
+ ACTIVE_SPINNER.frame_at(index)
+}
+
+/// Get the active spinner's tick interval
+pub fn active_tick_interval() -> Duration {
+ ACTIVE_SPINNER.tick_interval()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_frame_wrapping() {
+ let style = SpinnerStyle::Line;
+ assert_eq!(style.frame_at(0), "/");
+ assert_eq!(style.frame_at(4), "/"); // wraps
+ assert_eq!(style.frame_at(5), "-");
+ }
+
+ #[test]
+ fn test_all_styles_have_frames() {
+ let styles = [
+ SpinnerStyle::Line,
+ SpinnerStyle::Dots,
+ SpinnerStyle::Pulse,
+ SpinnerStyle::Arrow,
+ SpinnerStyle::Block,
+ ];
+ for style in styles {
+ assert!(!style.frames().is_empty());
+ assert!(style.tick_interval().as_millis() > 0);
+ }
+ }
+}