From 5050f1ce1c4a7790ee29a750709950f8b123939a Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Mon, 28 Sep 2020 00:28:26 +0200 Subject: [PATCH] feat(widgets/gauge): add `LineGauge` variant of `Gauge` --- examples/demo/ui.rs | 28 +++++++-- src/widgets/gauge.rs | 134 ++++++++++++++++++++++++++++++++++++++++- src/widgets/mod.rs | 2 +- tests/widgets_gauge.rs | 58 +++++++++++++++++- 4 files changed, 213 insertions(+), 9 deletions(-) diff --git a/examples/demo/ui.rs b/examples/demo/ui.rs index 600ba97..95c1246 100644 --- a/examples/demo/ui.rs +++ b/examples/demo/ui.rs @@ -7,8 +7,8 @@ use tui::{ text::{Span, Spans}, widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle}, widgets::{ - Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, List, ListItem, Paragraph, Row, - Sparkline, Table, Tabs, Wrap, + Axis, BarChart, Block, Borders, Chart, Dataset, Gauge, LineGauge, List, ListItem, + Paragraph, Row, Sparkline, Table, Tabs, Wrap, }, Frame, }; @@ -42,8 +42,8 @@ where let chunks = Layout::default() .constraints( [ - Constraint::Length(7), - Constraint::Min(7), + Constraint::Length(9), + Constraint::Min(8), Constraint::Length(7), ] .as_ref(), @@ -59,7 +59,14 @@ where B: Backend, { let chunks = Layout::default() - .constraints([Constraint::Length(2), Constraint::Length(3)].as_ref()) + .constraints( + [ + Constraint::Length(2), + Constraint::Length(3), + Constraint::Length(1), + ] + .as_ref(), + ) .margin(1) .split(area); let block = Block::default().borders(Borders::ALL).title("Graphs"); @@ -88,6 +95,17 @@ where symbols::bar::THREE_LEVELS }); f.render_widget(sparkline, chunks[1]); + + let line_gauge = LineGauge::default() + .block(Block::default().title("LineGauge:")) + .gauge_style(Style::default().fg(Color::Magenta)) + .line_set(if app.enhanced_graphics { + symbols::line::THICK + } else { + symbols::line::NORMAL + }) + .ratio(app.progress); + f.render_widget(line_gauge, chunks[2]); } fn draw_charts(f: &mut Frame, app: &mut App, area: Rect) diff --git a/src/widgets/gauge.rs b/src/widgets/gauge.rs index 1beae59..c86e07b 100644 --- a/src/widgets/gauge.rs +++ b/src/widgets/gauge.rs @@ -2,7 +2,8 @@ use crate::{ buffer::Buffer, layout::Rect, style::{Color, Style}, - text::Span, + symbols, + text::{Span, Spans}, widgets::{Block, Widget}, }; @@ -129,6 +130,137 @@ impl<'a> Widget for Gauge<'a> { } } +/// A compact widget to display a task progress over a single line. +/// +/// # Examples: +/// +/// ``` +/// # use tui::widgets::{Widget, LineGauge, Block, Borders}; +/// # use tui::style::{Style, Color, Modifier}; +/// # use tui::symbols; +/// LineGauge::default() +/// .block(Block::default().borders(Borders::ALL).title("Progress")) +/// .gauge_style(Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD)) +/// .line_set(symbols::line::THICK) +/// .ratio(0.4); +/// ``` +pub struct LineGauge<'a> { + block: Option>, + ratio: f64, + label: Option>, + line_set: symbols::line::Set, + style: Style, + gauge_style: Style, +} + +impl<'a> Default for LineGauge<'a> { + fn default() -> Self { + Self { + block: None, + ratio: 0.0, + label: None, + style: Style::default(), + line_set: symbols::line::NORMAL, + gauge_style: Style::default(), + } + } +} + +impl<'a> LineGauge<'a> { + pub fn block(mut self, block: Block<'a>) -> Self { + self.block = Some(block); + self + } + + pub fn ratio(mut self, ratio: f64) -> Self { + assert!( + ratio <= 1.0 && ratio >= 0.0, + "Ratio should be between 0 and 1 inclusively." + ); + self.ratio = ratio; + self + } + + pub fn line_set(mut self, set: symbols::line::Set) -> Self { + self.line_set = set; + self + } + + pub fn label(mut self, label: T) -> Self + where + T: Into>, + { + self.label = Some(label.into()); + self + } + + pub fn style(mut self, style: Style) -> Self { + self.style = style; + self + } + + pub fn gauge_style(mut self, style: Style) -> Self { + self.gauge_style = style; + self + } +} + +impl<'a> Widget for LineGauge<'a> { + fn render(mut self, area: Rect, buf: &mut Buffer) { + buf.set_style(area, self.style); + let gauge_area = match self.block.take() { + Some(b) => { + let inner_area = b.inner(area); + b.render(area, buf); + inner_area + } + None => area, + }; + + if gauge_area.height < 1 { + return; + } + + let ratio = self.ratio; + let label = self + .label + .unwrap_or_else(move || Spans::from(format!("{:.0}%", ratio * 100.0))); + let (col, row) = buf.set_spans( + gauge_area.left(), + gauge_area.top(), + &label, + gauge_area.width, + ); + let start = col + 1; + if start >= gauge_area.right() { + return; + } + + let end = start + + (f64::from(gauge_area.right().saturating_sub(start)) * self.ratio).floor() as u16; + for col in start..end { + buf.get_mut(col, row) + .set_symbol(self.line_set.horizontal) + .set_style(Style { + fg: self.gauge_style.fg, + bg: None, + add_modifier: self.gauge_style.add_modifier, + sub_modifier: self.gauge_style.sub_modifier, + }); + } + for col in end..gauge_area.right() { + buf.get_mut(col, row) + .set_symbol(self.line_set.horizontal) + .set_style(Style { + fg: self.gauge_style.bg, + bg: None, + add_modifier: self.gauge_style.add_modifier, + sub_modifier: self.gauge_style.sub_modifier, + }); + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 99a3c9f..af94bd1 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -32,7 +32,7 @@ pub use self::barchart::BarChart; pub use self::block::{Block, BorderType}; pub use self::chart::{Axis, Chart, Dataset, GraphType}; pub use self::clear::Clear; -pub use self::gauge::Gauge; +pub use self::gauge::{Gauge, LineGauge}; pub use self::list::{List, ListItem, ListState}; pub use self::paragraph::{Paragraph, Wrap}; pub use self::sparkline::Sparkline; diff --git a/tests/widgets_gauge.rs b/tests/widgets_gauge.rs index 8acc62c..2dc304a 100644 --- a/tests/widgets_gauge.rs +++ b/tests/widgets_gauge.rs @@ -1,8 +1,10 @@ use tui::{ backend::TestBackend, buffer::Buffer, - layout::{Constraint, Direction, Layout}, - widgets::{Block, Borders, Gauge}, + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Style}, + symbols, + widgets::{Block, Borders, Gauge, LineGauge}, Terminal, }; @@ -42,3 +44,55 @@ fn widgets_gauge_renders() { ]); terminal.backend().assert_buffer(&expected); } + +#[test] +fn widgets_line_gauge_renders() { + let backend = TestBackend::new(20, 4); + let mut terminal = Terminal::new(backend).unwrap(); + terminal + .draw(|f| { + let gauge = LineGauge::default() + .gauge_style(Style::default().fg(Color::Green).bg(Color::White)) + .ratio(0.43); + f.render_widget( + gauge, + Rect { + x: 0, + y: 0, + width: 20, + height: 1, + }, + ); + let gauge = LineGauge::default() + .block(Block::default().title("Gauge 2").borders(Borders::ALL)) + .gauge_style(Style::default().fg(Color::Green)) + .line_set(symbols::line::THICK) + .ratio(0.211_313_934_313_1); + f.render_widget( + gauge, + Rect { + x: 0, + y: 1, + width: 20, + height: 3, + }, + ); + }) + .unwrap(); + let mut expected = Buffer::with_lines(vec![ + "43% ────────────────", + "┌Gauge 2───────────┐", + "│21% ━━━━━━━━━━━━━━│", + "└──────────────────┘", + ]); + for col in 4..10 { + expected.get_mut(col, 0).set_fg(Color::Green); + } + for col in 10..20 { + expected.get_mut(col, 0).set_fg(Color::White); + } + for col in 5..7 { + expected.get_mut(col, 2).set_fg(Color::Green); + } + terminal.backend().assert_buffer(&expected); +}