[go: up one dir, main page]

gauge/
gauge.rs

1//! # [Ratatui] Gauge example
2//!
3//! The latest version of this example is available in the [examples] folder in the repository.
4//!
5//! Please note that the examples are designed to be run against the `main` branch of the Github
6//! repository. This means that you may not be able to compile with the latest release version on
7//! crates.io, or the one that you have installed locally.
8//!
9//! See the [examples readme] for more information on finding examples that match the version of the
10//! library you are using.
11//!
12//! [Ratatui]: https://github.com/ratatui/ratatui
13//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
14//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
15
16use std::time::Duration;
17
18use color_eyre::Result;
19use ratatui::{
20    buffer::Buffer,
21    crossterm::event::{self, Event, KeyCode, KeyEventKind},
22    layout::{Alignment, Constraint, Layout, Rect},
23    style::{palette::tailwind, Color, Style, Stylize},
24    text::{Line, Span},
25    widgets::{Block, Borders, Gauge, Padding, Paragraph, Widget},
26    DefaultTerminal,
27};
28
29const GAUGE1_COLOR: Color = tailwind::RED.c800;
30const GAUGE2_COLOR: Color = tailwind::GREEN.c800;
31const GAUGE3_COLOR: Color = tailwind::BLUE.c800;
32const GAUGE4_COLOR: Color = tailwind::ORANGE.c800;
33const CUSTOM_LABEL_COLOR: Color = tailwind::SLATE.c200;
34
35#[derive(Debug, Default, Clone, Copy)]
36struct App {
37    state: AppState,
38    progress_columns: u16,
39    progress1: u16,
40    progress2: f64,
41    progress3: f64,
42    progress4: f64,
43}
44
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
46enum AppState {
47    #[default]
48    Running,
49    Started,
50    Quitting,
51}
52
53fn main() -> Result<()> {
54    color_eyre::install()?;
55    let terminal = ratatui::init();
56    let app_result = App::default().run(terminal);
57    ratatui::restore();
58    app_result
59}
60
61impl App {
62    fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
63        while self.state != AppState::Quitting {
64            terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;
65            self.handle_events()?;
66            self.update(terminal.size()?.width);
67        }
68        Ok(())
69    }
70
71    fn update(&mut self, terminal_width: u16) {
72        if self.state != AppState::Started {
73            return;
74        }
75
76        // progress1 and progress2 help show the difference between ratio and percentage measuring
77        // the same thing, but converting to either a u16 or f64. Effectively, we're showing the
78        // difference between how a continuous gauge acts for floor and rounded values.
79        self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
80        self.progress1 = self.progress_columns * 100 / terminal_width;
81        self.progress2 = f64::from(self.progress_columns) * 100.0 / f64::from(terminal_width);
82
83        // progress3 and progress4 similarly show the difference between unicode and non-unicode
84        // gauges measuring the same thing.
85        self.progress3 = (self.progress3 + 0.1).clamp(40.0, 100.0);
86        self.progress4 = (self.progress4 + 0.1).clamp(40.0, 100.0);
87    }
88
89    fn handle_events(&mut self) -> Result<()> {
90        let timeout = Duration::from_secs_f32(1.0 / 20.0);
91        if event::poll(timeout)? {
92            if let Event::Key(key) = event::read()? {
93                if key.kind == KeyEventKind::Press {
94                    match key.code {
95                        KeyCode::Char(' ') | KeyCode::Enter => self.start(),
96                        KeyCode::Char('q') | KeyCode::Esc => self.quit(),
97                        _ => {}
98                    }
99                }
100            }
101        }
102        Ok(())
103    }
104
105    fn start(&mut self) {
106        self.state = AppState::Started;
107    }
108
109    fn quit(&mut self) {
110        self.state = AppState::Quitting;
111    }
112}
113
114impl Widget for &App {
115    #[allow(clippy::similar_names)]
116    fn render(self, area: Rect, buf: &mut Buffer) {
117        use Constraint::{Length, Min, Ratio};
118        let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
119        let [header_area, gauge_area, footer_area] = layout.areas(area);
120
121        let layout = Layout::vertical([Ratio(1, 4); 4]);
122        let [gauge1_area, gauge2_area, gauge3_area, gauge4_area] = layout.areas(gauge_area);
123
124        render_header(header_area, buf);
125        render_footer(footer_area, buf);
126
127        self.render_gauge1(gauge1_area, buf);
128        self.render_gauge2(gauge2_area, buf);
129        self.render_gauge3(gauge3_area, buf);
130        self.render_gauge4(gauge4_area, buf);
131    }
132}
133
134fn render_header(area: Rect, buf: &mut Buffer) {
135    Paragraph::new("Ratatui Gauge Example")
136        .bold()
137        .alignment(Alignment::Center)
138        .fg(CUSTOM_LABEL_COLOR)
139        .render(area, buf);
140}
141
142fn render_footer(area: Rect, buf: &mut Buffer) {
143    Paragraph::new("Press ENTER to start")
144        .alignment(Alignment::Center)
145        .fg(CUSTOM_LABEL_COLOR)
146        .bold()
147        .render(area, buf);
148}
149
150impl App {
151    fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
152        let title = title_block("Gauge with percentage");
153        Gauge::default()
154            .block(title)
155            .gauge_style(GAUGE1_COLOR)
156            .percent(self.progress1)
157            .render(area, buf);
158    }
159
160    fn render_gauge2(&self, area: Rect, buf: &mut Buffer) {
161        let title = title_block("Gauge with ratio and custom label");
162        let label = Span::styled(
163            format!("{:.1}/100", self.progress2),
164            Style::new().italic().bold().fg(CUSTOM_LABEL_COLOR),
165        );
166        Gauge::default()
167            .block(title)
168            .gauge_style(GAUGE2_COLOR)
169            .ratio(self.progress2 / 100.0)
170            .label(label)
171            .render(area, buf);
172    }
173
174    fn render_gauge3(&self, area: Rect, buf: &mut Buffer) {
175        let title = title_block("Gauge with ratio (no unicode)");
176        let label = format!("{:.1}%", self.progress3);
177        Gauge::default()
178            .block(title)
179            .gauge_style(GAUGE3_COLOR)
180            .ratio(self.progress3 / 100.0)
181            .label(label)
182            .render(area, buf);
183    }
184
185    fn render_gauge4(&self, area: Rect, buf: &mut Buffer) {
186        let title = title_block("Gauge with ratio (unicode)");
187        let label = format!("{:.1}%", self.progress3);
188        Gauge::default()
189            .block(title)
190            .gauge_style(GAUGE4_COLOR)
191            .ratio(self.progress4 / 100.0)
192            .label(label)
193            .use_unicode(true)
194            .render(area, buf);
195    }
196}
197
198fn title_block(title: &str) -> Block {
199    let title = Line::from(title).centered();
200    Block::new()
201        .borders(Borders::NONE)
202        .padding(Padding::vertical(1))
203        .title(title)
204        .fg(CUSTOM_LABEL_COLOR)
205}