summaryrefslogtreecommitdiffstats
path: root/src/app
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-08-28 04:15:36 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2021-08-28 20:09:00 -0400
commit2bff04d8a4080be1af48e10d13d9d0b9dc9e1e1e (patch)
tree174ab7c00f9fa9010d07406bfbb214baaea40a1a /src/app
parentb72e76aa71ef6fc4790ffd5b47ea1fb7c07bd464 (diff)
refactor: port over graph widgets
Things working as of now: - Actually drawing - Interpolation - Styling
Diffstat (limited to 'src/app')
-rw-r--r--src/app/layout_manager.rs40
-rw-r--r--src/app/widgets.rs16
-rw-r--r--src/app/widgets/base/scrollable.rs6
-rw-r--r--src/app/widgets/base/sort_text_table.rs26
-rw-r--r--src/app/widgets/base/text_table.rs154
-rw-r--r--src/app/widgets/base/time_graph.rs141
-rw-r--r--src/app/widgets/battery.rs10
-rw-r--r--src/app/widgets/cpu.rs203
-rw-r--r--src/app/widgets/custom_tui/custom_legend_chart.rs335
-rw-r--r--src/app/widgets/disk.rs35
-rw-r--r--src/app/widgets/mem.rs79
-rw-r--r--src/app/widgets/net.rs542
-rw-r--r--src/app/widgets/process.rs63
-rw-r--r--src/app/widgets/temp.rs47
-rw-r--r--src/app/widgets/tui_widgets.rs2
-rw-r--r--src/app/widgets/tui_widgets/custom_legend_chart.rs596
16 files changed, 2105 insertions, 190 deletions
diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs
index 36a4e5d6..d2927a35 100644
--- a/src/app/layout_manager.rs
+++ b/src/app/layout_manager.rs
@@ -1,8 +1,5 @@
use crate::{
- app::{
- sort_text_table::SortableColumn, DiskTable, MemGraph, NetGraph, OldNetGraph,
- ProcessManager, TempTable,
- },
+ app::{DiskTable, MemGraph, NetGraph, OldNetGraph, ProcessManager, TempTable},
error::{BottomError, Result},
options::layout_options::{Row, RowChildren},
};
@@ -16,7 +13,7 @@ use crate::app::widgets::Widget;
use crate::constants::DEFAULT_WIDGET_ID;
use super::{
- event::SelectionAction, CpuGraph, SortableTextTable, TimeGraph, TmpBottomWidget, UsedWidgets,
+ event::SelectionAction, AppConfigFields, CpuGraph, TimeGraph, TmpBottomWidget, UsedWidgets,
};
/// Represents a more usable representation of the layout, derived from the
@@ -1051,44 +1048,43 @@ pub struct LayoutCreationOutput {
// FIXME: This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this!
pub fn create_layout_tree(
rows: &[Row], process_defaults: crate::options::ProcessDefaults,
- app_config_fields: &super::AppConfigFields,
+ app_config_fields: &AppConfigFields,
) -> Result<LayoutCreationOutput> {
fn add_widget_to_map(
widget_lookup_map: &mut FxHashMap<NodeId, TmpBottomWidget>, widget_type: BottomWidgetType,
widget_id: NodeId, process_defaults: &crate::options::ProcessDefaults,
- app_config_fields: &super::AppConfigFields,
+ app_config_fields: &AppConfigFields,
) -> Result<()> {
match widget_type {
BottomWidgetType::Cpu => {
- let graph = TimeGraph::from_config(app_config_fields);
- let legend = SortableTextTable::new(vec![
- SortableColumn::new_flex("CPU".into(), None, false, 0.5),
- SortableColumn::new_flex("Use%".into(), None, false, 0.5),
- ]);
- let legend_position = super::CpuGraphLegendPosition::Right;
-
- widget_lookup_map.insert(
- widget_id,
- CpuGraph::new(graph, legend, legend_position).into(),
- );
+ widget_lookup_map
+ .insert(widget_id, CpuGraph::from_config(app_config_fields).into());
}
BottomWidgetType::Mem => {
let graph = TimeGraph::from_config(app_config_fields);
widget_lookup_map.insert(widget_id, MemGraph::new(graph).into());
}
BottomWidgetType::Net => {
- let graph = TimeGraph::from_config(app_config_fields);
if app_config_fields.use_old_network_legend {
- widget_lookup_map.insert(widget_id, OldNetGraph::new(graph).into());
+ widget_lookup_map.insert(
+ widget_id,
+ OldNetGraph::from_config(app_config_fields).into(),
+ );
} else {
- widget_lookup_map.insert(widget_id, NetGraph::new(graph).into());
+ widget_lookup_map
+ .insert(widget_id, NetGraph::from_config(app_config_fields).into());
}
}
BottomWidgetType::Proc => {
widget_lookup_map.insert(widget_id, ProcessManager::new(process_defaults).into());
}
BottomWidgetType::Temp => {
- widget_lookup_map.insert(widget_id, TempTable::default().into());
+ widget_lookup_map.insert(
+ widget_id,
+ TempTable::default()
+ .set_temp_type(app_config_fields.temperature_type.clone())
+ .into(),
+ );
}
BottomWidgetType::Disk => {
widget_lookup_map.insert(widget_id, DiskTable::default().into());
diff --git a/src/app/widgets.rs b/src/app/widgets.rs
index ad24eb00..34009939 100644
--- a/src/app/widgets.rs
+++ b/src/app/widgets.rs
@@ -9,10 +9,12 @@ use crate::{
event::{EventResult, SelectionAction},
layout_manager::BottomWidgetType,
},
- canvas::{DisplayableData, Painter},
+ canvas::Painter,
constants,
};
+mod tui_widgets;
+
pub mod base;
pub use base::*;
@@ -37,6 +39,8 @@ pub use self::battery::*;
pub mod temp;
pub use temp::*;
+use super::data_farmer::DataCollection;
+
/// A trait for things that are drawn with state.
#[enum_dispatch]
#[allow(unused_variables)]
@@ -75,9 +79,6 @@ pub trait Component {
#[enum_dispatch]
#[allow(unused_variables)]
pub trait Widget {
- /// Updates a [`Widget`] given some data. Defaults to doing nothing.
- fn update(&mut self) {}
-
/// Handles what to do when trying to respond to a widget selection movement to the left.
/// Defaults to just moving to the next-possible widget in that direction.
fn handle_widget_selection_left(&mut self) -> SelectionAction {
@@ -107,12 +108,13 @@ pub trait Widget {
/// Draws a [`Widget`]. Defaults to doing nothing.
fn draw<B: Backend>(
- &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, data: &DisplayableData,
- selected: bool,
+ &mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
) {
// TODO: Remove the default implementation in the future!
- // TODO: Do another pass on ALL of the draw code - currently it's just glue, it should eventually be done properly!
}
+
+ /// How a [`Widget`] updates its internal displayed data. Defaults to doing nothing.
+ fn update_data(&mut self, data_collection: &DataCollection) {}
}
/// The "main" widgets that are used by bottom to display information!
diff --git a/src/app/widgets/base/scrollable.rs b/src/app/widgets/base/scrollable.rs
index 4b5f395f..963839e0 100644
--- a/src/app/widgets/base/scrollable.rs
+++ b/src/app/widgets/base/scrollable.rs
@@ -61,7 +61,7 @@ impl Scrollable {
}
/// Returns the currently selected index of the [`Scrollable`].
- pub fn index(&self) -> usize {
+ pub fn current_index(&self) -> usize {
self.current_index
}
@@ -195,8 +195,8 @@ impl Scrollable {
self.num_items
}
- pub fn tui_state(&self) -> TableState {
- self.tui_state.clone()
+ pub fn tui_state(&mut self) -> &mut TableState {
+ &mut self.tui_state
}
}
diff --git a/src/app/widgets/base/sort_text_table.rs b/src/app/widgets/base/sort_text_table.rs
index 0c282d49..0f99cd4c 100644
--- a/src/app/widgets/base/sort_text_table.rs
+++ b/src/app/widgets/base/sort_text_table.rs
@@ -2,13 +2,17 @@ use std::borrow::Cow;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use tui::{
+ backend::Backend,
layout::Rect,
- widgets::{Table, TableState},
+ widgets::{Block, Table, TableState},
};
-use crate::app::{event::EventResult, Component, TextTable};
+use crate::{
+ app::{event::EventResult, Component, TextTable},
+ canvas::Painter,
+};
-use super::text_table::{DesiredColumnWidth, SimpleColumn, TableColumn};
+use super::text_table::{DesiredColumnWidth, SimpleColumn, TableColumn, TextTableData};
fn get_shortcut_name(e: &KeyEvent) -> String {
let modifier = if e.modifiers.is_empty() {
@@ -182,6 +186,10 @@ impl SortableTextTable {
self
}
+ pub fn current_index(&self) -> usize {
+ self.table.current_index()
+ }
+
fn set_sort_index(&mut self, new_index: usize) {
if new_index == self.sort_index {
if let Some(column) = self.table.columns.get_mut(self.sort_index) {
@@ -218,6 +226,18 @@ impl SortableTextTable {
}
}
+ /// Draws a [`Table`] given the [`TextTable`] and the given data.
+ ///
+ /// Note if the number of columns don't match in the [`TextTable`] and data,
+ /// it will only create as many columns as it can grab data from both sources from.
+ pub fn draw_tui_table<B: Backend>(
+ &mut self, painter: &Painter, f: &mut tui::Frame<'_, B>, data: &TextTableData,
+ block: Block<'_>, block_area: Rect, show_selected_entry: bool,
+ ) {
+ self.table
+ .draw_tui_table(painter, f, data, block, block_area, show_selected_entry);
+ }
+
/// Creates a [`Table`] representing the sort list.
pub fn create_sort_list(&mut self) -> (Table<'_>, TableState) {
todo!()
diff --git a/src/app/widgets/base/text_table.rs b/src/app/widgets/base/text_table.rs
index 0edda286..3ce3092d 100644
--- a/src/app/widgets/base/text_table.rs
+++ b/src/app/widgets/base/text_table.rs
@@ -5,9 +5,12 @@ use std::{
use crossterm::event::{KeyEvent, MouseEvent};
use tui::{
+ backend::Backend,
layout::{Constraint, Rect},
+ style::Style,
text::Text,
- widgets::{Table, TableState},
+ widgets::{Block, Table},
+ Frame,
};
use unicode_segmentation::UnicodeSegmentation;
@@ -36,6 +39,8 @@ pub trait TableColumn {
fn set_x_bounds(&mut self, x_bounds: Option<(u16, u16)>);
}
+pub type TextTableData = Vec<Vec<(Cow<'static, str>, Option<Cow<'static, str>>, Option<Style>)>>;
+
/// A [`SimpleColumn`] represents some column in a [`TextTable`].
#[derive(Debug)]
pub struct SimpleColumn {
@@ -128,6 +133,9 @@ where
/// Whether we draw columns from left-to-right.
pub left_to_right: bool,
+
+ /// Whether to enable selection.
+ pub selectable: bool,
}
impl<C> TextTable<C>
@@ -142,6 +150,7 @@ where
show_gap: true,
bounds: Rect::default(),
left_to_right: true,
+ selectable: true,
}
}
@@ -155,6 +164,11 @@ where
self
}
+ pub fn unselectable(mut self) -> Self {
+ self.selectable = false;
+ self
+ }
+
pub fn displayed_column_names(&self) -> Vec<Cow<'static, str>> {
self.columns
.iter()
@@ -172,8 +186,12 @@ where
}
}
+ pub fn current_index(&self) -> usize {
+ self.scrollable.current_index()
+ }
+
pub fn get_desired_column_widths(
- columns: &[C], data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
+ columns: &[C], data: &TextTableData,
) -> Vec<DesiredColumnWidth> {
columns
.iter()
@@ -183,22 +201,22 @@ where
let max_len = data
.iter()
.filter_map(|c| c.get(column_index))
- .max_by(|(x, short_x), (y, short_y)| {
- let x = if let Some(short_x) = short_x {
- short_x
+ .max_by(|(a, short_a, _a_style), (b, short_b, _b_style)| {
+ let a_len = if let Some(short_a) = short_a {
+ short_a.len()
} else {
- x
+ a.len()
};
- let y = if let Some(short_y) = short_y {
- short_y
+ let b_len = if let Some(short_b) = short_b {
+ short_b.len()
} else {
- y
+ b.len()
};
- x.len().cmp(&y.len())
+ a_len.cmp(&b_len)
})
- .map(|(s, _)| s.len())
+ .map(|(longest_data_str, _, _)| longest_data_str.len())
.unwrap_or(0) as u16;
DesiredColumnWidth::Hard(max(max_len, *width))
@@ -211,9 +229,7 @@ where
.collect::<Vec<_>>()
}
- fn get_cache(
- &mut self, area: Rect, data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
- ) -> Vec<u16> {
+ fn get_cache(&mut self, area: Rect, data: &TextTableData) -> Vec<u16> {
fn calculate_column_widths(
left_to_right: bool, mut desired_widths: Vec<DesiredColumnWidth>, total_width: u16,
) -> Vec<u16> {
@@ -326,37 +342,37 @@ where
}
}
- /// Creates a [`Table`] given the [`TextTable`] and the given data, along with its
- /// widths (because for some reason a [`Table`] only borrows the constraints...?)
- /// and [`TableState`] (so we know which row is selected).
+ /// Draws a [`Table`] given the [`TextTable`] and the given data.
///
/// Note if the number of columns don't match in the [`TextTable`] and data,
/// it will only create as many columns as it can grab data from both sources from.
- pub fn create_draw_table(
- &mut self, painter: &Painter, data: &[Vec<(Cow<'static, str>, Option<Cow<'static, str>>)>],
- area: Rect,
- ) -> (Table<'_>, Vec<Constraint>, TableState) {
+ pub fn draw_tui_table<B: Backend>(
+ &mut self, painter: &Painter, f: &mut Frame<'_, B>, data: &TextTableData, block: Block<'_>,
+ block_area: Rect, show_selected_entry: bool,
+ ) {
use tui::widgets::Row;
- let table_gap = if !self.show_gap || area.height < TABLE_GAP_HEIGHT_LIMIT {
+ let inner_area = block.inner(block_area);
+
+ let table_gap = if !self.show_gap || inner_area.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
1
};
self.update_num_items(data.len());
- self.set_bounds(area);
+ self.set_bounds(inner_area);
let table_extras = 1 + table_gap;
- let scrollable_height = area.height.saturating_sub(table_extras);
+ let scrollable_height = inner_area.height.saturating_sub(table_extras);
self.scrollable.set_bounds(Rect::new(
- area.x,
- area.y + table_extras,
- area.width,
+ inner_area.x,
+ inner_area.y + table_extras,
+ inner_area.width,
scrollable_height,
));
// Calculate widths first, since we need them later.
- let calculated_widths = self.get_cache(area, data);
+ let calculated_widths = self.get_cache(inner_area, data);
let widths = calculated_widths
.iter()
.map(|column| Constraint::Length(*column))
@@ -373,25 +389,28 @@ where
&data[start..end]
};
let rows = data_slice.iter().map(|row| {
- Row::new(
- row.iter()
- .zip(&calculated_widths)
- .map(|((text, shrunk_text), width)| {
- let width = *width as usize;
- let graphemes = UnicodeSegmentation::graphemes(text.as_ref(), true)
- .collect::<Vec<&str>>();
- let grapheme_width = graphemes.len();
- if width < grapheme_width && width > 1 {
- if let Some(shrunk_text) = shrunk_text {
- Text::raw(shrunk_text.clone())
- } else {
- Text::raw(format!("{}…", graphemes[..(width - 1)].concat()))
- }
+ Row::new(row.iter().zip(&calculated_widths).map(
+ |((text, shrunk_text, opt_style), width)| {
+ let text_style = opt_style.unwrap_or(painter.colours.text_style);
+
+ let width = *width as usize;
+ let graphemes =
+ UnicodeSegmentation::graphemes(text.as_ref(), true).collect::<Vec<&str>>();
+ let grapheme_width = graphemes.len();
+ if width < grapheme_width && width > 1 {
+ if let Some(shrunk_text) = shrunk_text {
+ Text::styled(shrunk_text.clone(), text_style)
} else {
- Text::raw(text.to_owned())
+ Text::styled(
+ format!("{}…", graphemes[..(width - 1)].concat()),
+ text_style,
+ )
}
- }),
- )
+ } else {
+ Text::styled(text.to_owned(), text_style)
+ }
+ },
+ ))
});
// Now build up our headers...
@@ -399,21 +418,24 @@ where
.style(painter.colours.table_header_style)
.bottom_margin(table_gap);
- // And return tui-rs's [`TableState`].
- let tui_state = self.scrollable.tui_state();
-
- (
- Table::new(rows)
- .header(header)
- .style(painter.colours.text_style),
- widths,
- tui_state,
- )
- }
-
- /// Creates a [`Table`] representing the sort list.
- pub fn create_sort_list(&mut self) -> (Table<'_>, TableState) {
- todo!()
+ let table = Table::new(rows)
+ .header(header)
+ .style(painter.colours.text_style)
+ .highlight_style(if show_selected_entry {
+ painter.colours.currently_selected_text_style
+ } else {
+ painter.colours.text_style
+ });
+
+ if self.selectable {
+ f.render_stateful_widget(
+ table.block(block).widths(&widths),
+ block_area,
+ self.scrollable.tui_state(),
+ );
+ } else {
+ f.render_widget(table.block(block).widths(&widths), block_area);
+ }
}
}
@@ -422,11 +444,19 @@ where
C: TableColumn,
{
fn handle_key_event(&mut self, event: KeyEvent) -> EventResult {
- self.scrollable.handle_key_event(event)
+ if self.selectable {
+ self.scrollable.handle_key_event(event)
+ } else {
+ EventResult::NoRedraw
+ }
}
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
- self.scrollable.handle_mouse_event(event)
+ if self.selectable {
+ self.scrollable.handle_mouse_event(event)
+ } else {
+ EventResult::NoRedraw
+ }
}
fn bounds(&self) -> Rect {
diff --git a/src/app/widgets/base/time_graph.rs b/src/app/widgets/base/time_graph.rs
index a0054c38..7a3ddd16 100644
--- a/src/app/widgets/base/time_graph.rs
+++ b/src/app/widgets/base/time_graph.rs
@@ -1,10 +1,28 @@
-use std::time::{Duration, Instant};
+use std::{
+ borrow::Cow,
+ time::{Duration, Instant},
+};
-use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent};
-use tui::layout::Rect;
+use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
+use tui::{
+ backend::Backend,
+ layout::{Constraint, Rect},
+ style::Style,
+ symbols::Marker,
+ text::Span,
+ widgets::{Block, GraphType},
+};
use crate::{
- app::{event::EventResult, AppConfigFields, Component},
+ app::{
+ event::EventResult,
+ widgets::tui_widgets::{
+ custom_legend_chart::{Axis, Dataset},
+ TimeChart,
+ },
+ AppConfigFields, Component,
+ },
+ canvas::Painter,
constants::{AUTOHIDE_TIMEOUT_MILLISECONDS, STALE_MAX_MILLISECONDS, STALE_MIN_MILLISECONDS},
};
@@ -24,6 +42,7 @@ pub enum AutohideTimer {
},
}
+// TODO: [AUTOHIDE] Not a fan of how this is done, as this should really "trigger" a draw when it's done.
impl AutohideTimer {
fn start_display_timer(&mut self) {
match self {
@@ -57,6 +76,27 @@ impl AutohideTimer {
},
}
}
+
+ pub fn is_showing(&mut self) -> bool {
+ self.update_display_timer();
+ match self {
+ AutohideTimer::AlwaysShow => true,
+ AutohideTimer::AlwaysHide => false,
+ AutohideTimer::Enabled {
+ state,
+ show_duration: _,
+ } => match state {
+ AutohideTimerState::Hidden => false,
+ AutohideTimerState::Running(_) => true,
+ },
+ }
+ }
+}
+
+pub struct TimeGraphData<'d> {
+ pub data: &'d [(f64, f64)],
+ pub label: Option<Cow<'static, str>>,
+ pub style: Style,
}
/// A graph widget with controllable time ranges along the x-axis.
@@ -71,13 +111,15 @@ pub struct TimeGraph {
time_interval: u64,
bounds: Rect,
+
+ use_dot: bool,
}
impl TimeGraph {
/// Creates a new [`TimeGraph`]. All time values are in milliseconds.
pub fn new(
start_value: u64, autohide_timer: AutohideTimer, min_duration: u64, max_duration: u64,
- time_interval: u64,
+ time_interval: u64, use_dot: bool,
) -> Self {
Self {
current_display_time: start_value,
@@ -87,6 +129,7 @@ impl TimeGraph {
max_duration,
time_interval,
bounds: Rect::default(),
+ use_dot,
}
}
@@ -107,6 +150,7 @@ impl TimeGraph {
STALE_MIN_MILLISECONDS,
STALE_MAX_MILLISECONDS,
app_config_fields.time_interval,
+ app_config_fields.use_dot,
)
}
@@ -165,6 +209,89 @@ impl TimeGraph {
EventResult::Redraw
}
}
+
+ fn get_x_axis_labels(&self, painter: &Painter) -> Vec<Span<'_>> {
+ vec![
+ Span::styled(
+ format!("{}s", self.current_display_time / 1000),
+ painter.colours.graph_style,
+ ),
+ Span::styled("0s", painter.colours.graph_style),
+ ]
+ }
+
+ pub fn get_current_display_time(&self) -> u64 {
+ self.current_display_time
+ }
+
+ /// Creates a [`Chart`].
+ ///
+ /// The `reverse_order` parameter is mostly used for cases where you want the first entry to be drawn on
+ /// top - note that this will also reverse the naturally generated legend, if shown!
+ pub fn draw_tui_chart<B: Backend>(
+ &mut self, painter: &Painter, f: &mut tui::Frame<'_, B>, data: &'_ [TimeGraphData<'_>],
+ y_bound_labels: &[Cow<'static, str>], y_bounds: [f64; 2], reverse_order: bool,
+ block: Block<'_>, block_area: Rect,
+ ) {
+ let inner_area = block.inner(block_area);
+
+ self.set_bounds(inner_area);
+
+ let time_start = -(self.current_display_time as f64);
+ let x_axis = {
+ let x_axis = Axis::default()
+ .bounds([time_start, 0.0])
+ .style(painter.colours.graph_style);
+ if self.autohide_timer.is_showing() {
+ x_axis.labels(self.get_x_axis_labels(painter))
+ } else {
+ x_axis
+ }
+ };
+ let y_axis = Axis::default()
+ .bounds(y_bounds)
+ .style(painter.colours.graph_style)
+ .labels(
+ y_bound_labels
+ .into_iter()
+ .map(|label| Span::styled(label.clone(), painter.colours.graph_style))
+ .collect(),
+ );
+
+ let mut datasets: Vec<Dataset<'_>> = data
+ .iter()
+ .map(|time_graph_data| {
+ let mut dataset = Dataset::default()
+ .data(time_graph_data.data)
+ .style(time_graph_data.style)
+ .marker(if self.use_dot {
+ Marker::Dot
+ } else {
+ Marker::Braille
+ })
+ .graph_type(GraphType::Line);
+
+ if let Some(label) = &time_graph_data.label {
+ dataset = dataset.name(label.clone());
+ }
+
+ dataset
+ })
+ .collect();
+
+ if reverse_order {
+ datasets.reverse();
+ }
+
+ let chart = TimeChart::new(datasets)
+ .x_axis(x_axis)
+ .y_axis(y_axis)
+ .style(painter.colours.graph_style)
+ .legend_style(painter.colours.graph_style)
+ .hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4)));
+
+ f.render_widget(chart.block(block), block_area);
+ }
}
impl Component for TimeGraph {
@@ -183,8 +310,8 @@ impl Component for TimeGraph {
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
match event.kind {
- crossterm::event::MouseEventKind::ScrollDown => self.zoom_out(),
- crossterm::event::MouseEventKind::ScrollUp => self.zoom_in(),
+ MouseEventKind::ScrollDown => self.zoom_out(),
+ MouseEventKind::ScrollUp => self.zoom_in(),
_ => EventResult::NoRedraw,
}
}
diff --git a/src/app/widgets/battery.rs b/src/app/widgets/battery.rs
index 5ef6742a..e12e2fe6 100644
--- a/src/app/widgets/battery.rs
+++ b/src/app/widgets/battery.rs
@@ -2,6 +2,11 @@ use std::collections::HashMap;
use tui::layout::Rect;
+use crate::{
+ app::data_farmer::DataCollection,
+ data_conversion::{convert_battery_harvest, ConvertedBatteryData},
+};
+
use super::{Component, Widget};
#[derive(Default)]
@@ -36,6 +41,7 @@ pub struct BatteryTable {
bounds: Rect,
selected_index: usize,
batteries: Vec<String>,
+ battery_data: Vec<ConvertedBatteryData>,
}
impl BatteryTable {
@@ -70,4 +76,8 @@ impl Widget for BatteryTable {
fn get_pretty_name(&self) -> &'static str {
"Battery"
}
+
+ fn update_data(&mut self, data_collection: &DataCollection) {
+ self.battery_data = convert_battery_harvest(data_collection);
+ }
}
diff --git a/src/app/widgets/cpu.rs b/src/app/widgets/cpu.rs
index 15fe9efe..40c2fa89 100644
--- a/src/app/widgets/cpu.rs
+++ b/src/app/widgets/cpu.rs
@@ -1,9 +1,20 @@
-use std::{collections::HashMap, time::Instant};
+use std::{borrow::Cow, collections::HashMap, time::Instant};
use crossterm::event::{KeyEvent, MouseEvent};
-use tui::layout::Rect;
+use tui::{
+ backend::Backend,
+ layout::{Constraint, Direction, Layout, Rect},
+ widgets::{Block, Borders},
+};
-use crate::app::event::EventResult;
+use crate::{
+ app::{
+ event::EventResult, sort_text_table::SortableColumn, time_graph::TimeGraphData,
+ AppConfigFields, DataCollection,
+ },
+ canvas::Painter,
+ data_conversion::{convert_cpu_data_points, ConvertedCpuData},
+};
use super::{
AppScrollWidgetState, CanvasTableWidthState, Component, SortableTextTable, TimeGraph, Widget,
@@ -70,23 +81,40 @@ pub enum CpuGraphLegendPosition {
pub struct CpuGraph {
graph: TimeGraph,
legend: SortableTextTable,
- pub legend_position: CpuGraphLegendPosition,
+ legend_position: CpuGraphLegendPosition,
+ showing_avg: bool,
bounds: Rect,
selected: CpuGraphSelection,
+
+ display_data: Vec<ConvertedCpuData>,
+ load_avg_data: [f32; 3],
}
impl CpuGraph {
- /// Creates a new [`CpuGraph`].
- pub fn new(
- graph: TimeGraph, legend: SortableTextTable, legend_position: CpuGraphLegendPosition,
- ) -> Self {
+ /// Creates a new [`CpuGraph`] from a config.
+ pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
+ let graph = TimeGraph::from_config(app_config_fields);
+ let legend = SortableTextTable::new(vec![
+ SortableColumn::new_flex("CPU".into(), None, false, 0.5),
+ SortableColumn::new_flex("Use%".into(), None, false, 0.5),
+ ]);
+ let legend_position = if app_config_fields.left_legend {
+ CpuGraphLegendPosition::Left
+ } else {
+ CpuGraphLegendPosition::Right
+ };
+ let showing_avg = app_config_fields.show_average_cpu;
+
Self {
graph,
legend,
legend_position,
+ showing_avg,
bounds: Rect::default(),
selected: CpuGraphSelection::None,
+ display_data: Default::default(),
+ load_avg_data: [0.0; 3],
}
}
}
@@ -102,11 +130,21 @@ impl Component for CpuGraph {
fn handle_mouse_event(&mut self, event: MouseEvent) -> EventResult {
if self.graph.does_intersect_mouse(&event) {
- self.selected = CpuGraphSelection::Graph;
- self.graph.handle_mouse_event(event)
+ if let CpuGraphSelection::Graph = self.selected {
+ self.graph.handle_mouse_event(event)
+ } else {
+ self.selected = CpuGraphSelection::Graph;
+ self.graph.handle_mouse_event(event);
+ EventResult::Redraw
+ }
} else if self.legend.does_intersect_mouse(&event) {
- self.selected = CpuGraphSelection::Legend;
- self.legend.handle_mouse_event(event)
+ if let CpuGraphSelection::Legend = self.selected {
+ self.legend.handle_mouse_event(event)
+ } else {
+ self.selected = CpuGraphSelection::Legend;
+ self.legend.handle_mouse_event(event);
+ EventResult::Redraw
+ }
} else {
EventResult::NoRedraw
}
@@ -125,4 +163,145 @@ impl Widget for CpuGraph {
fn get_pretty_name(&self) -> &'static str {
"CPU"
}
+
+ fn draw<B: Backend>(
+ &mut self, painter: &Painter, f: &mut tui::Frame<'_, B>, area: Rect, selected: bool,
+ ) {
+ let constraints = match self.legend_position {
+ CpuGraphLegendPosition::Left => {
+ [Constraint::Percentage(15), Constraint::Percentage(85)]
+ }
+ CpuGraphLegendPosition::Right => {
+ [Constraint::Percentage(85), Constraint::Percentage(15)]
+ }
+ };
+
+ let split_area = Layout::default()
+