Skip to main content

lv_tui/widgets/
progressbar.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;
7
8const BLOCKS: &[&str] = &[" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
9
10/// A progress bar using Unicode 8-segment block characters.
11///
12/// Displays a bar of `width` cells filled to `ratio` (0.0–1.0). Purely visual —
13/// not focusable, no keyboard interaction. Combine with `#[reactive]` and
14/// [`Event::Tick`] for animated progress.
15pub struct ProgressBar {
16    ratio: f64,
17    width: u16,
18    style: Style,
19    track_style: Style,
20}
21
22impl ProgressBar {
23    /// Creates a new progress bar at 0%, 20 cells wide.
24    pub fn new() -> Self {
25        Self {
26            ratio: 0.0,
27            width: 20,
28            style: Style::default().fg(crate::style::Color::Cyan),
29            track_style: Style::default().fg(crate::style::Color::Gray),
30        }
31    }
32
33    /// Builder: sets the fill ratio (0.0–1.0).
34    pub fn ratio(mut self, ratio: f64) -> Self {
35        self.ratio = ratio.clamp(0.0, 1.0);
36        self
37    }
38
39    /// Builder: sets the bar width in character cells.
40    pub fn width(mut self, width: u16) -> Self {
41        self.width = width;
42        self
43    }
44
45    /// Builder: sets the filled-portion style.
46    pub fn style(mut self, style: Style) -> Self {
47        self.style = style;
48        self
49    }
50
51    /// Builder: sets the unfilled-portion (track) style.
52    pub fn track_style(mut self, style: Style) -> Self {
53        self.track_style = style;
54        self
55    }
56
57    /// Sets the fill ratio and marks paint dirty.
58    pub fn set_ratio(&mut self, ratio: f64, cx: &mut EventCx) {
59        let r = ratio.clamp(0.0, 1.0);
60        if (self.ratio - r).abs() > f64::EPSILON {
61            self.ratio = r;
62            cx.invalidate_paint();
63        }
64    }
65}
66
67impl Component for ProgressBar {
68    fn render(&self, cx: &mut RenderCx) {
69        let filled = (self.ratio * self.width as f64) as u16;
70        let whole = filled.min(self.width);
71        let frac = ((self.ratio * self.width as f64) - whole as f64) * 8.0;
72        let frac_idx = (frac as usize).min(BLOCKS.len() - 1);
73
74        // Filled portion
75        if whole > 0 {
76            cx.set_style(self.style.clone());
77            cx.text("█".repeat(whole as usize));
78        }
79
80        // Fractional character + track
81        if whole < self.width {
82            if frac_idx > 0 {
83                cx.set_style(self.style.clone());
84                cx.text(BLOCKS[frac_idx]);
85            }
86            let frac_used = if frac_idx > 0 { 1 } else { 0 };
87            let remaining = self.width.saturating_sub(whole).saturating_sub(frac_used);
88            if remaining > 0 {
89                cx.set_style(self.track_style.clone());
90                cx.text("░".repeat(remaining as usize));
91            }
92        }
93
94        // Ensure cursor advances to end
95        cx.set_style(self.track_style.clone());
96        cx.line("");
97    }
98
99    fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
100        Size { width: self.width, height: 1 }
101    }
102
103    fn event(&mut self, _event: &Event, _cx: &mut EventCx) {}
104    fn layout(&mut self, _rect: Rect, _cx: &mut crate::component::LayoutCx) {}
105    fn focusable(&self) -> bool { false }
106    fn style(&self) -> Style { self.style.clone() }
107}