Skip to content

Commit eef1afe

Browse files
nowNickjoshka
andauthored
feat(LineGauge): allow LineGauge background styles (#565)
This PR deprecates `gauge_style` in favor of `filled_style` and `unfilled_style` which can have it's foreground and background styled. `cargo run --example=line_gauge --features=crossterm` https://github.com/ratatui-org/ratatui/assets/5149215/5fb2ce65-8607-478f-8be4-092e08612f5b Implements: <#424> Co-authored-by: Josh McKinney <[email protected]>
1 parent 257db62 commit eef1afe

File tree

9 files changed

+333
-46
lines changed

9 files changed

+333
-46
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ name = "gauge"
246246
required-features = ["crossterm"]
247247
doc-scrape-examples = true
248248

249+
[[example]]
250+
name = "line_gauge"
251+
required-features = ["crossterm"]
252+
doc-scrape-examples = true
253+
249254
[[example]]
250255
name = "hello_world"
251256
required-features = ["crossterm"]

examples/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@ cargo run --example=gauge --features=crossterm
164164

165165
![Gauge][gauge.gif]
166166

167+
## Line Gauge
168+
169+
Demonstrates the [`Line
170+
Gauge`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.LineGauge.html) widget. Source:
171+
[line_gauge.rs](./line_gauge.rs).
172+
173+
```shell
174+
cargo run --example=line_gauge --features=crossterm
175+
```
176+
177+
![LineGauge][line_gauge.gif]
178+
167179
## Inline
168180

169181
Demonstrates how to use the
@@ -346,6 +358,7 @@ examples/vhs/generate.bash
346358
[inline.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/inline.gif?raw=true
347359
[layout.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/layout.gif?raw=true
348360
[list.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/list.gif?raw=true
361+
[line_gauge.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/line_gauge.gif?raw=true
349362
[modifiers.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/modifiers.gif?raw=true
350363
[panic.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/panic.gif?raw=true
351364
[paragraph.gif]: https://github.com/ratatui-org/ratatui/blob/images/examples/paragraph.gif?raw=true

examples/demo/ui.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn draw_gauges(f: &mut Frame, app: &mut App, area: Rect) {
7676

7777
let line_gauge = LineGauge::default()
7878
.block(Block::new().title("LineGauge:"))
79-
.gauge_style(Style::default().fg(Color::Magenta))
79+
.filled_style(Style::default().fg(Color::Magenta))
8080
.line_set(if app.enhanced_graphics {
8181
symbols::line::THICK
8282
} else {

examples/demo2/tabs/weather.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
140140
// cycle color hue based on the percent for a neat effect yellow -> red
141141
let hue = 90.0 - (percent as f32 * 0.6);
142142
let value = Okhsv::max_value();
143-
let fg = color_from_oklab(hue, Okhsv::max_saturation(), value);
144-
let bg = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
143+
let filled_color = color_from_oklab(hue, Okhsv::max_saturation(), value);
144+
let unfilled_color = color_from_oklab(hue, Okhsv::max_saturation(), value * 0.5);
145145
let label = if percent < 100.0 {
146146
format!("Downloading: {percent}%")
147147
} else {
@@ -151,7 +151,8 @@ fn render_line_gauge(percent: f64, area: Rect, buf: &mut Buffer) {
151151
.ratio(percent / 100.0)
152152
.label(label)
153153
.style(Style::new().light_blue())
154-
.gauge_style(Style::new().fg(fg).bg(bg))
154+
.filled_style(Style::new().fg(filled_color))
155+
.unfilled_style(Style::new().fg(unfilled_color))
155156
.line_set(symbols::line::THICK)
156157
.render(area, buf);
157158
}

examples/inline.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ fn ui(f: &mut Frame, downloads: &Downloads) {
248248
let done = NUM_DOWNLOADS - downloads.pending.len() - downloads.in_progress.len();
249249
#[allow(clippy::cast_precision_loss)]
250250
let progress = LineGauge::default()
251-
.gauge_style(Style::default().fg(Color::Blue))
251+
.filled_style(Style::default().fg(Color::Blue))
252252
.label(format!("{done}/{NUM_DOWNLOADS}"))
253253
.ratio(done as f64 / NUM_DOWNLOADS as f64);
254254
f.render_widget(progress, progress_area);

examples/line_gauge.rs

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//! # [Ratatui] Line 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-org/ratatui
13+
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
14+
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
15+
16+
use std::{io::stdout, time::Duration};
17+
18+
use color_eyre::{config::HookBuilder, Result};
19+
use crossterm::{
20+
event::{self, Event, KeyCode, KeyEventKind},
21+
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
22+
ExecutableCommand,
23+
};
24+
use ratatui::{
25+
prelude::*,
26+
style::palette::tailwind,
27+
widgets::{block::Title, *},
28+
};
29+
30+
const CUSTOM_LABEL_COLOR: Color = tailwind::SLATE.c200;
31+
32+
#[derive(Debug, Default, Clone, Copy)]
33+
struct App {
34+
state: AppState,
35+
progress_columns: u16,
36+
progress: f64,
37+
}
38+
39+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
40+
enum AppState {
41+
#[default]
42+
Running,
43+
Started,
44+
Quitting,
45+
}
46+
47+
fn main() -> Result<()> {
48+
init_error_hooks()?;
49+
let terminal = init_terminal()?;
50+
App::default().run(terminal)?;
51+
restore_terminal()?;
52+
Ok(())
53+
}
54+
55+
impl App {
56+
fn run(&mut self, mut terminal: Terminal<impl Backend>) -> Result<()> {
57+
while self.state != AppState::Quitting {
58+
self.draw(&mut terminal)?;
59+
self.handle_events()?;
60+
self.update(terminal.size()?.width);
61+
}
62+
Ok(())
63+
}
64+
65+
fn draw(&self, terminal: &mut Terminal<impl Backend>) -> Result<()> {
66+
terminal.draw(|f| f.render_widget(self, f.size()))?;
67+
Ok(())
68+
}
69+
70+
fn update(&mut self, terminal_width: u16) {
71+
if self.state != AppState::Started {
72+
return;
73+
}
74+
75+
self.progress_columns = (self.progress_columns + 1).clamp(0, terminal_width);
76+
self.progress = f64::from(self.progress_columns) / f64::from(terminal_width);
77+
}
78+
79+
fn handle_events(&mut self) -> Result<()> {
80+
let timeout = Duration::from_secs_f32(1.0 / 20.0);
81+
if event::poll(timeout)? {
82+
if let Event::Key(key) = event::read()? {
83+
if key.kind == KeyEventKind::Press {
84+
match key.code {
85+
KeyCode::Char(' ') | KeyCode::Enter => self.start(),
86+
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
87+
_ => {}
88+
}
89+
}
90+
}
91+
}
92+
Ok(())
93+
}
94+
95+
fn start(&mut self) {
96+
self.state = AppState::Started;
97+
}
98+
99+
fn quit(&mut self) {
100+
self.state = AppState::Quitting;
101+
}
102+
}
103+
104+
impl Widget for &App {
105+
fn render(self, area: Rect, buf: &mut Buffer) {
106+
use Constraint::{Length, Min, Ratio};
107+
let layout = Layout::vertical([Length(2), Min(0), Length(1)]);
108+
let [header_area, main_area, footer_area] = layout.areas(area);
109+
110+
let layout = Layout::vertical([Ratio(1, 3); 3]);
111+
let [gauge1_area, gauge2_area, gauge3_area] = layout.areas(main_area);
112+
113+
header().render(header_area, buf);
114+
footer().render(footer_area, buf);
115+
116+
self.render_gauge1(gauge1_area, buf);
117+
self.render_gauge2(gauge2_area, buf);
118+
self.render_gauge3(gauge3_area, buf);
119+
}
120+
}
121+
122+
fn header() -> impl Widget {
123+
Paragraph::new("Ratatui Line Gauge Example")
124+
.bold()
125+
.alignment(Alignment::Center)
126+
.fg(CUSTOM_LABEL_COLOR)
127+
}
128+
129+
fn footer() -> impl Widget {
130+
Paragraph::new("Press ENTER / SPACE to start")
131+
.alignment(Alignment::Center)
132+
.fg(CUSTOM_LABEL_COLOR)
133+
.bold()
134+
}
135+
136+
impl App {
137+
fn render_gauge1(&self, area: Rect, buf: &mut Buffer) {
138+
let title = title_block("Blue / red only foreground");
139+
LineGauge::default()
140+
.block(title)
141+
.filled_style(Style::default().fg(Color::Blue))
142+
.unfilled_style(Style::default().fg(Color::Red))
143+
.label("Foreground:")
144+
.ratio(self.progress)
145+
.render(area, buf);
146+
}
147+
148+
fn render_gauge2(&self, area: Rect, buf: &mut Buffer) {
149+
let title = title_block("Blue / red only background");
150+
LineGauge::default()
151+
.block(title)
152+
.filled_style(Style::default().fg(Color::Blue).bg(Color::Blue))
153+
.unfilled_style(Style::default().fg(Color::Red).bg(Color::Red))
154+
.label("Background:")
155+
.ratio(self.progress)
156+
.render(area, buf);
157+
}
158+
159+
fn render_gauge3(&self, area: Rect, buf: &mut Buffer) {
160+
let title = title_block("Fully styled with background");
161+
LineGauge::default()
162+
.block(title)
163+
.filled_style(
164+
Style::default()
165+
.fg(tailwind::BLUE.c400)
166+
.bg(tailwind::BLUE.c600),
167+
)
168+
.unfilled_style(
169+
Style::default()
170+
.fg(tailwind::RED.c400)
171+
.bg(tailwind::RED.c800),
172+
)
173+
.label("Both:")
174+
.ratio(self.progress)
175+
.render(area, buf);
176+
}
177+
}
178+
179+
fn title_block(title: &str) -> Block {
180+
let title = Title::from(title).alignment(Alignment::Center);
181+
Block::default()
182+
.title(title)
183+
.borders(Borders::NONE)
184+
.fg(CUSTOM_LABEL_COLOR)
185+
.padding(Padding::vertical(1))
186+
}
187+
188+
fn init_error_hooks() -> color_eyre::Result<()> {
189+
let (panic, error) = HookBuilder::default().into_hooks();
190+
let panic = panic.into_panic_hook();
191+
let error = error.into_eyre_hook();
192+
color_eyre::eyre::set_hook(Box::new(move |e| {
193+
let _ = restore_terminal();
194+
error(e)
195+
}))?;
196+
std::panic::set_hook(Box::new(move |info| {
197+
let _ = restore_terminal();
198+
panic(info);
199+
}));
200+
Ok(())
201+
}
202+
203+
fn init_terminal() -> color_eyre::Result<Terminal<impl Backend>> {
204+
enable_raw_mode()?;
205+
stdout().execute(EnterAlternateScreen)?;
206+
let backend = CrosstermBackend::new(stdout());
207+
let terminal = Terminal::new(backend)?;
208+
Ok(terminal)
209+
}
210+
211+
fn restore_terminal() -> color_eyre::Result<()> {
212+
disable_raw_mode()?;
213+
stdout().execute(LeaveAlternateScreen)?;
214+
Ok(())
215+
}

examples/vhs/line_gauge.tape

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
2+
# To run this script, install vhs and run `vhs ./examples/line_gauge.tape`
3+
Output "target/line_gauge.gif"
4+
Set Theme "Aardvark Blue"
5+
Set Width 1200
6+
Set Height 850
7+
Hide
8+
Type "cargo run --example=line_gauge --features=crossterm"
9+
Enter
10+
Sleep 2s
11+
Show
12+
Sleep 2s
13+
Enter 1
14+
Sleep 15s

0 commit comments

Comments
 (0)