textual_rs/widget/
loading_indicator.rs1use ratatui::buffer::Buffer;
3use ratatui::layout::Rect;
4use ratatui::style::{Color, Style};
5
6use super::context::AppContext;
7use super::Widget;
8
9const SPINNER_FRAMES: [char; 8] = [
13 '\u{28FE}', '\u{28FD}', '\u{28FB}', '\u{283F}', '\u{285F}', '\u{289F}', '\u{28AF}', '\u{28B7}',
14];
15
16pub struct LoadingIndicator;
36
37impl LoadingIndicator {
38 pub fn new() -> Self {
40 Self
41 }
42}
43
44impl Default for LoadingIndicator {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl Widget for LoadingIndicator {
51 fn widget_type_name(&self) -> &'static str {
52 "LoadingIndicator"
53 }
54
55 fn default_css() -> &'static str
56 where
57 Self: Sized,
58 {
59 "LoadingIndicator { width: 100%; height: 100%; min-height: 1; }"
60 }
61
62 fn render(&self, ctx: &AppContext, area: Rect, buf: &mut Buffer) {
63 if area.height == 0 || area.width == 0 {
64 return;
65 }
66
67 let style = Style::default().fg(Color::Rgb(0, 255, 163)); if ctx.skip_animations {
70 let text = "Loading...";
72 let x = area.x + area.width.saturating_sub(text.len() as u16) / 2;
73 let y = area.y + area.height / 2;
74 buf.set_string(x, y, text, style);
75 return;
76 }
77
78 let frame_idx = (ctx.spinner_tick.get() / 2) as usize % SPINNER_FRAMES.len();
80 let ch = SPINNER_FRAMES[frame_idx];
81 let x = area.x + area.width / 2;
82 let y = area.y + area.height / 2;
83 buf.set_string(x, y, ch.to_string(), style);
84 }
85}
86
87pub fn draw_loading_spinner_overlay(area: Rect, buf: &mut Buffer, tick: u8, skip_animations: bool) {
94 if area.height == 0 || area.width == 0 {
95 return;
96 }
97
98 let bg_style = Style::default().bg(Color::Rgb(20, 20, 28));
100 for y in area.y..area.y + area.height {
101 for x in area.x..area.x + area.width {
102 if let Some(cell) = buf.cell_mut((x, y)) {
103 cell.set_char(' ');
104 cell.set_style(bg_style);
105 }
106 }
107 }
108
109 let fg_style = Style::default()
111 .fg(Color::Rgb(0, 255, 163))
112 .bg(Color::Rgb(20, 20, 28));
113
114 if skip_animations {
115 let text = "Loading...";
116 let x = area.x + area.width.saturating_sub(text.len() as u16) / 2;
117 let y = area.y + area.height / 2;
118 buf.set_string(x, y, text, fg_style);
119 } else {
120 let frame_idx = (tick / 2) as usize % SPINNER_FRAMES.len();
121 let ch = SPINNER_FRAMES[frame_idx];
122 let x = area.x + area.width / 2;
123 let y = area.y + area.height / 2;
124 buf.set_string(x, y, ch.to_string(), fg_style);
125 }
126}