Skip to main content

lv_tui/widgets/
spinner.rs

1use crate::component::{Component, EventCx, MeasureCx};
2use crate::event::Event;
3use crate::geom::{Rect, Size};
4use crate::layout::Constraint;
5use crate::render::RenderCx;
6use crate::style::Style;
7use crate::text::Text;
8
9const FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
10
11/// An animated spinner for loading states.
12///
13/// Renders a braille animation frame followed by a message. Advances one frame
14/// on each [`Event::Tick`]. Use [`App::tick_rate`] to control animation speed.
15pub struct Spinner {
16    message: Text,
17    frame: usize,
18    style: Style,
19}
20
21impl Spinner {
22    /// Creates a spinner with the given message.
23    pub fn new(message: impl Into<Text>) -> Self {
24        Self { message: message.into(), frame: 0, style: Style::default() }
25    }
26
27    /// Builder: sets the spinner style.
28    pub fn style(mut self, style: Style) -> Self { self.style = style; self }
29}
30
31impl Component for Spinner {
32    fn render(&self, cx: &mut RenderCx) {
33        let ch = FRAMES[self.frame % FRAMES.len()];
34        cx.set_style(self.style.clone());
35        cx.line(&format!("{} {}", ch, self.message.first_text()));
36    }
37
38    fn measure(&self, _c: Constraint, _cx: &mut MeasureCx) -> Size {
39        Size { width: self.message.max_width() + 2, height: 1 }
40    }
41
42    fn event(&mut self, event: &Event, cx: &mut EventCx) {
43        if matches!(event, Event::Tick) {
44            self.frame += 1;
45            cx.invalidate_paint();
46        }
47    }
48
49    fn layout(&mut self, _rect: Rect, _cx: &mut crate::component::LayoutCx) {}
50    fn focusable(&self) -> bool { false }
51    fn style(&self) -> Style { self.style.clone() }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::testbuffer::TestBuffer;
58
59    #[test]
60    fn test_spinner_renders() {
61        let mut tb = TestBuffer::new(20, 1);
62        tb.render(&Spinner::new("loading"));
63        assert!(tb.buffer.cells[0].symbol.chars().next().unwrap() != ' '); // frame char
64    }
65}