summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2022-10-13 10:17:26 -0400
committerGitHub <noreply@github.com>2022-10-13 10:17:26 -0400
commitb6a75db1b48e19ca85c00e88554b5132b543f1c7 (patch)
tree1c3195b4ed5dc4aab20f6f99e8826d2e55247a79 /src
parent436dadb683cc82b8d2310a3a015c66bf08398432 (diff)
refactor: switch to pipe gauge implementation for basic cpu + mem (#829)
* refactor: switch to pipe gauge implementation for basic cpu + mem * fix incorrect new basic cpu chunking scheme, revert to old one
Diffstat (limited to 'src')
-rw-r--r--src/canvas/widgets/cpu_basic.rs197
-rw-r--r--src/canvas/widgets/mem_basic.rs155
-rw-r--r--src/components.rs3
-rw-r--r--src/components/tui_widget.rs1
-rw-r--r--src/components/tui_widget/pipe_gauge.rs223
5 files changed, 353 insertions, 226 deletions
diff --git a/src/canvas/widgets/cpu_basic.rs b/src/canvas/widgets/cpu_basic.rs
index 4296b967..1ca75a90 100644
--- a/src/canvas/widgets/cpu_basic.rs
+++ b/src/canvas/widgets/cpu_basic.rs
@@ -1,8 +1,9 @@
use std::cmp::min;
use crate::{
- app::App,
- canvas::{drawing_utils::*, Painter},
+ app::{data_harvester::cpu::CpuDataType, App},
+ canvas::Painter,
+ components::tui_widget::pipe_gauge::{LabelLimit, PipeGauge},
constants::*,
data_conversion::CpuWidgetData,
};
@@ -11,11 +12,11 @@ use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
- text::{Span, Spans},
- widgets::{Block, Paragraph},
+ widgets::Block,
};
impl Painter {
+ /// Inspired by htop.
pub fn draw_basic_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
@@ -42,148 +43,84 @@ impl Painter {
);
}
- let num_cpus = cpu_data.len();
- let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
-
if draw_loc.height > 0 {
let remaining_height = usize::from(draw_loc.height);
const REQUIRED_COLUMNS: usize = 4;
- let chunk_vec =
+ let col_constraints =
vec![Constraint::Percentage((100 / REQUIRED_COLUMNS) as u16); REQUIRED_COLUMNS];
- let chunks = Layout::default()
- .constraints(chunk_vec)
+ let columns = Layout::default()
+ .constraints(col_constraints)
.direction(Direction::Horizontal)
.split(draw_loc);
- const CPU_NAME_SPACE: usize = 3;
- const BAR_BOUND_SPACE: usize = 2;
- const PERCENTAGE_SPACE: usize = 4;
- const MARGIN_SPACE: usize = 2;
-
- const COMBINED_SPACING: usize =
- CPU_NAME_SPACE + BAR_BOUND_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
- const REDUCED_SPACING: usize = CPU_NAME_SPACE + PERCENTAGE_SPACE + MARGIN_SPACE;
- let chunk_width: usize = chunks[0].width.into();
-
- // Inspired by htop.
- // We do +4 as if it's too few bars in the bar length, it's kinda pointless.
- let cpu_bars = if chunk_width >= COMBINED_SPACING + 4 {
- let bar_length = chunk_width - COMBINED_SPACING;
- cpu_data
- .iter()
- .enumerate()
- .filter_map(|(index, cpu)| match &cpu {
- CpuWidgetData::All => None,
- CpuWidgetData::Entry {
- data_type: _,
- data: _,
- last_entry,
- } => {
- let num_bars = calculate_basic_use_bars(*last_entry, bar_length);
- Some(format!(
- "{:3}[{}{}{:3.0}%]",
- if app_state.app_config_fields.show_average_cpu {
- if index == 0 {
- "AVG".to_string()
- } else {
- (index - 1).to_string()
- }
- } else {
- index.to_string()
- },
- "|".repeat(num_bars),
- " ".repeat(bar_length - num_bars),
- last_entry.round(),
- ))
- }
- })
- .collect::<Vec<_>>()
- } else if chunk_width >= REDUCED_SPACING {
- cpu_data
- .iter()
- .enumerate()
- .filter_map(|(index, cpu)| match &cpu {
- CpuWidgetData::All => None,
- CpuWidgetData::Entry {
- data_type: _,
- data: _,
- last_entry,
- } => Some(format!(
- "{:3} {:3.0}%",
- if app_state.app_config_fields.show_average_cpu {
- if index == 0 {
- "AVG".to_string()
- } else {
- (index - 1).to_string()
- }
- } else {
- index.to_string()
- },
- last_entry.round(),
- )),
- })
- .collect::<Vec<_>>()
- } else {
- cpu_data
- .iter()
- .filter_map(|cpu| match &cpu {
- CpuWidgetData::All => None,
- CpuWidgetData::Entry {
- data_type: _,
- data: _,
- last_entry,
- } => Some(format!("{:3.0}%", last_entry.round())),
- })
- .collect::<Vec<_>>()
- };
-
- let mut row_counter = num_cpus;
- let mut start_index = 0;
- for (itx, chunk) in chunks.iter().enumerate() {
- // Explicitly check... don't want an accidental DBZ or underflow, this ensures
- // to_divide is > 0
+ let mut gauge_info = cpu_data.iter().map(|cpu| match cpu {
+ CpuWidgetData::All => unreachable!(),
+ CpuWidgetData::Entry {
+ data_type,
+ data: _,
+ last_entry,
+ } => {
+ let (outer, style) = match data_type {
+ CpuDataType::Avg => ("AVG".to_string(), self.colours.avg_colour_style),
+ CpuDataType::Cpu(index) => (
+ format!("{index:<3}",),
+ self.colours.cpu_colour_styles
+ [index % self.colours.cpu_colour_styles.len()],
+ ),
+ };
+ let inner = format!("{:>3.0}%", last_entry.round());
+ let ratio = last_entry / 100.0;
+
+ (outer, inner, ratio, style)
+ }
+ });
+
+ // Very ugly way to sync the gauge limit across all gauges.
+ let hide_parts = columns
+ .get(0)
+ .map(|col| {
+ if col.width >= 12 {
+ LabelLimit::None
+ } else if col.width >= 10 {
+ LabelLimit::Bars
+ } else {
+ LabelLimit::StartLabel
+ }
+ })
+ .unwrap_or_default();
+
+ let num_entries = cpu_data.len();
+ let mut row_counter = num_entries;
+ for (itx, column) in columns.into_iter().enumerate() {
if REQUIRED_COLUMNS > itx {
let to_divide = REQUIRED_COLUMNS - itx;
- let how_many_cpus = min(
+ let num_taken = min(
remaining_height,
(row_counter / to_divide)
+ (if row_counter % to_divide == 0 { 0 } else { 1 }),
);
- row_counter -= how_many_cpus;
- let end_index = min(start_index + how_many_cpus, num_cpus);
+ row_counter -= num_taken;
+ let chunk = (&mut gauge_info).take(num_taken);
- let cpu_column = (start_index..end_index)
- .map(|itx| {
- Spans::from(Span {
- content: (&cpu_bars[itx]).into(),
- style: if show_avg_cpu {
- if itx == 0 {
- self.colours.avg_colour_style
- } else {
- self.colours.cpu_colour_styles
- [(itx - 1) % self.colours.cpu_colour_styles.len()]
- }
- } else {
- self.colours.cpu_colour_styles
- [itx % self.colours.cpu_colour_styles.len()]
- },
- })
- })
- .collect::<Vec<_>>();
-
- start_index += how_many_cpus;
-
- let margined_loc = Layout::default()
- .direction(Direction::Horizontal)
- .constraints([Constraint::Percentage(100)])
+ let rows = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(vec![Constraint::Length(1); remaining_height])
.horizontal_margin(1)
- .split(*chunk)[0];
-
- f.render_widget(
- Paragraph::new(cpu_column).block(Block::default()),
- margined_loc,
- );
+ .split(column);
+
+ for ((start_label, inner_label, ratio, style), row) in chunk.zip(rows) {
+ f.render_widget(
+ PipeGauge::default()
+ .gauge_style(style)
+ .label_style(style)
+ .inner_label(inner_label)
+ .start_label(start_label)
+ .ratio(ratio)
+ .hide_parts(hide_parts),
+ row,
+ );
+ }
}
}
}
diff --git a/src/canvas/widgets/mem_basic.rs b/src/canvas/widgets/mem_basic.rs
index 60f35f09..1d3c9969 100644
--- a/src/canvas/widgets/mem_basic.rs
+++ b/src/canvas/widgets/mem_basic.rs
@@ -1,16 +1,12 @@
use crate::{
- app::App,
- canvas::{drawing_utils::*, Painter},
- constants::*,
+ app::App, canvas::Painter, components::tui_widget::pipe_gauge::PipeGauge, constants::*,
};
use tui::{
backend::Backend,
- layout::{Constraint, Layout, Rect},
+ layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
- text::Span,
- text::Spans,
- widgets::{Block, Paragraph},
+ widgets::Block,
};
impl Painter {
@@ -21,7 +17,18 @@ impl Painter {
let swap_data: &[(f64, f64)] = &app_state.converted_data.swap_data;
let margined_loc = Layout::default()
- .constraints([Constraint::Percentage(100)])
+ .constraints({
+ #[cfg(feature = "zfs")]
+ {
+ [Constraint::Length(1); 3]
+ }
+
+ #[cfg(not(feature = "zfs"))]
+ {
+ [Constraint::Length(1); 2]
+ }
+ })
+ .direction(Direction::Vertical)
.horizontal_margin(1)
.split(draw_loc);
@@ -34,118 +41,78 @@ impl Painter {
);
}
- let ram_use_percentage = if let Some(mem) = mem_data.last() {
- mem.1
+ let ram_ratio = if let Some(mem) = mem_data.last() {
+ mem.1 / 100.0
} else {
0.0
};
- let swap_use_percentage = if let Some(swap) = swap_data.last() {
- swap.1
+ let swap_ratio = if let Some(swap) = swap_data.last() {
+ swap.1 / 100.0
} else {
0.0
};
const EMPTY_MEMORY_FRAC_STRING: &str = "0.0B/0.0B";
- let trimmed_memory_frac =
- if let Some((_label_percent, label_frac)) = &app_state.converted_data.mem_labels {
+ let memory_fraction_label =
+ if let Some((_, label_frac)) = &app_state.converted_data.mem_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
- let trimmed_swap_frac =
- if let Some((_label_percent, label_frac)) = &app_state.converted_data.swap_labels {
+ let swap_fraction_label =
+ if let Some((_, label_frac)) = &app_state.converted_data.swap_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
- // +7 due to 3 + 2 + 2 columns for the name & space + bar bounds + margin spacing
- // Then + length of fraction
- let ram_bar_length =
- usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_memory_frac.len());
- let swap_bar_length =
- usize::from(draw_loc.width.saturating_sub(7)).saturating_sub(trimmed_swap_frac.len());
+ f.render_widget(
+ PipeGauge::default()
+ .ratio(ram_ratio)
+ .start_label("RAM")
+ .inner_label(memory_fraction_label)
+ .label_style(self.colours.ram_style)
+ .gauge_style(self.colours.ram_style),
+ margined_loc[0],
+ );
- let num_bars_ram = calculate_basic_use_bars(ram_use_percentage, ram_bar_length);
- let num_bars_swap = calculate_basic_use_bars(swap_use_percentage, swap_bar_length);
- // TODO: Use different styling for the frac.
- let mem_label = if app_state.basic_mode_use_percent {
- format!(
- "RAM[{}{}{:3.0}%]\n",
- "|".repeat(num_bars_ram),
- " ".repeat(ram_bar_length - num_bars_ram + trimmed_memory_frac.len() - 4),
- ram_use_percentage.round()
- )
- } else {
- format!(
- "RAM[{}{}{}]\n",
- "|".repeat(num_bars_ram),
- " ".repeat(ram_bar_length - num_bars_ram),
- trimmed_memory_frac
- )
- };
- let swap_label = if app_state.basic_mode_use_percent {
- format!(
- "SWP[{}{}{:3.0}%]",
- "|".repeat(num_bars_swap),
- " ".repeat(swap_bar_length - num_bars_swap + trimmed_swap_frac.len() - 4),
- swap_use_percentage.round()
- )
- } else {
- format!(
- "SWP[{}{}{}]",
- "|".repeat(num_bars_swap),
- " ".repeat(swap_bar_length - num_bars_swap),
- trimmed_swap_frac
- )
- };
+ f.render_widget(
+ PipeGauge::default()
+ .ratio(swap_ratio)
+ .start_label("SWP")
+ .inner_label(swap_fraction_label)
+ .label_style(self.colours.swap_style)
+ .gauge_style(self.colours.swap_style),
+ margined_loc[1],
+ );
- let mem_text = vec![
- Spans::from(Span::styled(mem_label, self.colours.ram_style)),
- Spans::from(Span::styled(swap_label, self.colours.swap_style)),
- #[cfg(feature = "zfs")]
- {
- let arc_data: &[(f64, f64)] = &app_state.converted_data.arc_data;
- let arc_use_percentage = if let Some(arc) = arc_data.last() {
- arc.1
- } else {
- 0.0
- };
- let trimmed_arc_frac = if let Some((_label_percent, label_frac)) =
- &app_state.converted_data.arc_labels
- {
+ #[cfg(feature = "zfs")]
+ {
+ let arc_data: &[(f64, f64)] = &app_state.converted_data.arc_data;
+ let arc_ratio = if let Some(arc) = arc_data.last() {
+ arc.1 / 100.0
+ } else {
+ 0.0
+ };
+ let arc_fraction_label =
+ if let Some((_, label_frac)) = &app_state.converted_data.arc_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
- let arc_bar_length = usize::from(draw_loc.width.saturating_sub(7))
- .saturating_sub(trimmed_arc_frac.len());
- let num_bars_arc = calculate_basic_use_bars(arc_use_percentage, arc_bar_length);
- let arc_label = if app_state.basic_mode_use_percent {
- format!(
- "ARC[{}{}{:3.0}%]",
- "|".repeat(num_bars_arc),
- " ".repeat(arc_bar_length - num_bars_arc + trimmed_arc_frac.len() - 4),
- arc_use_percentage.round()
- )
- } else {
- format!(
- "ARC[{}{}{}]",
- "|".repeat(num_bars_arc),
- " ".repeat(arc_bar_length - num_bars_arc),
- trimmed_arc_frac
- )
- };
- Spans::from(Span::styled(arc_label, self.colours.arc_style))
- },
- ];
- f.render_widget(
- Paragraph::new(mem_text).block(Block::default()),
- margined_loc[0],
- );
+ f.render_widget(
+ PipeGauge::default()
+ .ratio(arc_ratio)
+ .start_label("ARC")
+ .inner_label(arc_fraction_label)
+ .label_style(self.colours.arc_style)
+ .gauge_style(self.colours.arc_style),
+ margined_loc[2],
+ );
+ }
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
diff --git a/src/components.rs b/src/components.rs
index 1c4c453c..c2ee5aae 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -1,4 +1,3 @@
-mod tui_widget;
-
pub mod data_table;
pub mod time_graph;
+pub mod tui_widget;
diff --git a/src/components/tui_widget.rs b/src/components/tui_widget.rs
index a4e0978a..93c0509d 100644
--- a/src/components/tui_widget.rs
+++ b/src/components/tui_widget.rs
@@ -1 +1,2 @@
+pub mod pipe_gauge;
pub mod time_chart;
diff --git a/src/components/tui_widget/pipe_gauge.rs b/src/components/tui_widget/pipe_gauge.rs
new file mode 100644
index 00000000..aa933185
--- /dev/null
+++ b/src/components/tui_widget/pipe_gauge.rs
@@ -0,0 +1,223 @@
+use tui::{
+ buffer::Buffer,
+ layout::Rect,
+ style::Style,
+ text::Spans,
+ 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<Spans<'a>>,
+ inner_label: Option<Spans<'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<Spans<'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<Spans<'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(|| Spans::from(""));
+ let _ = buf.set_spans(
+ 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(|| Spans::from(""));
+ let _ = buf.set_spans(
+ 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(|| Spans::from(""));
+ buf.set_spans(
+ gauge_area.left(),
+ gauge_area.top(),
+ &start_label,
+ start_label.width() as u16,
+ )
+ }
+ }
+ };
+
+ let end_label = self.inner_label.unwrap_or_else(|| Spans::from(""));
+ match self.hide_parts {
+ LabelLimit::Bars => {
+ let _ = buf.set_spans(
+ 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_spans(
+ gauge_area
+ .right()
+ .saturating_sub(end_label.width() as u16 + 1),
+ row,
+ &end_label,
+ 1,
+ );
+ }
+ LabelLimit::Auto(_) | LabelLimit::None => {
+ let (start, _) = buf.set_spans(col, row, &Spans::from("["), gauge_area.width);
+ if start >= gauge_area.right() {
+ return;
+ }
+
+ let (end, _) = buf.set_spans(
+ (gauge_area.x + gauge_area.width).saturating_sub(1),
+ row,
+ &Spans::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,
+ });
+ }
+
+ 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_spans(gauge_end, row, &end_label, end_label.width() as u16);
+ }
+ }
+ LabelLimit::StartLabel => unreachable!(),
+ }
+ }
+}