From 6af37dbeca38093b8cbdf86d97184058a30cbad9 Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Wed, 16 Jun 2021 20:14:16 +0200 Subject: [PATCH 1/2] fix(widgets/gauge): apply label style and avoid overflow on large labels --- src/widgets/gauge.rs | 65 +++++++++++++------------------ tests/widgets_gauge.rs | 86 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 39 deletions(-) diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index ebff1889..a98a6803 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -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, diff --git a/tests/widgets_gauge.rs b/tests/widgets_gauge.rs index cc3ca951..ad1f5f66 100644 --- a/tests/widgets_gauge.rs +++ b/tests/widgets_gauge.rs @@ -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, }; @@ -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); From 2ad57b92a4b96708d4789cdaa250f18c5565f987 Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Wed, 16 Jun 2021 20:14:53 +0200 Subject: [PATCH 2/2] refactor(examples): show more use case in gauge example --- examples/gauge.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/gauge.rs b/examples/gauge.rs index 44e96e0b..9cb097be 100644 --- a/examples/gauge.rs +++ b/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, }; @@ -24,17 +25,17 @@ 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; } @@ -42,7 +43,7 @@ impl App { if self.progress3 > 1.0 { self.progress3 = 0.0; } - self.progress4 += 3; + self.progress4 += 1; if self.progress4 > 100 { self.progress4 = 0; } @@ -57,7 +58,9 @@ fn main() -> Result<(), Box> { 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(); @@ -91,10 +94,18 @@ fn main() -> Result<(), Box> { .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); @@ -106,8 +117,7 @@ fn main() -> Result<(), Box> { .add_modifier(Modifier::ITALIC), ) .percent(app.progress4) - .label(label) - .use_unicode(true); + .label(label); f.render_widget(gauge, chunks[3]); })?;