1use ratatui_core::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Color, Style},
5 widgets::Widget,
6};
7
8use crate::animation::{AnimationMode, cell_intensity, interpolate_color, is_uniform};
9use crate::defaults;
10
11const DEFAULT_WIDTHS: [f32; 7] = [0.85, 0.60, 0.95, 0.45, 0.75, 0.55, 0.70];
13
14#[must_use]
20#[derive(Debug, Clone)]
21pub struct SkeletonHBarChart<'a> {
22 elapsed_ms: u64,
23 mode: AnimationMode,
24 braille: bool,
25 base: Color,
26 highlight: Color,
27 bars: u16,
28 bar_height: u16,
29 widths: &'a [f32],
30 block: Option<ratatui_widgets::block::Block<'a>>,
31}
32
33impl<'a> SkeletonHBarChart<'a> {
34 pub fn new(elapsed_ms: u64) -> Self {
35 Self {
36 elapsed_ms,
37 mode: AnimationMode::default(),
38 braille: false,
39 base: defaults::BASE,
40 highlight: defaults::HIGHLIGHT,
41 bars: 5,
42 bar_height: 1,
43 widths: &DEFAULT_WIDTHS,
44 block: None,
45 }
46 }
47
48 pub fn mode(mut self, mode: AnimationMode) -> Self {
49 self.mode = mode;
50 self
51 }
52
53 pub fn braille(mut self, braille: bool) -> Self {
54 self.braille = braille;
55 self
56 }
57
58 pub fn base(mut self, color: impl Into<Color>) -> Self {
59 self.base = color.into();
60 self
61 }
62
63 pub fn highlight(mut self, color: impl Into<Color>) -> Self {
64 self.highlight = color.into();
65 self
66 }
67
68 pub fn bars(mut self, bars: u16) -> Self {
70 self.bars = bars;
71 self
72 }
73
74 pub fn bar_height(mut self, height: u16) -> Self {
76 self.bar_height = height;
77 self
78 }
79
80 pub fn widths(mut self, widths: &'a [f32]) -> Self {
84 self.widths = widths;
85 self
86 }
87
88 pub fn block(mut self, block: ratatui_widgets::block::Block<'a>) -> Self {
89 self.block = Some(block);
90 self
91 }
92}
93
94impl Widget for SkeletonHBarChart<'_> {
95 fn render(self, area: Rect, buf: &mut Buffer) {
96 let inner = if let Some(ref block) = self.block {
97 let inner_area = block.inner(area);
98 block.render(area, buf);
99 inner_area
100 } else {
101 area
102 };
103
104 if inner.is_empty() || self.widths.is_empty() || self.bar_height == 0 {
105 return;
106 }
107
108 let stride = self.bar_height + 1; let bar_count = self.bars.min((inner.height + 1) / stride);
110
111 let uniform_t = is_uniform(self.mode)
112 .then(|| cell_intensity(self.mode, self.elapsed_ms, 0, inner.width));
113
114 for i in 0..bar_count {
115 let frac = self.widths[i as usize % self.widths.len()].clamp(0.0, 1.0);
116 let bar_width = ((inner.width as f32) * frac).ceil() as u16;
117 let bar_y = inner.y + i * stride;
118
119 for dy in 0..self.bar_height {
120 let y = bar_y + dy;
121
122 if y >= inner.bottom() {
123 break;
124 }
125
126 let row = y - inner.y;
127
128 for col in 0..bar_width.min(inner.width) {
129 let x = inner.x + col;
130
131 let t = uniform_t.unwrap_or_else(|| {
132 cell_intensity(self.mode, self.elapsed_ms, col, inner.width)
133 });
134 let fg = interpolate_color(self.base, self.highlight, self.mode, t);
135 let glyph = crate::animation::cell_glyph(
136 self.braille,
137 self.mode,
138 self.elapsed_ms,
139 row,
140 col,
141 );
142
143 buf[(x, y)]
144 .set_char(glyph)
145 .set_style(Style::default().fg(fg));
146 }
147 }
148 }
149 }
150}
151
152#[cfg(feature = "pantry")]
153#[path = "hbar_chart.ingredient.rs"]
154pub mod ingredient;
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn bars_extend_from_left() {
162 let area = Rect::new(0, 0, 20, 5);
163 let mut buf = Buffer::empty(area);
164
165 SkeletonHBarChart::new(1000)
166 .bars(1)
167 .bar_height(1)
168 .widths(&[0.5])
169 .render(area, &mut buf);
170
171 assert_eq!(buf[(9, 0)].symbol(), "█");
173 assert_eq!(buf[(10, 0)].symbol(), " ");
174 }
175
176 #[test]
177 fn bars_have_gaps() {
178 let area = Rect::new(0, 0, 10, 5);
179 let mut buf = Buffer::empty(area);
180
181 SkeletonHBarChart::new(1000)
182 .bars(2)
183 .bar_height(1)
184 .widths(&[1.0, 1.0])
185 .render(area, &mut buf);
186
187 assert_eq!(buf[(0, 0)].symbol(), "█");
189 assert_eq!(buf[(0, 1)].symbol(), " ");
190 assert_eq!(buf[(0, 2)].symbol(), "█");
191 }
192
193 #[test]
194 fn multi_row_bars() {
195 let area = Rect::new(0, 0, 10, 7);
196 let mut buf = Buffer::empty(area);
197
198 SkeletonHBarChart::new(1000)
199 .bars(2)
200 .bar_height(2)
201 .widths(&[1.0, 1.0])
202 .render(area, &mut buf);
203
204 assert_eq!(buf[(0, 0)].symbol(), "█");
206 assert_eq!(buf[(0, 1)].symbol(), "█");
207 assert_eq!(buf[(0, 2)].symbol(), " ");
208 assert_eq!(buf[(0, 3)].symbol(), "█");
209 assert_eq!(buf[(0, 4)].symbol(), "█");
210 }
211}