lv_tui/widgets/
spinner.rs1use 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
11pub struct Spinner {
16 message: Text,
17 frame: usize,
18 style: Style,
19}
20
21impl Spinner {
22 pub fn new(message: impl Into<Text>) -> Self {
24 Self { message: message.into(), frame: 0, style: Style::default() }
25 }
26
27 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() != ' '); }
65}