diff options
Diffstat (limited to 'src/canvas/components/tui_widget/pipe_gauge.rs')
-rw-r--r-- | src/canvas/components/tui_widget/pipe_gauge.rs | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/src/canvas/components/tui_widget/pipe_gauge.rs b/src/canvas/components/tui_widget/pipe_gauge.rs new file mode 100644 index 00000000..08aa4ca2 --- /dev/null +++ b/src/canvas/components/tui_widget/pipe_gauge.rs @@ -0,0 +1,224 @@ +use tui::{ + buffer::Buffer, + layout::Rect, + style::Style, + text::Line, + widgets::{Block, Widget}, +}; + +#[derive(Debug, Clone, Copy)] +pub enum LabelLimit { + None, + Auto(u16), + Bars, + StartLabel, +} + +impl Default for LabelLimit { + fn default() -> Self { + Self::None + } +} + +/// A widget to measure something, using pipe characters ('|') as a unit. +#[derive(Debug, Clone)] +pub struct PipeGauge<'a> { + block: Option<Block<'a>>, + ratio: f64, + start_label: Option<Line<'a>>, + inner_label: Option<Line<'a>>, + label_style: Style, + gauge_style: Style, + hide_parts: LabelLimit, +} + +impl<'a> Default for PipeGauge<'a> { + fn default() -> Self { + Self { + block: None, + ratio: 0.0, + start_label: None, + inner_label: None, + label_style: Style::default(), + gauge_style: Style::default(), + hide_parts: LabelLimit::default(), + } + } +} + +impl<'a> PipeGauge<'a> { + /// The ratio, a value from 0.0 to 1.0 (any other greater or less will be clamped) + /// represents the portion of the pipe gauge to fill. + /// + /// Note: passing in NaN will potentially cause problems. + pub fn ratio(mut self, ratio: f64) -> Self { + self.ratio = ratio.clamp(0.0, 1.0); + + self + } + + /// The label displayed before the bar. + pub fn start_label<T>(mut self, start_label: T) -> Self + where + T: Into<Line<'a>>, + { + self.start_label = Some(start_label.into()); + self + } + + /// The label displayed inside the bar. + pub fn inner_label<T>(mut self, inner_label: T) -> Self + where + T: Into<Line<'a>>, + { + self.inner_label = Some(inner_label.into()); + self + } + + /// The style of the labels. + pub fn label_style(mut self, label_style: Style) -> Self { + self.label_style = label_style; + self + } + + /// The style of the gauge itself. + pub fn gauge_style(mut self, style: Style) -> Self { + self.gauge_style = style; + self + } + + /// Whether to hide parts of the gauge/label if the inner label wouldn't fit. + pub fn hide_parts(mut self, hide_parts: LabelLimit) -> Self { + self.hide_parts = hide_parts; + self + } +} + +impl<'a> Widget for PipeGauge<'a> { + fn render(mut self, area: Rect, buf: &mut Buffer) { + buf.set_style(area, self.label_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 (col, row) = { + let inner_label_width = self + .inner_label + .as_ref() + .map(|l| l.width()) + .unwrap_or_default(); + + let start_label_width = self + .start_label + .as_ref() + .map(|l| l.width()) + .unwrap_or_default(); + + match self.hide_parts { + LabelLimit::StartLabel => { + let inner_label = self.inner_label.unwrap_or_else(|| Line::from("")); + let _ = buf.set_line( + gauge_area.left(), + gauge_area.top(), + &inner_label, + inner_label.width() as u16, + ); + + // Short circuit. + return; + } + LabelLimit::Auto(_) + if gauge_area.width < (inner_label_width + start_label_width + 1) as u16 => + { + let inner_label = self.inner_label.unwrap_or_else(|| Line::from("")); + let _ = buf.set_line( + gauge_area.left(), + gauge_area.top(), + &inner_label, + inner_label.width() as u16, + ); + + // Short circuit. + return; + } + _ => { + let start_label = self.start_label.unwrap_or_else(|| Line::from("")); + buf.set_line( + gauge_area.left(), + gauge_area.top(), + &start_label, + start_label.width() as u16, + ) + } + } + }; + + let end_label = self.inner_label.unwrap_or_else(|| Line::from("")); + match self.hide_parts { + LabelLimit::Bars => { + let _ = buf.set_line( + gauge_area + .right() + .saturating_sub(end_label.width() as u16 + 1), + row, + &end_label, + end_label.width() as u16, + ); + } + LabelLimit::Auto(width_limit) + if gauge_area.right().saturating_sub(col) < width_limit => + { + let _ = buf.set_line( + gauge_area + .right() + .saturating_sub(end_label.width() as u16 + 1), + row, + &end_label, + 1, + ); + } + LabelLimit::Auto(_) | LabelLimit::None => { + let (start, _) = buf.set_line(col, row, &Line::from("["), gauge_area.width); + if start >= gauge_area.right() { + return; + } + + let (end, _) = buf.set_line( + (gauge_area.x + gauge_area.width).saturating_sub(1), + row, + &Line::from("]"), + gauge_area.width, + ); + + let pipe_end = + start + (f64::from(end.saturating_sub(start)) * self.ratio).floor() as u16; + for col in start..pipe_end { + buf.get_mut(col, row).set_symbol("|").set_style(Style { + fg: self.gauge_style.fg, + bg: None, + add_modifier: self.gauge_style.add_modifier, + sub_modifier: self.gauge_style.sub_modifier, + underline_color: None, + }); + } + + if (end_label.width() as u16) < end.saturating_sub(start) { + let gauge_end = gauge_area + .right() + .saturating_sub(end_label.width() as u16 + 1); + buf.set_line(gauge_end, row, &end_label, end_label.width() as u16); + } + } + LabelLimit::StartLabel => unreachable!(), + } + } +} |