Skip to content
This repository has been archived by the owner on Aug 6, 2023. It is now read-only.

fix(widgets/gauge): apply label style and avoid overflow on large labels #494

Merged
merged 2 commits into from Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 20 additions & 10 deletions examples/gauge.rs
@@ -1,13 +1,14 @@
#[allow(dead_code)]
mod util;

use crate::util::event::{Event, Events};
use std::{error::Error, io};
use crate::util::event::{Config, Event, Events};
use std::{error::Error, io, time::Duration};
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
backend::TermionBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::Span,
widgets::{Block, Borders, Gauge},
Terminal,
};
Expand All @@ -24,25 +25,25 @@ impl App {
App {
progress1: 0,
progress2: 0,
progress3: 0.0,
progress3: 0.45,
progress4: 0,
}
}

fn update(&mut self) {
self.progress1 += 5;
self.progress1 += 1;
if self.progress1 > 100 {
self.progress1 = 0;
}
self.progress2 += 10;
self.progress2 += 2;
if self.progress2 > 100 {
self.progress2 = 0;
}
self.progress3 += 0.001;
if self.progress3 > 1.0 {
self.progress3 = 0.0;
}
self.progress4 += 3;
self.progress4 += 1;
if self.progress4 > 100 {
self.progress4 = 0;
}
Expand All @@ -57,7 +58,9 @@ fn main() -> Result<(), Box<dyn Error>> {
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;

let events = Events::new();
let events = Events::with_config(Config {
tick_rate: Duration::from_millis(100),
});

let mut app = App::new();

Expand Down Expand Up @@ -91,10 +94,18 @@ fn main() -> Result<(), Box<dyn Error>> {
.label(label);
f.render_widget(gauge, chunks[1]);

let label = Span::styled(
format!("{:.2}%", app.progress3 * 100.0),
Style::default()
.fg(Color::Red)
.add_modifier(Modifier::ITALIC | Modifier::BOLD),
);
let gauge = Gauge::default()
.block(Block::default().title("Gauge3").borders(Borders::ALL))
.gauge_style(Style::default().fg(Color::Yellow))
.ratio(app.progress3);
.ratio(app.progress3)
.label(label)
.use_unicode(true);
f.render_widget(gauge, chunks[2]);

let label = format!("{}/100", app.progress2);
Expand All @@ -106,8 +117,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.add_modifier(Modifier::ITALIC),
)
.percent(app.progress4)
.label(label)
.use_unicode(true);
.label(label);
f.render_widget(gauge, chunks[3]);
})?;

Expand Down
65 changes: 27 additions & 38 deletions src/widgets/gauge.rs
Expand Up @@ -107,56 +107,45 @@ impl<'a> Widget for Gauge<'a> {
return;
}

let center = gauge_area.height / 2 + gauge_area.top();
let width = f64::from(gauge_area.width) * self.ratio;
//go to regular rounding behavior if we're not using unicode blocks
let end = gauge_area.left()
+ if self.use_unicode {
width.floor() as u16
} else {
width.round() as u16
};
// Label
let ratio = self.ratio;
let label = self
.label
.unwrap_or_else(|| Span::from(format!("{}%", (ratio * 100.0).round())));
// compute label value and its position
// label is put at the center of the gauge_area
let label = {
let pct = f64::round(self.ratio * 100.0);
self.label
.unwrap_or_else(|| Span::from(format!("{}%", pct)))
};
let clamped_label_width = gauge_area.width.min(label.width() as u16);
let label_col = gauge_area.left() + (gauge_area.width - clamped_label_width) / 2;
let label_row = gauge_area.top() + gauge_area.height / 2;

// the gauge will be filled proportionally to the ratio
let filled_width = f64::from(gauge_area.width) * self.ratio;
let end = if self.use_unicode {
gauge_area.left() + filled_width.floor() as u16
} else {
gauge_area.left() + filled_width.round() as u16
};
for y in gauge_area.top()..gauge_area.bottom() {
// Gauge
// render the filled area (left to end)
for x in gauge_area.left()..end {
buf.get_mut(x, y).set_symbol(" ");
}

//set unicode block
if self.use_unicode && self.ratio < 1.0 {
buf.get_mut(end, y)
.set_symbol(get_unicode_block(width % 1.0));
}

let mut color_end = end;

if y == center {
let label_width = label.width() as u16;
let middle = (gauge_area.width - label_width) / 2 + gauge_area.left();
buf.set_span(middle, y, &label, gauge_area.right() - middle);
if self.use_unicode && end >= middle && end < middle + label_width {
color_end = gauge_area.left() + (width.round() as u16); //set color on the label to the rounded gauge level
}
}

// Fix colors
for x in gauge_area.left()..color_end {
// spaces are needed to apply the background styling
buf.get_mut(x, y)
.set_symbol(" ")
.set_fg(self.gauge_style.bg.unwrap_or(Color::Reset))
.set_bg(self.gauge_style.fg.unwrap_or(Color::Reset));
}
if self.use_unicode && self.ratio < 1.0 {
buf.get_mut(end, y)
.set_symbol(get_unicode_block(filled_width % 1.0));
}
}
// set the span
buf.set_span(label_col, label_row, &label, clamped_label_width);
}
}

fn get_unicode_block<'a>(frac: f64) -> &'a str {
match (frac * 8.0).round() as u16 {
//get how many eighths the fraction is closest to
1 => symbols::block::ONE_EIGHTH,
2 => symbols::block::ONE_QUARTER,
3 => symbols::block::THREE_EIGHTHS,
Expand Down
86 changes: 85 additions & 1 deletion tests/widgets_gauge.rs
Expand Up @@ -2,8 +2,9 @@ use tui::{
backend::TestBackend,
buffer::Buffer,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
style::{Color, Modifier, Style},
symbols,
text::Span,
widgets::{Block, Borders, Gauge, LineGauge},
Terminal,
};
Expand Down Expand Up @@ -116,6 +117,89 @@ fn widgets_gauge_renders_no_unicode() {
terminal.backend().assert_buffer(&expected);
}

#[test]
fn widgets_gauge_applies_styles() {
let backend = TestBackend::new(12, 5);
let mut terminal = Terminal::new(backend).unwrap();

terminal
.draw(|f| {
let gauge = Gauge::default()
.block(
Block::default()
.title(Span::styled("Test", Style::default().fg(Color::Red)))
.borders(Borders::ALL),
)
.gauge_style(Style::default().fg(Color::Blue).bg(Color::Red))
.percent(43)
.label(Span::styled(
"43%",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
));
f.render_widget(gauge, f.size());
})
.unwrap();
let mut expected = Buffer::with_lines(vec![
"┌Test──────┐",
"│ │",
"│ 43% │",
"│ │",
"└──────────┘",
]);
// title
expected.set_style(Rect::new(1, 0, 4, 1), Style::default().fg(Color::Red));
// gauge area
expected.set_style(
Rect::new(1, 1, 10, 3),
Style::default().fg(Color::Blue).bg(Color::Red),
);
// filled area
for y in 1..4 {
expected.set_style(
Rect::new(1, y, 4, 1),
// filled style is invert of gauge_style
Style::default().fg(Color::Red).bg(Color::Blue),
);
}
// label (foreground and modifier from label style)
expected.set_style(
Rect::new(4, 2, 1, 1),
Style::default()
.fg(Color::Green)
// "4" is in the filled area so background is gauge_style foreground
.bg(Color::Blue)
.add_modifier(Modifier::BOLD),
);
expected.set_style(
Rect::new(5, 2, 2, 1),
Style::default()
.fg(Color::Green)
// "3%" is not in the filled area so background is gauge_style background
.bg(Color::Red)
.add_modifier(Modifier::BOLD),
);
terminal.backend().assert_buffer(&expected);
}

#[test]
fn widgets_gauge_supports_large_labels() {
let backend = TestBackend::new(10, 1);
let mut terminal = Terminal::new(backend).unwrap();

terminal
.draw(|f| {
let gauge = Gauge::default()
.percent(43)
.label("43333333333333333333333333333%");
f.render_widget(gauge, f.size());
})
.unwrap();
let expected = Buffer::with_lines(vec!["4333333333"]);
terminal.backend().assert_buffer(&expected);
}

#[test]
fn widgets_line_gauge_renders() {
let backend = TestBackend::new(20, 4);
Expand Down