Skip to main content

intelli_shell/widgets/
loading.rs

1use std::borrow::Cow;
2
3use ratatui::{
4    backend::FromCrossterm,
5    Frame,
6    layout::Rect,
7    style::Style,
8    widgets::{Clear, Paragraph},
9};
10
11use crate::config::Theme;
12
13/// The characters for the spinner animation
14pub const SPINNER_CHARS: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
15
16/// A widget to display a centered loading spinner and message as an overlay
17pub struct LoadingSpinner<'a> {
18    /// The current state of the loading spinner animation
19    spinner_state: usize,
20    /// The style for the spinner text
21    style: Style,
22    /// Optional message to display with the spinner
23    message: Option<Cow<'a, str>>,
24}
25
26impl<'a> LoadingSpinner<'a> {
27    /// Creates a new [`LoadingSpinner`] styled according to the provided theme
28    pub fn new(theme: &Theme) -> Self {
29        Self {
30            spinner_state: 0,
31            style: Style::from_crossterm(theme.primary),
32            message: None,
33        }
34    }
35
36    /// Sets or replaces the message to be displayed with the spinner
37    pub fn with_message(mut self, message: impl Into<Cow<'a, str>>) -> Self {
38        self.set_message(message);
39        self
40    }
41
42    /// Sets or replaces the message to be displayed with the spinner
43    pub fn set_message(&mut self, message: impl Into<Cow<'a, str>>) {
44        self.message = Some(message.into());
45    }
46
47    /// Advances the spinner animation by one tick
48    pub fn tick(&mut self) {
49        self.spinner_state = (self.spinner_state + 1) % SPINNER_CHARS.len();
50    }
51
52    /// Renders the loading spinner in the center of the given area
53    pub fn render_in(&self, frame: &mut Frame, area: Rect) {
54        let spinner_char = SPINNER_CHARS[self.spinner_state];
55        let loading_text = if let Some(ref msg) = self.message {
56            format!("{spinner_char} {msg}")
57        } else {
58            spinner_char.to_string()
59        };
60
61        // Create the main paragraph widget
62        let loading_paragraph = Paragraph::new(loading_text).style(self.style);
63
64        // Clear the entire area before rendering
65        frame.render_widget(Clear, area);
66        frame.render_widget(loading_paragraph, area);
67    }
68}