diff --git a/Cargo.lock b/Cargo.lock index 0cead99b..ad116773 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,7 @@ dependencies = [ "dirs", "enum_dispatch", "fern", + "float-ord", "futures", "futures-timer", "fxhash", @@ -584,6 +585,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "futures" version = "0.3.14" diff --git a/Cargo.toml b/Cargo.toml index d6380949..38432ab3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ clap = "2.33" cfg-if = "1.0" dirs = "3.0.2" enum_dispatch = "0.3.7" +float-ord = "0.3.2" futures = "0.3.14" futures-timer = "3.0.2" fxhash = "0.2.1" diff --git a/src/app.rs b/src/app.rs index 764ff92c..aee57db8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,7 +19,7 @@ use indextree::{Arena, NodeId}; use unicode_segmentation::GraphemeCursor; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; -use data_farmer::*; +pub use data_farmer::*; use data_harvester::{processes, temperature}; pub use filter::*; use layout_manager::*; @@ -28,9 +28,7 @@ pub use widgets::*; use crate::{ canvas, constants::{self, MAX_SIGNAL}, - data_conversion::*, units::data_units::DataUnit, - update_final_process_list, utils::error::{BottomError, Result}, BottomEvent, Pid, }; @@ -367,7 +365,10 @@ impl AppState { self.data_collection.eat_data(new_data); if !self.is_frozen { - self.convert_data(); + let data_collection = &self.data_collection; + self.widget_lookup_map + .iter_mut() + .for_each(|(_id, widget)| widget.update_data(data_collection)); EventResult::Redraw } else { @@ -395,89 +396,6 @@ impl AppState { } } - fn convert_data(&mut self) { - // TODO: Probably refactor this. - - // Network - if self.used_widgets.use_net { - let network_data = convert_network_data_points( - &self.data_collection, - false, - self.app_config_fields.use_basic_mode - || self.app_config_fields.use_old_network_legend, - &self.app_config_fields.network_scale_type, - &self.app_config_fields.network_unit_type, - self.app_config_fields.network_use_binary_prefix, - ); - self.canvas_data.network_data_rx = network_data.rx; - self.canvas_data.network_data_tx = network_data.tx; - self.canvas_data.rx_display = network_data.rx_display; - self.canvas_data.tx_display = network_data.tx_display; - if let Some(total_rx_display) = network_data.total_rx_display { - self.canvas_data.total_rx_display = total_rx_display; - } - if let Some(total_tx_display) = network_data.total_tx_display { - self.canvas_data.total_tx_display = total_tx_display; - } - } - - // Disk - if self.used_widgets.use_disk { - self.canvas_data.disk_data = convert_disk_row(&self.data_collection); - } - - // Temperatures - if self.used_widgets.use_temp { - self.canvas_data.temp_sensor_data = convert_temp_row(&self); - } - - // Memory - if self.used_widgets.use_mem { - self.canvas_data.mem_data = convert_mem_data_points(&self.data_collection, false); - self.canvas_data.swap_data = convert_swap_data_points(&self.data_collection, false); - let (memory_labels, swap_labels) = convert_mem_labels(&self.data_collection); - - self.canvas_data.mem_labels = memory_labels; - self.canvas_data.swap_labels = swap_labels; - } - - if self.used_widgets.use_cpu { - // CPU - convert_cpu_data_points(&self.data_collection, &mut self.canvas_data.cpu_data, false); - self.canvas_data.load_avg_data = self.data_collection.load_avg_harvest; - } - - // Processes - if self.used_widgets.use_proc { - self.update_all_process_lists(); - } - - // Battery - if self.used_widgets.use_battery { - self.canvas_data.battery_data = convert_battery_harvest(&self.data_collection); - } - } - - #[allow(clippy::needless_collect)] - fn update_all_process_lists(&mut self) { - // TODO: Probably refactor this. - - // According to clippy, I can avoid a collect... but if I follow it, - // I end up conflicting with the borrow checker since app is used within the closure... hm. - if !self.is_frozen { - let widget_ids = self - .proc_state - .widget_states - .keys() - .cloned() - .collect::>(); - - widget_ids.into_iter().for_each(|widget_id| { - update_final_process_list(self, widget_id); - }); - } - } - pub fn on_esc(&mut self) { self.reset_multi_tap_keys(); if self.is_in_dialog() { 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 { fn add_widget_to_map( widget_lookup_map: &mut FxHashMap, 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( - &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( + &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, Option>, Option