refactor: delete a bunch of old unused code

This commit is contained in:
ClementTsang 2021-09-05 19:02:50 -04:00
parent fa00dec146
commit 18af6b01bf
25 changed files with 10 additions and 5346 deletions

View File

@ -2300,87 +2300,7 @@ impl AppState {
}
}
pub fn skip_to_last(&mut self) {
// if !self.ignore_normal_keybinds() {
// match self.current_widget.widget_type {
// BottomWidgetType::Proc => {
// if let Some(proc_widget_state) = self
// .proc_state
// .get_mut_widget_state(self.current_widget.widget_id)
// {
// if let Some(finalized_process_data) = self
// .canvas_data
// .finalized_process_data_map
// .get(&self.current_widget.widget_id)
// {
// if !self.canvas_data.finalized_process_data_map.is_empty() {
// proc_widget_state.scroll_state.current_scroll_position =
// finalized_process_data.len() - 1;
// proc_widget_state.scroll_state.scroll_direction =
// ScrollDirection::Down;
// }
// }
// }
// }
// BottomWidgetType::ProcSort => {
// if let Some(proc_widget_state) = self
// .proc_state
// .get_mut_widget_state(self.current_widget.widget_id - 2)
// {
// proc_widget_state.columns.current_scroll_position =
// proc_widget_state.columns.get_enabled_columns_len() - 1;
// proc_widget_state.columns.scroll_direction = ScrollDirection::Down;
// }
// }
// BottomWidgetType::Temp => {
// if let Some(temp_widget_state) = self
// .temp_state
// .get_mut_widget_state(self.current_widget.widget_id)
// {
// if !self.canvas_data.temp_sensor_data.is_empty() {
// temp_widget_state.scroll_state.current_scroll_position =
// self.canvas_data.temp_sensor_data.len() - 1;
// temp_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
// }
// }
// }
// BottomWidgetType::Disk => {
// if let Some(disk_widget_state) = self
// .disk_state
// .get_mut_widget_state(self.current_widget.widget_id)
// {
// if !self.canvas_data.disk_data.is_empty() {
// disk_widget_state.scroll_state.current_scroll_position =
// self.canvas_data.disk_data.len() - 1;
// disk_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
// }
// }
// }
// BottomWidgetType::CpuLegend => {
// if let Some(cpu_widget_state) = self
// .cpu_state
// .get_mut_widget_state(self.current_widget.widget_id - 1)
// {
// let cap = self.canvas_data.cpu_data.len();
// if cap > 0 {
// cpu_widget_state.scroll_state.current_scroll_position = cap - 1;
// cpu_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
// }
// }
// }
// _ => {}
// }
// self.reset_multi_tap_keys();
// } else if self.help_dialog_state.is_showing_help {
// self.help_dialog_state.scroll_state.current_scroll_index = self
// .help_dialog_state
// .scroll_state
// .max_scroll_index
// .saturating_sub(1);
// } else if self.delete_dialog_state.is_showing_dd {
// self.delete_dialog_state.selected_signal = KillSignal::Kill(MAX_SIGNAL);
// }
}
pub fn skip_to_last(&mut self) {}
pub fn decrement_position_count(&mut self) {
if !self.ignore_normal_keybinds() {
@ -2461,35 +2381,6 @@ impl AppState {
/// Returns the new position.
fn increment_process_position(&mut self, _num_to_change_by: i64) -> Option<usize> {
// if let Some(proc_widget_state) = self
// .proc_state
// .get_mut_widget_state(self.current_widget.widget_id)
// {
// let current_posn = proc_widget_state.scroll_state.current_scroll_position;
// if let Some(finalized_process_data) = self
// .canvas_data
// .finalized_process_data_map
// .get(&self.current_widget.widget_id)
// {
// if current_posn as i64 + num_to_change_by >= 0
// && current_posn as i64 + num_to_change_by < finalized_process_data.len() as i64
// {
// proc_widget_state.scroll_state.current_scroll_position =
// (current_posn as i64 + num_to_change_by) as usize;
// } else {
// return None;
// }
// }
// if num_to_change_by < 0 {
// proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Up;
// } else {
// proc_widget_state.scroll_state.scroll_direction = ScrollDirection::Down;
// }
// return Some(proc_widget_state.scroll_state.current_scroll_position);
// }
None
}
@ -2563,40 +2454,6 @@ impl AppState {
}
}
pub fn handle_scroll_up(&mut self) {
if self.delete_dialog_state.is_showing_dd {
#[cfg(target_family = "unix")]
{
self.on_up_key();
return;
}
}
if self.help_dialog_state.is_showing_help {
self.help_scroll_up();
} else if self.current_widget.widget_type.is_widget_graph() {
self.zoom_in();
} else if self.current_widget.widget_type.is_widget_table() {
self.decrement_position_count();
}
}
pub fn handle_scroll_down(&mut self) {
if self.delete_dialog_state.is_showing_dd {
#[cfg(target_family = "unix")]
{
self.on_down_key();
return;
}
}
if self.help_dialog_state.is_showing_help {
self.help_scroll_down();
} else if self.current_widget.widget_type.is_widget_graph() {
self.zoom_out();
} else if self.current_widget.widget_type.is_widget_table() {
self.increment_position_count();
}
}
fn on_plus(&mut self) {
if let BottomWidgetType::Proc = self.current_widget.widget_type {
// Toggle collapsing if tree
@ -2615,34 +2472,7 @@ impl AppState {
}
}
fn toggle_collapsing_process_branch(&mut self) {
// if let Some(proc_widget_state) = self
// .proc_state
// .widget_states
// .get_mut(&self.current_widget.widget_id)
// {
// let current_posn = proc_widget_state.scroll_state.current_scroll_position;
// if let Some(displayed_process_list) = self
// .canvas_data
// .finalized_process_data_map
// .get(&self.current_widget.widget_id)
// {
// if let Some(corresponding_process) = displayed_process_list.get(current_posn) {
// let corresponding_pid = corresponding_process.pid;
// if let Some(process_data) = self
// .canvas_data
// .single_process_data
// .get_mut(&corresponding_pid)
// {
// process_data.is_collapsed_entry = !process_data.is_collapsed_entry;
// self.proc_state.force_update = Some(self.current_widget.widget_id);
// }
// }
// }
// }
}
fn toggle_collapsing_process_branch(&mut self) {}
fn zoom_out(&mut self) {
match self.current_widget.widget_type {
@ -2856,343 +2686,4 @@ impl AppState {
_ => {}
}
}
/// Moves the mouse to the widget that was clicked on, then propagates the click down to be
/// handled by the widget specifically.
pub fn on_left_mouse_up(&mut self, x: u16, y: u16) {
// Pretty dead simple - iterate through the widget map and go to the widget where the click
// is within.
// TODO: [REFACTOR] might want to refactor this, it's really ugly.
// TODO: [REFACTOR] Might wanna refactor ALL state things in general, currently everything
// is grouped up as an app state. We should separate stuff like event state and gui state and etc.
// TODO: [MOUSE] double click functionality...? We would do this above all other actions and SC if needed.
// Short circuit if we're in basic table... we might have to handle the basic table arrow
// case here...
if let Some(bt) = &mut self.basic_table_widget_state {
if let (
Some((left_tlc_x, left_tlc_y)),
Some((left_brc_x, left_brc_y)),
Some((right_tlc_x, right_tlc_y)),
Some((right_brc_x, right_brc_y)),
) = (bt.left_tlc, bt.left_brc, bt.right_tlc, bt.right_brc)
{
if (x >= left_tlc_x && y >= left_tlc_y) && (x < left_brc_x && y < left_brc_y) {
// Case for the left "button" in the simple arrow.
if let Some(new_widget) =
self.widget_map.get(&(bt.currently_displayed_widget_id))
{
// We have to move to the current table widget first...
self.current_widget = new_widget.clone();
if let BottomWidgetType::Proc = &new_widget.widget_type {
if let Some(proc_widget_state) =
self.proc_state.get_widget_state(new_widget.widget_id)
{
if proc_widget_state.is_sort_open {
self.move_widget_selection(&WidgetDirection::Left);
}
}
}
self.move_widget_selection(&WidgetDirection::Left);
return;
}
} else if (x >= right_tlc_x && y >= right_tlc_y)
&& (x < right_brc_x && y < right_brc_y)
{
// Case for the right "button" in the simple arrow.
if let Some(new_widget) =
self.widget_map.get(&(bt.currently_displayed_widget_id))
{
// We have to move to the current table widget first...
self.current_widget = new_widget.clone();
if let BottomWidgetType::ProcSort = &new_widget.widget_type {
if let Some(proc_widget_state) =
self.proc_state.get_widget_state(new_widget.widget_id - 2)
{
if proc_widget_state.is_sort_open {
self.move_widget_selection(&WidgetDirection::Right);
}
}
}
}
self.move_widget_selection(&WidgetDirection::Right);
// Bit extra logic to ensure you always land on a proc widget, not the sort
if let BottomWidgetType::ProcSort = &self.current_widget.widget_type {
self.move_widget_selection(&WidgetDirection::Right);
}
return;
}
}
}
// Second short circuit --- are we in the dd dialog state? If so, only check yes/no/signals
// and bail after.
if self.is_in_dialog() {
match self.delete_dialog_state.button_positions.iter().find(
|(tl_x, tl_y, br_x, br_y, _idx)| {
(x >= *tl_x && y >= *tl_y) && (x <= *br_x && y <= *br_y)
},
) {
Some((_, _, _, _, 0)) => {
self.delete_dialog_state.selected_signal = KillSignal::Cancel
}
Some((_, _, _, _, idx)) => {
if *idx > 31 {
self.delete_dialog_state.selected_signal = KillSignal::Kill(*idx + 2)
} else {
self.delete_dialog_state.selected_signal = KillSignal::Kill(*idx)
}
}
_ => {}
}
return;
}
let mut failed_to_get = true;
// TODO: [MOUSE] We could use a better data structure for this? Currently it's a blind
// traversal through a hashmap, using a 2d binary tree of sorts would be better.
// See: https://docs.rs/kdtree/0.6.0/kdtree/
for (new_widget_id, widget) in &self.widget_map {
if let (Some((tlc_x, tlc_y)), Some((brc_x, brc_y))) =
(widget.top_left_corner, widget.bottom_right_corner)
{
if (x >= tlc_x && y >= tlc_y) && (x < brc_x && y < brc_y) {
if let Some(new_widget) = self.widget_map.get(new_widget_id) {
self.current_widget = new_widget.clone();
match &self.current_widget.widget_type {
BottomWidgetType::Temp
| BottomWidgetType::Proc
| BottomWidgetType::ProcSort
| BottomWidgetType::Disk
| BottomWidgetType::Battery => {
if let Some(basic_table_widget_state) =
&mut self.basic_table_widget_state
{
basic_table_widget_state.currently_displayed_widget_id =
self.current_widget.widget_id;
basic_table_widget_state.currently_displayed_widget_type =
self.current_widget.widget_type.clone();
}
}
_ => {}
}
failed_to_get = false;
break;
}
}
}
}
if failed_to_get {
return;
}
// Now handle click propagation down to widget.
if let (Some((_tlc_x, tlc_y)), Some((_brc_x, brc_y))) = (
&self.current_widget.top_left_corner,
&self.current_widget.bottom_right_corner,
) {
let border_offset = if self.is_drawing_border() { 1 } else { 0 };
// This check ensures the click isn't actually just clicking on the bottom border.
if y < (brc_y - border_offset) {
match &self.current_widget.widget_type {
BottomWidgetType::Proc
| BottomWidgetType::ProcSort
| BottomWidgetType::CpuLegend
| BottomWidgetType::Temp
| BottomWidgetType::Disk => {
// Get our index...
let clicked_entry = y - *tlc_y;
// + 1 so we start at 0.
let header_gap_offset = 1 + if self.is_drawing_gap(&self.current_widget) {
self.app_config_fields.table_gap
} else {
0
};
let offset = border_offset + header_gap_offset;
if clicked_entry >= offset {
let offset_clicked_entry = clicked_entry - offset;
match &self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self
.proc_state
.get_widget_state(self.current_widget.widget_id)
{
if let Some(visual_index) =
proc_widget_state.scroll_state.table_state.selected()
{
// If in tree mode, also check to see if this click is on
// the same entry as the already selected one - if it is,
// then we minimize.
let previous_scroll_position = proc_widget_state
.scroll_state
.current_scroll_position;
let is_tree_mode = proc_widget_state.is_tree_mode;
let new_position = self.increment_process_position(
offset_clicked_entry as i64 - visual_index as i64,
);
if is_tree_mode {
if let Some(new_position) = new_position {
if previous_scroll_position == new_position {
self.toggle_collapsing_process_branch();
}
}
}
}
}
}
BottomWidgetType::ProcSort => {
// TODO: This should sort if you double click!
if let Some(proc_widget_state) = self
.proc_state
.get_widget_state(self.current_widget.widget_id - 2)
{
if let Some(visual_index) =
proc_widget_state.columns.column_state.selected()
{
self.increment_process_sort_position(
offset_clicked_entry as i64 - visual_index as i64,
);
}
}
}
BottomWidgetType::CpuLegend => {
if let Some(cpu_widget_state) = self
.cpu_state
.get_widget_state(self.current_widget.widget_id - 1)
{
if let Some(visual_index) =
cpu_widget_state.scroll_state.table_state.selected()
{
self.increment_cpu_legend_position(
offset_clicked_entry as i64 - visual_index as i64,
);
}
}
}
BottomWidgetType::Temp => {
if let Some(temp_widget_state) = self
.temp_state
.get_widget_state(self.current_widget.widget_id)
{
if let Some(visual_index) =
temp_widget_state.scroll_state.table_state.selected()
{
self.increment_temp_position(
offset_clicked_entry as i64 - visual_index as i64,
);
}
}
}
BottomWidgetType::Disk => {
if let Some(disk_widget_state) = self
.disk_state
.get_widget_state(self.current_widget.widget_id)
{
if let Some(visual_index) =
disk_widget_state.scroll_state.table_state.selected()
{
self.increment_disk_position(
offset_clicked_entry as i64 - visual_index as i64,
);
}
}
}
_ => {}
}
} else {
// We might have clicked on a header! Check if we only exceeded the table + border offset, and
// it's implied we exceeded the gap offset.
if clicked_entry == border_offset {
#[allow(clippy::single_match)]
match &self.current_widget.widget_type {
BottomWidgetType::Proc => {
if let Some(proc_widget_state) = self
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
// Let's now check if it's a column header.
if let (Some(y_loc), Some(x_locs)) = (
&proc_widget_state.columns.column_header_y_loc,
&proc_widget_state.columns.column_header_x_locs,
) {
// debug!("x, y: {}, {}", x, y);
// debug!("y_loc: {}", y_loc);
// debug!("x_locs: {:?}", x_locs);
if y == *y_loc {
for (itx, (x_left, x_right)) in
x_locs.iter().enumerate()
{
if x >= *x_left && x <= *x_right {
// Found our column!
proc_widget_state
.columns
.set_to_sorted_index_from_visual_index(
itx,
);
proc_widget_state
.update_sorting_with_columns();
self.proc_state.force_update =
Some(self.current_widget.widget_id);
break;
}
}
}
}
}
}
_ => {}
}
}
}
}
BottomWidgetType::Battery => {
if let Some(battery_widget_state) = self
.battery_state
.get_mut_widget_state(self.current_widget.widget_id)
{
if let Some(tab_spacing) = &battery_widget_state.tab_click_locs {
for (itx, ((tlc_x, tlc_y), (brc_x, brc_y))) in
tab_spacing.iter().enumerate()
{
if (x >= *tlc_x && y >= *tlc_y) && (x <= *brc_x && y <= *brc_y)
{
battery_widget_state.currently_selected_battery_index = itx;
break;
}
}
}
}
}
_ => {}
}
}
}
}
fn is_drawing_border(&self) -> bool {
self.is_expanded || !self.app_config_fields.use_basic_mode
}
fn is_drawing_gap(&self, widget: &BottomWidget) -> bool {
if let (Some((_tlc_x, tlc_y)), Some((_brc_x, brc_y))) =
(widget.top_left_corner, widget.bottom_right_corner)
{
brc_y - tlc_y >= constants::TABLE_GAP_HEIGHT_LIMIT
} else {
self.app_config_fields.table_gap == 0
}
}
}

View File

@ -35,17 +35,9 @@ pub struct BatteryState {
}
impl BatteryState {
pub fn init(widget_states: HashMap<u64, BatteryWidgetState>) -> Self {
BatteryState { widget_states }
}
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut BatteryWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&BatteryWidgetState> {
self.widget_states.get(&widget_id)
}
}
/// A table displaying battery information on a per-battery basis.

View File

@ -28,19 +28,6 @@ pub struct CpuWidgetState {
pub table_width_state: CanvasTableWidthState,
}
impl CpuWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
CpuWidgetState {
current_display_time,
is_legend_hidden: false,
autohide_timer,
scroll_state: AppScrollWidgetState::default(),
is_multi_graph_mode: false,
table_width_state: CanvasTableWidthState::default(),
}
}
}
#[derive(Default)]
pub struct CpuState {
pub force_update: Option<u64>,
@ -48,13 +35,6 @@ pub struct CpuState {
}
impl CpuState {
pub fn init(widget_states: HashMap<u64, CpuWidgetState>) -> Self {
CpuState {
force_update: None,
widget_states,
}
}
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut CpuWidgetState> {
self.widget_states.get_mut(&widget_id)
}
@ -64,6 +44,7 @@ impl CpuState {
}
}
/// Which part of the [`CpuGraph`] is currently selected.
enum CpuGraphSelection {
Graph,
Legend,

View File

@ -24,15 +24,6 @@ pub struct DiskWidgetState {
pub table_width_state: CanvasTableWidthState,
}
impl DiskWidgetState {
pub fn init() -> Self {
DiskWidgetState {
scroll_state: AppScrollWidgetState::default(),
table_width_state: CanvasTableWidthState::default(),
}
}
}
#[derive(Default)]
pub struct DiskState {
pub widget_states: HashMap<u64, DiskWidgetState>,

View File

@ -19,38 +19,12 @@ pub struct MemWidgetState {
pub autohide_timer: Option<Instant>,
}
impl MemWidgetState {
pub fn init(current_display_time: u64, autohide_timer: Option<Instant>) -> Self {
MemWidgetState {
current_display_time,
autohide_timer,
}
}
}
#[derive(Default)]
pub struct MemState {
pub force_update: Option<u64>,
pub widget_states: HashMap<u64, MemWidgetState>,
}
impl MemState {
pub fn init(widget_states: HashMap<u64, MemWidgetState>) -> Self {
MemState {
force_update: None,
widget_states,
}
}
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut MemWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&MemWidgetState> {
self.widget_states.get(&widget_id)
}
}
/// A widget that deals with displaying memory usage on a [`TimeGraph`]. Basically just a wrapper
/// around [`TimeGraph`] as of now.
pub struct MemGraph {

View File

@ -22,31 +22,6 @@ use crate::{
pub struct NetWidgetState {
pub current_display_time: u64,
pub autohide_timer: Option<Instant>,
// pub draw_max_range_cache: f64,
// pub draw_labels_cache: Vec<String>,
// pub draw_time_start_cache: f64,
// TODO: Re-enable these when we move net details state-side!
// pub unit_type: DataUnitTypes,
// pub scale_type: AxisScaling,
}
impl NetWidgetState {
pub fn init(
current_display_time: u64,
autohide_timer: Option<Instant>,
// unit_type: DataUnitTypes,
// scale_type: AxisScaling,
) -> Self {
NetWidgetState {
current_display_time,
autohide_timer,
// draw_max_range_cache: 0.0,
// draw_labels_cache: vec![],
// draw_time_start_cache: 0.0,
// unit_type,
// scale_type,
}
}
}
#[derive(Default)]
@ -55,23 +30,6 @@ pub struct NetState {
pub widget_states: HashMap<u64, NetWidgetState>,
}
impl NetState {
pub fn init(widget_states: HashMap<u64, NetWidgetState>) -> Self {
NetState {
force_update: None,
widget_states,
}
}
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut NetWidgetState> {
self.widget_states.get_mut(&widget_id)
}
pub fn get_widget_state(&self, widget_id: u64) -> Option<&NetWidgetState> {
self.widget_states.get(&widget_id)
}
}
// --- NEW STUFF BELOW ---
/// Returns the max data point and time given a time.

View File

@ -119,9 +119,6 @@ impl ProcessSearchState {
pub struct ColumnInfo {
pub enabled: bool,
pub shortcut: Option<&'static str>,
// FIXME: Move column width logic here!
// pub hard_width: Option<u16>,
// pub max_soft_width: Option<f64>,
}
pub struct ProcColumn {
@ -419,59 +416,6 @@ pub struct ProcWidgetState {
}
impl ProcWidgetState {
pub fn init(
is_case_sensitive: bool, is_match_whole_word: bool, is_use_regex: bool, is_grouped: bool,
show_memory_as_values: bool, is_tree_mode: bool, is_using_command: bool,
) -> Self {
let mut process_search_state = ProcessSearchState::default();
if is_case_sensitive {
// By default it's off
process_search_state.search_toggle_ignore_case();
}
if is_match_whole_word {
process_search_state.search_toggle_whole_word();
}
if is_use_regex {
process_search_state.search_toggle_regex();
}
let (process_sorting_type, is_process_sort_descending) = if is_tree_mode {
(processes::ProcessSorting::Pid, false)
} else {
(processes::ProcessSorting::CpuPercent, true)
};
// TODO: If we add customizable columns, this should pull from config
let mut columns = ProcColumn::default();
columns.set_to_sorted_index_from_type(&process_sorting_type);
if is_grouped {
// Normally defaults to showing by PID, toggle count on instead.
columns.toggle(&ProcessSorting::Count);
columns.toggle(&ProcessSorting::Pid);
}
if show_memory_as_values {
// Normally defaults to showing by percent, toggle value on instead.
columns.toggle(&ProcessSorting::Mem);
columns.toggle(&ProcessSorting::MemPercent);
}
ProcWidgetState {
process_search_state,
is_grouped,
scroll_state: AppScrollWidgetState::default(),
process_sorting_type,
is_process_sort_descending,
is_using_command,
current_column_index: 0,
is_sort_open: false,
columns,
is_tree_mode,
table_width_state: CanvasTableWidthState::default(),
requires_redraw: false,
}
}
/// Updates sorting when using the column list.
/// ...this really should be part of the ProcColumn struct (along with the sorting fields),
/// but I'm too lazy.
@ -620,14 +564,6 @@ pub struct ProcState {
}
impl ProcState {
pub fn init(widget_states: HashMap<u64, ProcWidgetState>) -> Self {
ProcState {
widget_states,
force_update: None,
force_update_all: false,
}
}
pub fn get_mut_widget_state(&mut self, widget_id: u64) -> Option<&mut ProcWidgetState> {
self.widget_states.get_mut(&widget_id)
}

View File

@ -10,8 +10,6 @@ use tui::{
Frame, Terminal,
};
// use ordered_float::OrderedFloat;
use canvas_colours::*;
use dialogs::*;
@ -33,8 +31,6 @@ use crate::{
mod canvas_colours;
mod dialogs;
pub mod drawing; // TODO: Remove pub access at some point!
mod drawing_utils;
/// Point is of time, data
type Point = (f64, f64);
@ -191,7 +187,7 @@ impl Painter {
self.styled_help_text = styled_help_spans.into_iter().map(Spans::from).collect();
}
// FIXME: [CONFIG] write this, should call painter init and any changed colour functions...
// TODO: [CONFIG] write this, should call painter init and any changed colour functions...
pub fn update_painter_colours(&mut self) {}
fn draw_frozen_indicator<B: Backend>(&self, f: &mut Frame<'_, B>, draw_loc: Rect) {

View File

@ -1,15 +0,0 @@
pub mod basic_table_arrows;
pub mod battery_display;
pub mod cpu_basic;
pub mod mem_basic;
pub mod mem_graph;
pub mod network_basic;
pub mod network_graph;
pub use basic_table_arrows::*;
pub use battery_display::*;
pub use cpu_basic::*;
pub use mem_basic::*;
pub use mem_graph::*;
pub use network_basic::*;
pub use network_graph::*;

View File

@ -1,154 +0,0 @@
use crate::{
app::{layout_manager::BottomWidgetType, AppState},
canvas::Painter,
};
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
terminal::Frame,
text::Span,
text::Spans,
widgets::{Block, Paragraph},
};
pub fn draw_basic_table_arrows<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if let Some(current_table) = app_state.widget_map.get(&widget_id) {
let current_table = if let BottomWidgetType::ProcSort = current_table.widget_type {
current_table
.right_neighbour
.map(|id| app_state.widget_map.get(&id).unwrap())
.unwrap()
} else {
current_table
};
let (left_table, right_table) = (
{
current_table
.left_neighbour
.map(|left_widget_id| {
app_state
.widget_map
.get(&left_widget_id)
.map(|left_widget| {
if left_widget.widget_type == BottomWidgetType::ProcSort {
left_widget
.left_neighbour
.map(|left_left_widget_id| {
app_state.widget_map.get(&left_left_widget_id).map(
|left_left_widget| &left_left_widget.widget_type,
)
})
.unwrap_or(Some(&BottomWidgetType::Temp))
.unwrap_or(&BottomWidgetType::Temp)
} else {
&left_widget.widget_type
}
})
.unwrap_or(&BottomWidgetType::Temp)
})
.unwrap_or(&BottomWidgetType::Temp)
},
{
current_table
.right_neighbour
.map(|right_widget_id| {
app_state
.widget_map
.get(&right_widget_id)
.map(|right_widget| {
if right_widget.widget_type == BottomWidgetType::ProcSort {
right_widget
.right_neighbour
.map(|right_right_widget_id| {
app_state.widget_map.get(&right_right_widget_id).map(
|right_right_widget| {
&right_right_widget.widget_type
},
)
})
.unwrap_or(Some(&BottomWidgetType::Disk))
.unwrap_or(&BottomWidgetType::Disk)
} else {
&right_widget.widget_type
}
})
.unwrap_or(&BottomWidgetType::Disk)
})
.unwrap_or(&BottomWidgetType::Disk)
},
);
let left_name = left_table.get_pretty_name();
let right_name = right_table.get_pretty_name();
let num_spaces =
usize::from(draw_loc.width).saturating_sub(6 + left_name.len() + right_name.len());
let left_arrow_text = vec![
Spans::default(),
Spans::from(Span::styled(
format!("{}", left_name),
painter.colours.text_style,
)),
];
let right_arrow_text = vec![
Spans::default(),
Spans::from(Span::styled(
format!("{}", right_name),
painter.colours.text_style,
)),
];
let margined_draw_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Length(2 + left_name.len() as u16),
Constraint::Length(num_spaces as u16),
Constraint::Length(2 + right_name.len() as u16),
])
.horizontal_margin(1)
.split(draw_loc);
f.render_widget(
Paragraph::new(left_arrow_text).block(Block::default()),
margined_draw_loc[0],
);
f.render_widget(
Paragraph::new(right_arrow_text)
.block(Block::default())
.alignment(Alignment::Right),
margined_draw_loc[2],
);
if app_state.should_get_widget_bounds() {
// Some explanations for future readers:
// - The "height" as of writing of this entire widget is 2. If it's 1, it occasionally doesn't draw.
// - As such, the buttons are only on the lower part of this 2-high widget.
// - So, we want to only check at one location, the `draw_loc.y + 1`, and that's it.
// - But why is it "+2" then? Well, it's because I have a REALLY ugly hack
// for mouse button checking, since most button checks are of the form `(draw_loc.y + draw_loc.height)`,
// and the same for the x and width. Unfortunately, if you check using >= and <=, the outer bound is
// actually too large - so, we assume all of them are one too big and check via < (see
// https://github.com/ClementTsang/bottom/pull/459 for details).
// - So in other words, to make it simple, we keep this to a standard and overshoot by one here.
if let Some(basic_table) = &mut app_state.basic_table_widget_state {
basic_table.left_tlc = Some((margined_draw_loc[0].x, margined_draw_loc[0].y + 1));
basic_table.left_brc = Some((
margined_draw_loc[0].x + margined_draw_loc[0].width,
margined_draw_loc[0].y + 2,
));
basic_table.right_tlc = Some((margined_draw_loc[2].x, margined_draw_loc[2].y + 1));
basic_table.right_brc = Some((
margined_draw_loc[2].x + margined_draw_loc[2].width,
margined_draw_loc[2].y + 2,
));
}
}
}
}

View File

@ -1,197 +0,0 @@
use crate::{
app::AppState,
canvas::{drawing_utils::calculate_basic_use_bars, Painter},
constants::*,
};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
text::{Span, Spans},
widgets::{Block, Borders, Cell, Paragraph, Row, Table, Tabs},
};
use unicode_segmentation::UnicodeSegmentation;
pub fn draw_battery_display<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let should_get_widget_bounds = app_state.should_get_widget_bounds();
if let Some(battery_widget_state) = app_state.battery_state.widget_states.get_mut(&widget_id) {
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Battery ── Esc to go back ";
Spans::from(vec![
Span::styled(" Battery ".to_string(), painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(Span::styled(
" Battery ".to_string(),
painter.colours.widget_title_style,
))
};
let battery_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let battery_names = app_state
.canvas_data
.battery_data
.iter()
.map(|battery| &battery.battery_name)
.collect::<Vec<_>>();
let tab_draw_loc = Layout::default()
.constraints([
Constraint::Length(1),
Constraint::Length(2),
Constraint::Min(0),
])
.direction(Direction::Vertical)
.split(draw_loc)[1];
f.render_widget(
Tabs::new(
battery_names
.iter()
.map(|name| Spans::from((*name).clone()))
.collect::<Vec<_>>(),
)
.block(Block::default())
.divider(tui::symbols::line::VERTICAL)
.style(painter.colours.text_style)
.highlight_style(painter.colours.currently_selected_text_style)
.select(battery_widget_state.currently_selected_battery_index),
tab_draw_loc,
);
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
if let Some(battery_details) = app_state
.canvas_data
.battery_data
.get(battery_widget_state.currently_selected_battery_index)
{
// Assuming a 50/50 split in width
let bar_length = usize::from((draw_loc.width.saturating_sub(2) / 2).saturating_sub(8));
let charge_percentage = battery_details.charge_percentage;
let num_bars = calculate_basic_use_bars(charge_percentage, bar_length);
let bars = format!(
"[{}{}{:3.0}%]",
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
charge_percentage,
);
let battery_rows = vec![
Row::new(vec![
Cell::from("Charge %").style(painter.colours.text_style),
Cell::from(bars).style(if charge_percentage < 10.0 {
painter.colours.low_battery_colour
} else if charge_percentage < 50.0 {
painter.colours.medium_battery_colour
} else {
painter.colours.high_battery_colour
}),
]),
Row::new(vec!["Consumption", &battery_details.watt_consumption])
.style(painter.colours.text_style),
// if let Some(duration_until_full) = &battery_details.duration_until_full {
// Row::new(vec!["Time to full", duration_until_full])
// .style(painter.colours.text_style)
// } else if let Some(duration_until_empty) = &battery_details.duration_until_empty {
// Row::new(vec!["Time to empty", duration_until_empty])
// .style(painter.colours.text_style)
// } else {
// Row::new(vec!["Time to full/empty", "N/A"]).style(painter.colours.text_style)
// },
Row::new(vec!["Health %", &battery_details.health])
.style(painter.colours.text_style),
];
// Draw
f.render_widget(
Table::new(battery_rows)
.block(battery_block)
.header(Row::new(vec![""]).bottom_margin(table_gap))
.widths(&[Constraint::Percentage(50), Constraint::Percentage(50)]),
margined_draw_loc,
);
} else {
let mut contents = vec![Spans::default(); table_gap as usize];
contents.push(Spans::from(Span::styled(
"No data found for this battery",
painter.colours.text_style,
)));
f.render_widget(
Paragraph::new(contents).block(battery_block),
margined_draw_loc,
);
}
if should_get_widget_bounds {
// Tab wizardry
if !battery_names.is_empty() {
let mut current_x = tab_draw_loc.x;
let current_y = tab_draw_loc.y;
let mut tab_click_locs: Vec<((u16, u16), (u16, u16))> = vec![];
for battery in battery_names {
// +1 because there's a space after the tab label.
let width = unicode_width::UnicodeWidthStr::width(battery.as_str()) as u16;
tab_click_locs.push(((current_x, current_y), (current_x + width, current_y)));
// +4 because we want to go one space, then one space past to get to the '|', then 2 more
// to start at the blank space before the tab label.
current_x += width + 4;
}
battery_widget_state.tab_click_locs = Some(tab_click_locs);
}
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}
}

View File

@ -1,199 +0,0 @@
use std::cmp::min;
use crate::{
app::AppState,
canvas::{drawing_utils::*, Painter},
constants::*,
data_conversion::ConvertedCpuData,
};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
text::{Span, Spans},
widgets::{Block, Paragraph},
};
pub fn draw_basic_cpu<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
// Skip the first element, it's the "all" element
if app_state.canvas_data.cpu_data.len() > 1 {
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data[1..];
// This is a bit complicated, but basically, we want to draw SOME number
// of columns to draw all CPUs. Ideally, as well, we want to not have
// to ever scroll.
// **General logic** - count number of elements in cpu_data. Then see how
// many rows and columns we have in draw_loc (-2 on both sides for border?).
// I think what we can do is try to fit in as many in one column as possible.
// If not, then add a new column.
// Then, from this, split the row space across ALL columns. From there, generate
// the desired lengths.
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style),
draw_loc,
);
}
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 =
vec![Constraint::Percentage((100 / REQUIRED_COLUMNS) as u16); REQUIRED_COLUMNS];
let chunks = Layout::default()
.constraints(chunk_vec)
.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 = chunks[0].width as usize;
// 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;
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
let num_bars = calculate_basic_use_bars(use_percentage, bar_length);
format!(
"{:3}[{}{}{:3.0}%]",
if app_state.app_config_fields.show_average_cpu {
if cpu_index == 0 {
"AVG".to_string()
} else {
(cpu_index - 1).to_string()
}
} else {
cpu_index.to_string()
},
"|".repeat(num_bars),
" ".repeat(bar_length - num_bars),
use_percentage.round(),
)
})
.collect::<Vec<_>>()
} else if chunk_width >= REDUCED_SPACING {
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
format!(
"{:3} {:3.0}%",
if app_state.app_config_fields.show_average_cpu {
if cpu_index == 0 {
"AVG".to_string()
} else {
(cpu_index - 1).to_string()
}
} else {
cpu_index.to_string()
},
use_percentage.round(),
)
})
.collect::<Vec<_>>()
} else {
(0..num_cpus)
.map(|cpu_index| {
let use_percentage =
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
cpu_usage.1
} else {
0.0
};
format!("{:3.0}%", use_percentage.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
if REQUIRED_COLUMNS > itx {
let to_divide = REQUIRED_COLUMNS - itx;
let how_many_cpus = 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);
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 {
painter.colours.avg_colour_style
} else {
painter.colours.cpu_colour_styles
[(itx - 1) % painter.colours.cpu_colour_styles.len()]
}
} else {
painter.colours.cpu_colour_styles
[itx % painter.colours.cpu_colour_styles.len()]
},
})
})
.collect::<Vec<_>>();
start_index += how_many_cpus;
let margined_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(*chunk)[0];
f.render_widget(
Paragraph::new(cpu_column).block(Block::default()),
margined_loc,
);
}
}
}
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -1,527 +0,0 @@
use once_cell::sync::Lazy;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
app::{layout_manager::WidgetDirection, AppState},
canvas::{
drawing_utils::{get_column_widths, get_start_position, interpolate_points},
Painter,
},
constants::*,
data_conversion::ConvertedCpuData,
};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
symbols::Marker,
terminal::Frame,
text::Span,
text::{Spans, Text},
widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table},
};
const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
const AVG_POSITION: usize = 1;
const ALL_POSITION: usize = 0;
static CPU_LEGEND_HEADER_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
CPU_LEGEND_HEADER
.iter()
.map(|entry| entry.len() as u16)
.collect::<Vec<_>>()
});
pub fn draw_cpu<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if draw_loc.width as f64 * 0.15 <= 6.0 {
// Skip drawing legend
if app_state.current_widget.widget_id == (widget_id + 1) {
if app_state.app_config_fields.left_legend {
app_state.move_widget_selection(&WidgetDirection::Right);
} else {
app_state.move_widget_selection(&WidgetDirection::Left);
}
}
draw_cpu_graph(painter, f, app_state, draw_loc, widget_id);
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
cpu_widget_state.is_legend_hidden = true;
}
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(bottom_widget) = app_state.widget_map.get_mut(&widget_id) {
bottom_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
bottom_widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
} else {
let (graph_index, legend_index, constraints) = if app_state.app_config_fields.left_legend {
(
1,
0,
[Constraint::Percentage(15), Constraint::Percentage(85)],
)
} else {
(
0,
1,
[Constraint::Percentage(85), Constraint::Percentage(15)],
)
};
let partitioned_draw_loc = Layout::default()
.margin(0)
.direction(Direction::Horizontal)
.constraints(constraints)
.split(draw_loc);
draw_cpu_graph(
painter,
f,
app_state,
partitioned_draw_loc[graph_index],
widget_id,
);
draw_cpu_legend(
painter,
f,
app_state,
partitioned_draw_loc[legend_index],
widget_id + 1,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(cpu_widget) = app_state.widget_map.get_mut(&widget_id) {
cpu_widget.top_left_corner = Some((
partitioned_draw_loc[graph_index].x,
partitioned_draw_loc[graph_index].y,
));
cpu_widget.bottom_right_corner = Some((
partitioned_draw_loc[graph_index].x + partitioned_draw_loc[graph_index].width,
partitioned_draw_loc[graph_index].y + partitioned_draw_loc[graph_index].height,
));
}
if let Some(legend_widget) = app_state.widget_map.get_mut(&(widget_id + 1)) {
legend_widget.top_left_corner = Some((
partitioned_draw_loc[legend_index].x,
partitioned_draw_loc[legend_index].y,
));
legend_widget.bottom_right_corner = Some((
partitioned_draw_loc[legend_index].x + partitioned_draw_loc[legend_index].width,
partitioned_draw_loc[legend_index].y
+ partitioned_draw_loc[legend_index].height,
));
}
}
}
}
fn draw_cpu_graph<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let display_time_labels = vec![
Span::styled(
format!("{}s", cpu_widget_state.current_display_time / 1000),
painter.colours.graph_style,
),
Span::styled("0s".to_string(), painter.colours.graph_style),
];
let y_axis_labels = vec![
Span::styled(" 0%", painter.colours.graph_style),
Span::styled("100%", painter.colours.graph_style),
];
let time_start = -(cpu_widget_state.current_display_time as f64);
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& cpu_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = cpu_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
} else {
cpu_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
let y_axis = Axis::default()
.style(painter.colours.graph_style)
.bounds([0.0, 100.5])
.labels(y_axis_labels);
let use_dot = app_state.app_config_fields.use_dot;
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
let interpolated_cpu_points = cpu_data
.iter_mut()
.enumerate()
.map(|(itx, cpu)| {
let to_show = if current_scroll_position == ALL_POSITION {
true
} else {
itx == current_scroll_position
};
if to_show {
if let Some(end_pos) = cpu
.cpu_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = cpu.cpu_data.get(start_pos);
let inside_point = cpu.cpu_data.get(end_pos);
if let (Some(outside_point), Some(inside_point)) =
(outside_point, inside_point)
{
let old = *outside_point;
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
if let Some(to_replace) = cpu.cpu_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Failed to get mutable reference.
}
} else {
None // Point somehow doesn't exist in our data
}
} else {
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
}
} else {
None
}
})
.collect::<Vec<_>>();
let dataset_vector: Vec<Dataset<'_>> = if current_scroll_position == ALL_POSITION {
cpu_data
.iter()
.enumerate()
.rev()
.map(|(itx, cpu)| {
Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && itx == AVG_POSITION {
painter.colours.avg_colour_style
} else if itx == ALL_POSITION {
painter.colours.all_colour_style
} else {
painter.colours.cpu_colour_styles[(itx - 1 // Because of the all position
- (if show_avg_cpu {
AVG_POSITION
} else {
0
}))
% painter.colours.cpu_colour_styles.len()]
})
.data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)
})
.collect()
} else if let Some(cpu) = cpu_data.get(current_scroll_position) {
vec![Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && current_scroll_position == AVG_POSITION {
painter.colours.avg_colour_style
} else {
painter.colours.cpu_colour_styles[(cpu_widget_state
.scroll_state
.current_scroll_position
- 1 // Because of the all position
- (if show_avg_cpu {
AVG_POSITION
} else {
0
}))
% painter.colours.cpu_colour_styles.len()]
})
.data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line)]
} else {
vec![]
};
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let title = if cfg!(target_family = "unix") {
let load_avg = app_state.canvas_data.load_avg_data;
let load_avg_str = format!(
"─ {:.2} {:.2} {:.2} ",
load_avg[0], load_avg[1], load_avg[2]
);
let load_avg_str_size =
UnicodeSegmentation::graphemes(load_avg_str.as_str(), true).count();
if app_state.is_expanded {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
Spans::from(vec![
Span::styled(" CPU ", painter.colours.widget_title_style),
Span::styled(load_avg_str, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
load_avg_str_size
+ UnicodeSegmentation::graphemes(TITLE_BASE, true).count()
+ 2
))
),
border_style,
),
])
} else {
Spans::from(vec![
Span::styled(" CPU ", painter.colours.widget_title_style),
Span::styled(load_avg_str, painter.colours.widget_title_style),
])
}
} else if app_state.is_expanded {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
Spans::from(vec![
Span::styled(" CPU ", painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(vec![Span::styled(
" CPU ",
painter.colours.widget_title_style,
)])
};
f.render_widget(
Chart::new(dataset_vector)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style),
)
.x_axis(x_axis)
.y_axis(y_axis),
draw_loc,
);
// Reset interpolated points
cpu_data
.iter_mut()
.zip(interpolated_cpu_points)
.for_each(|(cpu, interpolation)| {
if let Some((index, old_value)) = interpolation {
if let Some(to_replace) = cpu.cpu_data.get_mut(index) {
*to_replace = old_value;
}
}
});
}
}
fn draw_cpu_legend<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
// let recalculate_column_widths = app_state.should_get_widget_bounds();
// if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1)) {
// cpu_widget_state.is_legend_hidden = false;
// let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
// let cpu_table_state = &mut cpu_widget_state.scroll_state.table_state;
// let is_on_widget = widget_id == app_state.current_widget.widget_id;
// let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
// 0
// } else {
// app_state.app_config_fields.table_gap
// };
// let start_position = get_start_position(
// usize::from(
// (draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
// ),
// &cpu_widget_state.scroll_state.scroll_direction,
// &mut cpu_widget_state.scroll_state.previous_scroll_position,
// cpu_widget_state.scroll_state.current_scroll_position,
// app_state.is_force_redraw,
// );
// cpu_table_state.select(Some(
// cpu_widget_state
// .scroll_state
// .current_scroll_position
// .saturating_sub(start_position),
// ));
// let sliced_cpu_data = &cpu_data[start_position..];
// let offset_scroll_index = cpu_widget_state
// .scroll_state
// .current_scroll_position
// .saturating_sub(start_position);
// let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
// // Calculate widths
// if recalculate_column_widths {
// cpu_widget_state.table_width_state.desired_column_widths = vec![6, 4];
// cpu_widget_state.table_width_state.calculated_column_widths = get_column_widths(
// draw_loc.width,
// &[None, None],
// &(CPU_LEGEND_HEADER_LENS
// .iter()
// .map(|width| Some(*width))
// .collect::<Vec<_>>()),
// &[Some(0.5), Some(0.5)],
// &(cpu_widget_state
// .table_width_state
// .desired_column_widths
// .iter()
// .map(|width| Some(*width))
// .collect::<Vec<_>>()),
// false,
// );
// }
// let dcw = &cpu_widget_state.table_width_state.desired_column_widths;
// let ccw = &cpu_widget_state.table_width_state.calculated_column_widths;
// let cpu_rows = sliced_cpu_data.iter().enumerate().map(|(itx, cpu)| {
// let mut truncated_name =
// if let (Some(desired_column_width), Some(calculated_column_width)) =
// (dcw.get(0), ccw.get(0))
// {
// if *desired_column_width > *calculated_column_width {
// Text::raw(&cpu.short_cpu_name)
// } else {
// Text::raw(&cpu.cpu_name)
// }
// } else {
// Text::raw(&cpu.cpu_name)
// };
// let is_first_column_hidden = if let Some(calculated_column_width) = ccw.get(0) {
// *calculated_column_width == 0
// } else {
// false
// };
// let truncated_legend = if is_first_column_hidden && cpu.legend_value.is_empty() {
// // For the case where we only have room for one column, display "All" in the normally blank area.
// Text::raw("All")
// } else {
// Text::raw(&cpu.legend_value)
// };
// if !is_first_column_hidden
// && itx == offset_scroll_index
// && itx + start_position == ALL_POSITION
// {
// truncated_name.patch_style(painter.colours.currently_selected_text_style);
// Row::new(vec![truncated_name, truncated_legend])
// } else {
// let cpu_string_row = vec![truncated_name, truncated_legend];
// Row::new(cpu_string_row).style(if itx == offset_scroll_index {
// painter.colours.currently_selected_text_style
// } else if itx + start_position == ALL_POSITION {
// painter.colours.all_colour_style
// } else if show_avg_cpu {
// if itx + start_position == AVG_POSITION {
// painter.colours.avg_colour_style
// } else {
// painter.colours.cpu_colour_styles[(itx + start_position - AVG_POSITION - 1)
// % painter.colours.cpu_colour_styles.len()]
// }
// } else {
// painter.colours.cpu_colour_styles[(itx + start_position - ALL_POSITION - 1)
// % painter.colours.cpu_colour_styles.len()]
// })
// }
// });
// // Note we don't set highlight_style, as it should always be shown for this widget.
// let border_and_title_style = if is_on_widget {
// painter.colours.highlighted_border_style
// } else {
// painter.colours.border_style
// };
// // Draw
// f.render_stateful_widget(
// Table::new(cpu_rows)
// .block(
// Block::default()
// .borders(Borders::ALL)
// .border_style(border_and_title_style),
// )
// .header(
// Row::new(CPU_LEGEND_HEADER.to_vec())
// .style(painter.colours.table_header_style)
// .bottom_margin(table_gap),
// )
// .widths(
// &(cpu_widget_state
// .table_width_state
// .calculated_column_widths
// .iter()
// .map(|calculated_width| Constraint::Length(*calculated_width as u16))
// .collect::<Vec<_>>()),
// ),
// draw_loc,
// cpu_table_state,
// );
// }
}

View File

@ -1,266 +0,0 @@
use once_cell::sync::Lazy;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
text::Span,
text::{Spans, Text},
widgets::{Block, Borders, Row, Table},
};
use crate::{
app,
canvas::{
drawing_utils::{get_column_widths, get_start_position},
Painter,
},
constants::*,
};
use unicode_segmentation::UnicodeSegmentation;
const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"];
static DISK_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
DISK_HEADERS
.iter()
.map(|entry| entry.len() as u16)
.collect::<Vec<_>>()
});
pub fn draw_disk_table<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&disk_widget_state.scroll_state.scroll_direction,
&mut disk_widget_state.scroll_state.previous_scroll_position,
disk_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
let is_on_widget = app_state.current_widget.widget_id == widget_id;
let disk_table_state = &mut disk_widget_state.scroll_state.table_state;
disk_table_state.select(Some(
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &app_state.canvas_data.disk_data[start_position..];
// Calculate widths
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
if recalculate_column_widths {
disk_widget_state.table_width_state.desired_column_widths = {
let mut column_widths = DISK_HEADERS_LENS.clone();
for row in sliced_vec {
for (col, entry) in row.iter().enumerate() {
if entry.len() as u16 > column_widths[col] {
column_widths[col] = entry.len() as u16;
}
}
}
column_widths
};
disk_widget_state.table_width_state.desired_column_widths = disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.zip(&hard_widths)
.map(|(current, hard)| {
if let Some(hard) = hard {
if *hard > *current {
*hard
} else {
*current
}
} else {
*current
}
})
.collect::<Vec<_>>();
disk_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&hard_widths,
&(DISK_HEADERS_LENS
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
&[Some(0.2), Some(0.2), None, None, None, None, None],
&(disk_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|w| Some(*w))
.collect::<Vec<_>>()),
true,
);
}
let dcw = &disk_widget_state.table_width_state.desired_column_widths;
let ccw = &disk_widget_state.table_width_state.calculated_column_widths;
let disk_rows = sliced_vec.iter().map(|disk_row| {
let truncated_data =
disk_row
.iter()
.zip(&hard_widths)
.enumerate()
.map(|(itx, (entry, width))| {
if width.is_none() {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
{
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
return Text::raw(format!("{}", first_n));
}
}
}
}
Text::raw(entry)
});
Row::new(truncated_data)
});
let (border_style, highlight_style) = if is_on_widget {
(
painter.colours.highlighted_border_style,
painter.colours.currently_selected_text_style,
)
} else {
(painter.colours.border_style, painter.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Disk ({} of {}) ",
disk_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.disk_data.len()
);
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Disk ".to_string()
}
} else {
" Disk ".to_string()
};
let title = if app_state.is_expanded {
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Disk ".to_string(),
format!("{}{}", " Disk ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
};
let disk_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw!
f.render_stateful_widget(
Table::new(disk_rows)
.block(disk_block)
.header(
Row::new(DISK_HEADERS.to_vec())
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.highlight_style(highlight_style)
.style(painter.colours.text_style)
.widths(
&(disk_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
disk_table_state,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}
}

View File

@ -1,123 +0,0 @@
use crate::{
app::AppState,
canvas::{drawing_utils::*, Painter},
constants::*,
};
use tui::{
backend::Backend,
layout::{Constraint, Layout, Rect},
terminal::Frame,
text::Span,
text::Spans,
widgets::{Block, Paragraph},
};
pub fn draw_basic_memory<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
let margined_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(draw_loc);
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style),
draw_loc,
);
}
let ram_use_percentage = if let Some(mem) = mem_data.last() {
mem.1
} else {
0.0
};
let swap_use_percentage = if let Some(swap) = swap_data.last() {
swap.1
} 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.canvas_data.mem_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
let trimmed_swap_frac =
if let Some((_label_percent, label_frac)) = &app_state.canvas_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());
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
)
};
let mem_text = vec![
Spans::from(Span::styled(mem_label, painter.colours.ram_style)),
Spans::from(Span::styled(swap_label, painter.colours.swap_style)),
];
f.render_widget(
Paragraph::new(mem_text).block(Block::default()),
margined_loc[0],
);
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -1,240 +0,0 @@
use crate::{
app::AppState,
canvas::{drawing_utils::interpolate_points, Painter},
constants::*,
};
use tui::{
backend::Backend,
layout::{Constraint, Rect},
symbols::Marker,
terminal::Frame,
text::Span,
text::Spans,
widgets::{Axis, Block, Borders, Chart, Dataset},
};
use unicode_segmentation::UnicodeSegmentation;
pub fn draw_memory_graph<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
let mem_data: &mut [(f64, f64)] = &mut app_state.canvas_data.mem_data;
let swap_data: &mut [(f64, f64)] = &mut app_state.canvas_data.swap_data;
let time_start = -(mem_widget_state.current_display_time as f64);
let display_time_labels = vec![
Span::styled(
format!("{}s", mem_widget_state.current_display_time / 1000),
painter.colours.graph_style,
),
Span::styled("0s".to_string(), painter.colours.graph_style),
];
let y_axis_label = vec![
Span::styled(" 0%", painter.colours.graph_style),
Span::styled("100%", painter.colours.graph_style),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& mem_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = mem_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
} else {
mem_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
let y_axis = Axis::default()
.style(painter.colours.graph_style)
.bounds([0.0, 100.5])
.labels(y_axis_label);
// Interpolate values to avoid ugly gaps
let interpolated_mem_point = if let Some(end_pos) = mem_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = mem_data.get(start_pos);
let inside_point = mem_data.get(end_pos);
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) {
let old = *outside_point;
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
if let Some(to_replace) = mem_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Failed to get mutable reference.
}
} else {
None // Point somehow doesn't exist in our data
}
} else {
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
};
let interpolated_swap_point = if let Some(end_pos) = swap_data
.iter()
.position(|(time, _data)| *time >= time_start)
{
if end_pos > 1 {
let start_pos = end_pos - 1;
let outside_point = swap_data.get(start_pos);
let inside_point = swap_data.get(end_pos);
if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point) {
let old = *outside_point;
let new_point = (
time_start,
interpolate_points(outside_point, inside_point, time_start),
);
if let Some(to_replace) = swap_data.get_mut(start_pos) {
*to_replace = new_point;
Some((start_pos, old))
} else {
None // Failed to get mutable reference.
}
} else {
None // Point somehow doesn't exist in our data
}
} else {
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
};
let mut mem_canvas_vec: Vec<Dataset<'_>> = vec![];
if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
let mem_label = format!("RAM:{}{}", label_percent, label_frac);
mem_canvas_vec.push(
Dataset::default()
.name(mem_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.ram_style)
.data(mem_data)
.graph_type(tui::widgets::GraphType::Line),
);
}
if let Some((label_percent, label_frac)) = &app_state.canvas_data.swap_labels {
let swap_label = format!("SWP:{}{}", label_percent, label_frac);
mem_canvas_vec.push(
Dataset::default()
.name(swap_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.swap_style)
.data(swap_data)
.graph_type(tui::widgets::GraphType::Line),
);
}
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Memory ── Esc to go back ";
Spans::from(vec![
Span::styled(" Memory ", painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(Span::styled(
" Memory ".to_string(),
painter.colours.widget_title_style,
))
};
f.render_widget(
Chart::new(mem_canvas_vec)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))),
draw_loc,
);
// Now if you're done, reset any interpolated points!
if let Some((index, old_value)) = interpolated_mem_point {
if let Some(to_replace) = mem_data.get_mut(index) {
*to_replace = old_value;
}
}
if let Some((index, old_value)) = interpolated_swap_point {
if let Some(to_replace) = swap_data.get_mut(index) {
*to_replace = old_value;
}
}
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -1,71 +0,0 @@
use crate::{app::AppState, canvas::Painter, constants::*};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
text::{Span, Spans},
widgets::{Block, Paragraph},
};
pub fn draw_basic_network<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(draw_loc);
let net_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[0]);
let total_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[1]);
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style),
draw_loc,
);
}
let rx_label = format!("RX: {}", &app_state.canvas_data.rx_display);
let tx_label = format!("TX: {}", &app_state.canvas_data.tx_display);
let total_rx_label = format!("Total RX: {}", &app_state.canvas_data.total_rx_display);
let total_tx_label = format!("Total TX: {}", &app_state.canvas_data.total_tx_display);
let net_text = vec![
Spans::from(Span::styled(rx_label, painter.colours.rx_style)),
Spans::from(Span::styled(tx_label, painter.colours.tx_style)),
];
let total_net_text = vec![
Spans::from(Span::styled(total_rx_label, painter.colours.total_rx_style)),
Spans::from(Span::styled(total_tx_label, painter.colours.total_tx_style)),
];
f.render_widget(Paragraph::new(net_text).block(Block::default()), net_loc[0]);
f.render_widget(
Paragraph::new(total_net_text).block(Block::default()),
total_loc[0],
);
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}

View File

@ -1,758 +0,0 @@
use once_cell::sync::Lazy;
use std::cmp::max;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
app::{AppState, AxisScaling},
canvas::{
drawing_utils::{get_column_widths, interpolate_points},
Painter,
},
constants::*,
units::data_units::DataUnit,
utils::gen_util::*,
};
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
symbols::Marker,
terminal::Frame,
text::Span,
text::{Spans, Text},
widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table},
};
const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
static NETWORK_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
NETWORK_HEADERS
.iter()
.map(|entry| entry.len() as u16)
.collect::<Vec<_>>()
});
pub fn draw_network<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
if app_state.app_config_fields.use_old_network_legend {
let network_chunk = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([
Constraint::Length(max(draw_loc.height as i64 - 5, 0) as u16),
Constraint::Length(5),
])
.split(draw_loc);
draw_network_graph(painter, f, app_state, network_chunk[0], widget_id, true);
draw_network_labels(painter, f, app_state, network_chunk[1], widget_id);
} else {
draw_network_graph(painter, f, app_state, draw_loc, widget_id, false);
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
// Note that in both cases, we always go to the same widget id so it's fine to do it like
// this lol.
if let Some(network_widget) = app_state.widget_map.get_mut(&widget_id) {
network_widget.top_left_corner = Some((draw_loc.x, draw_loc.y));
network_widget.bottom_right_corner =
Some((draw_loc.x + draw_loc.width, draw_loc.y + draw_loc.height));
}
}
}
pub fn draw_network_graph<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64, hide_legend: bool,
) {
/// Point is of time, data
type Point = (f64, f64);
/// Returns the max data point and time given a time.
fn get_max_entry(
rx: &[Point], tx: &[Point], time_start: f64, network_scale_type: &AxisScaling,
network_use_binary_prefix: bool,
) -> (f64, f64) {
/// Determines a "fake" max value in circumstances where we couldn't find one from the data.
fn calculate_missing_max(
network_scale_type: &AxisScaling, network_use_binary_prefix: bool,
) -> f64 {
match network_scale_type {
AxisScaling::Log => {
if network_use_binary_prefix {
LOG_KIBI_LIMIT
} else {
LOG_KILO_LIMIT
}
}
AxisScaling::Linear => {
if network_use_binary_prefix {
KIBI_LIMIT_F64
} else {
KILO_LIMIT_F64
}
}
}
}
// First, let's shorten our ranges to actually look. We can abuse the fact that our rx and tx arrays
// are sorted, so we can short-circuit our search to filter out only the relevant data points...
let filtered_rx = if let (Some(rx_start), Some(rx_end)) = (
rx.iter().position(|(time, _data)| *time >= time_start),
rx.iter().rposition(|(time, _data)| *time <= 0.0),
) {
Some(&rx[rx_start..=rx_end])
} else {
None
};
let filtered_tx = if let (Some(tx_start), Some(tx_end)) = (
tx.iter().position(|(time, _data)| *time >= time_start),
tx.iter().rposition(|(time, _data)| *time <= 0.0),
) {
Some(&tx[tx_start..=tx_end])
} else {
None
};
// Then, find the maximal rx/tx so we know how to scale, and return it.
match (filtered_rx, filtered_tx) {
(None, None) => (
time_start,
calculate_missing_max(network_scale_type, network_use_binary_prefix),
),
(None, Some(filtered_tx)) => {
match filtered_tx
.iter()
.max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
{
Some((best_time, max_val)) => {
if *max_val == 0.0 {
(
time_start,
calculate_missing_max(
network_scale_type,
network_use_binary_prefix,
),
)
} else {
(*best_time, *max_val)
}
}
None => (
time_start,
calculate_missing_max(network_scale_type, network_use_binary_prefix),
),
}
}
(Some(filtered_rx), None) => {
match filtered_rx
.iter()
.max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
{
Some((best_time, max_val)) => {
if *max_val == 0.0 {
(
time_start,
calculate_missing_max(
network_scale_type,
network_use_binary_prefix,
),
)
} else {
(*best_time, *max_val)
}
}
None => (
time_start,
calculate_missing_max(network_scale_type, network_use_binary_prefix),
),
}
}
(Some(filtered_rx), Some(filtered_tx)) => {
match filtered_rx
.iter()
.chain(filtered_tx)
.max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
{
Some((best_time, max_val)) => {
if *max_val == 0.0 {
(
*best_time,
calculate_missing_max(
network_scale_type,
network_use_binary_prefix,
),
)
} else {
(*best_time, *max_val)
}
}
None => (
time_start,
calculate_missing_max(network_scale_type, network_use_binary_prefix),
),
}
}
}
}
/// Returns the required max data point and labels.
fn adjust_network_data_point(
max_entry: f64, network_scale_type: &AxisScaling, network_unit_type: &DataUnit,
network_use_binary_prefix: bool,
) -> (f64, Vec<String>) {
// So, we're going with an approach like this for linear data:
// - Main goal is to maximize the amount of information displayed given a specific height.
// We don't want to drown out some data if the ranges are too far though! Nor do we want to filter
// out too much data...
// - Change the y-axis unit (kilo/kibi, mega/mebi...) dynamically based on max load.
//
// The idea is we take the top value, build our scale such that each "point" is a scaled version of that.
// So for example, let's say I use 390 Mb/s. If I drew 4 segments, it would be 97.5, 195, 292.5, 390, and
// probably something like 438.75?
//
// So, how do we do this in tui-rs? Well, if we are using intervals that tie in perfectly to the max
// value we want... then it's actually not that hard. Since tui-rs accepts a vector as labels and will
// properly space them all out... we just work with that and space it out properly.
//
// Dynamic chart idea based off of FreeNAS's chart design.
//
// ===
//
// For log data, we just use the old method of log intervals (kilo/mega/giga/etc.). Keep it nice and simple.
// Now just check the largest unit we correspond to... then proceed to build some entries from there!
let unit_char = match network_unit_type {
DataUnit::Byte => "B",
DataUnit::Bit => "b",
};
match network_scale_type {
AxisScaling::Linear => {
let (k_limit, m_limit, g_limit, t_limit) = if network_use_binary_prefix {
(
KIBI_LIMIT_F64,
MEBI_LIMIT_F64,
GIBI_LIMIT_F64,
TEBI_LIMIT_F64,
)
} else {
(
KILO_LIMIT_F64,
MEGA_LIMIT_F64,
GIGA_LIMIT_F64,
TERA_LIMIT_F64,
)
};
let bumped_max_entry = max_entry * 1.5; // We use the bumped up version to calculate our unit type.
let (max_value_scaled, unit_prefix, unit_type): (f64, &str, &str) =
if bumped_max_entry < k_limit {
(max_entry, "", unit_char)
} else if bumped_max_entry < m_limit {
(
max_entry / k_limit,
if network_use_binary_prefix { "Ki" } else { "K" },
unit_char,
)
} else if bumped_max_entry < g_limit {
(
max_entry / m_limit,
if network_use_binary_prefix { "Mi" } else { "M" },
unit_char,
)
} else if bumped_max_entry < t_limit {
(
max_entry / g_limit,
if network_use_binary_prefix { "Gi" } else { "G" },
unit_char,
)
} else {
(
max_entry / t_limit,
if network_use_binary_prefix { "Ti" } else { "T" },
unit_char,
)
};
// Finally, build an acceptable range starting from there, using the given height!
// Note we try to put more of a weight on the bottom section vs. the top, since the top has less data.
let base_unit = max_value_scaled;
let labels: Vec<String> = vec![
format!("0{}{}", unit_prefix, unit_type),
format!("{:.1}", base_unit * 0.5),
format!("{:.1}", base_unit),
format!("{:.1}", base_unit * 1.5),
]
.into_iter()
.map(|s| format!("{:>5}", s)) // Pull 5 as the longest legend value is generally going to be 5 digits (if they somehow hit over 5 terabits per second)
.collect();
(bumped_max_entry, labels)
}
AxisScaling::Log => {
let (m_limit, g_limit, t_limit) = if network_use_binary_prefix {
(LOG_MEBI_LIMIT, LOG_GIBI_LIMIT, LOG_TEBI_LIMIT)
} else {
(LOG_MEGA_LIMIT, LOG_GIGA_LIMIT, LOG_TERA_LIMIT)
};
fn get_zero(network_use_binary_prefix: bool, unit_char: &str) -> String {
format!(
"{}0{}",
if network_use_binary_prefix { " " } else { " " },
unit_char
)
}
fn get_k(network_use_binary_prefix: bool, unit_char: &str) -> String {
format!(
"1{}{}",
if network_use_binary_prefix { "Ki" } else { "K" },
unit_char
)
}
fn get_m(network_use_binary_prefix: bool, unit_char: &str) -> String {
format!(
"1{}{}",
if network_use_binary_prefix { "Mi" } else { "M" },
unit_char
)
}
fn get_g(network_use_binary_prefix: bool, unit_char: &str) -> String {
format!(
"1{}{}",
if network_use_binary_prefix { "Gi" } else { "G" },
unit_char
)
}
fn get_t(network_use_binary_prefix: bool, unit_char: &str) -> String {
format!(
"1{}{}",
if network_use_binary_prefix { "Ti" } else { "T" },
unit_char
)
}
fn get_p(network_use_binary_prefix: bool, unit_char: &str) -> String {
format!(
"1{}{}",
if network_use_binary_prefix { "Pi" } else { "P" },
unit_char
)
}
if max_entry < m_limit {
(
m_limit,
vec![
get_zero(network_use_binary_prefix, unit_char),
get_k(network_use_binary_prefix, unit_char),
get_m(network_use_binary_prefix, unit_char),
],
)
} else if max_entry < g_limit {
(
g_limit,
vec![
get_zero(network_use_binary_prefix, unit_char),
get_k(network_use_binary_prefix, unit_char),
get_m(network_use_binary_prefix, unit_char),
get_g(network_use_binary_prefix, unit_char),
],
)
} else if max_entry < t_limit {
(
t_limit,
vec![
get_zero(network_use_binary_prefix, unit_char),
get_k(network_use_binary_prefix, unit_char),
get_m(network_use_binary_prefix, unit_char),
get_g(network_use_binary_prefix, unit_char),
get_t(network_use_binary_prefix, unit_char),
],
)
} else {
// I really doubt anyone's transferring beyond petabyte speeds...
(
if network_use_binary_prefix {
LOG_PEBI_LIMIT
} else {
LOG_PETA_LIMIT
},
vec![
get_zero(network_use_binary_prefix, unit_char),
get_k(network_use_binary_prefix, unit_char),
get_m(network_use_binary_prefix, unit_char),
get_g(network_use_binary_prefix, unit_char),
get_t(network_use_binary_prefix, unit_char),
get_p(network_use_binary_prefix, unit_char),
],
)
}
}
}
}
if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) {
let network_data_rx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_rx;
let network_data_tx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_tx;
let time_start = -(network_widget_state.current_display_time as f64);
let display_time_labels = vec![
Span::styled(
format!("{}s", network_widget_state.current_display_time / 1000),
painter.colours.graph_style,
),
Span::styled("0s".to_string(), painter.colours.graph_style),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& network_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = network_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
} else {
network_widget_state.autohide_timer = None;
Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
.bounds([time_start, 0.0])
.style(painter.colours.graph_style)
.labels(display_time_labels)
};
// Interpolate a point for rx and tx between the last value outside of the left bounds and the first value
// inside it.
// Because we assume it is all in order for... basically all our code, we can't just append it,
// and insertion in the middle seems. So instead, we swap *out* the value that is outside with our
// interpolated point, draw and do whatever calculations, then swap back in the old value!
//
// Note there is some re-used work here! For potential optimizations, we could re-use some work here in/from
// get_max_entry...
let interpolated_rx_point = if let Some(rx_end_pos) = network_data_rx
.iter()
.position(|(time, _data)| *time >= time_start)
{
if rx_end_pos > 1 {
let rx_start_pos = rx_end_pos - 1;
let outside_rx_point = network_data_rx.get(rx_start_pos);
let inside_rx_point = network_data_rx.get(rx_end_pos);
if let (Some(outside_rx_point), Some(inside_rx_point)) =
(outside_rx_point, inside_rx_point)
{
let old = *outside_rx_point;
let new_point = (
time_start,
interpolate_points(outside_rx_point, inside_rx_point, time_start),
);
// debug!(
// "Interpolated between {:?} and {:?}, got rx for time {:?}: {:?}",
// outside_rx_point, inside_rx_point, time_start, new_point
// );
if let Some(to_replace) = network_data_rx.get_mut(rx_start_pos) {
*to_replace = new_point;
Some((rx_start_pos, old))
} else {
None // Failed to get mutable reference.
}
} else {
None // Point somehow doesn't exist in our network_data_rx
}
} else {
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
};
let interpolated_tx_point = if let Some(tx_end_pos) = network_data_tx
.iter()
.position(|(time, _data)| *time >= time_start)
{
if tx_end_pos > 1 {
let tx_start_pos = tx_end_pos - 1;
let outside_tx_point = network_data_tx.get(tx_start_pos);
let inside_tx_point = network_data_tx.get(tx_end_pos);
if let (Some(outside_tx_point), Some(inside_tx_point)) =
(outside_tx_point, inside_tx_point)
{
let old = *outside_tx_point;
let new_point = (
time_start,
interpolate_points(outside_tx_point, inside_tx_point, time_start),
);
if let Some(to_replace) = network_data_tx.get_mut(tx_start_pos) {
*to_replace = new_point;
Some((tx_start_pos, old))
} else {
None // Failed to get mutable reference.
}
} else {
None // Point somehow doesn't exist in our network_data_tx
}
} else {
None // Point is already "leftmost", no need to interpolate.
}
} else {
None // There is no point.
};
// TODO: Cache network results: Only update if:
// - Force update (includes time interval change)
// - Old max time is off screen
// - A new time interval is better and does not fit (check from end of vector to last checked; we only want to update if it is TOO big!)
// Find the maximal rx/tx so we know how to scale, and return it.
let (_best_time, max_entry) = get_max_entry(
network_data_rx,
network_data_tx,
time_start,
&app_state.app_config_fields.network_scale_type,
app_state.app_config_fields.network_use_binary_prefix,
);
let (max_range, labels) = adjust_network_data_point(
max_entry,
&app_state.app_config_fields.network_scale_type,
&app_state.app_config_fields.network_unit_type,
app_state.app_config_fields.network_use_binary_prefix,
);
// Cache results.
// network_widget_state.draw_max_range_cache = max_range;
// network_widget_state.draw_time_start_cache = best_time;
// network_widget_state.draw_labels_cache = labels;
let y_axis_labels = labels
.iter()
.map(|label| Span::styled(label, painter.colours.graph_style))
.collect::<Vec<_>>();
let y_axis = Axis::default()
.style(painter.colours.graph_style)
.bounds([0.0, max_range])
.labels(y_axis_labels);
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let border_style = if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Network ── Esc to go back ";
Spans::from(vec![
Span::styled(" Network ", painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
))
),
border_style,
),
])
} else {
Spans::from(Span::styled(
" Network ",
painter.colours.widget_title_style,
))
};
let legend_constraints = if hide_legend {
(Constraint::Ratio(0, 1), Constraint::Ratio(0, 1))
} else {
(Constraint::Ratio(1, 1), Constraint::Ratio(3, 4))
};
// TODO: Add support for clicking on legend to only show that value on chart.
let dataset = if app_state.app_config_fields.use_old_network_legend && !hide_legend {
vec![
Dataset::default()
.name(format!("RX: {:7}", app_state.canvas_data.rx_display))
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.rx_style)
.data(network_data_rx)
.graph_type(tui::widgets::GraphType::Line),
Dataset::default()
.name(format!("TX: {:7}", app_state.canvas_data.tx_display))
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.tx_style)
.data(network_data_tx)
.graph_type(tui::widgets::GraphType::Line),
Dataset::default()
.name(format!(
"Total RX: {:7}",
app_state.canvas_data.total_rx_display
))
.style(painter.colours.total_rx_style),
Dataset::default()
.name(format!(
"Total TX: {:7}",
app_state.canvas_data.total_tx_display
))
.style(painter.colours.total_tx_style),
]
} else {
vec![
Dataset::default()
.name(&app_state.canvas_data.rx_display)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.rx_style)
.data(network_data_rx)
.graph_type(tui::widgets::GraphType::Line),
Dataset::default()
.name(&app_state.canvas_data.tx_display)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(painter.colours.tx_style)
.data(network_data_tx)
.graph_type(tui::widgets::GraphType::Line),
]
};
f.render_widget(
Chart::new(dataset)
.block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.hidden_legend_constraints(legend_constraints),
draw_loc,
);
// Now if you're done, reset any interpolated points!
if let Some((index, old_value)) = interpolated_rx_point {
if let Some(to_replace) = network_data_rx.get_mut(index) {
*to_replace = old_value;
}
}
if let Some((index, old_value)) = interpolated_tx_point {
if let Some(to_replace) = network_data_tx.get_mut(index) {
*to_replace = old_value;
}
}
}
}
fn draw_network_labels<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
widget_id: u64,
) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let rx_display = &app_state.canvas_data.rx_display;
let tx_display = &app_state.canvas_data.tx_display;
let total_rx_display = &app_state.canvas_data.total_rx_display;
let total_tx_display = &app_state.canvas_data.total_tx_display;
// Gross but I need it to work...
let total_network = vec![vec![
Text::raw(rx_display),
Text::raw(tx_display),
Text::raw(total_rx_display),
Text::raw(total_tx_display),
]];
let mapped_network = total_network
.into_iter()
.map(|val| Row::new(val).style(painter.colours.text_style));
// Calculate widths
let intrinsic_widths = get_column_widths(
draw_loc.width,
&[None, None, None, None],
&(NETWORK_HEADERS_LENS
.iter()
.map(|s| Some(*s))
.collect::<Vec<_>>()),
&[Some(0.25); 4],
&(NETWORK_HEADERS_LENS
.iter()
.map(|s| Some(*s))
.collect::<Vec<_>>()),
true,
);
// Draw
f.render_widget(
Table::new(mapped_network)
.header(
Row::new(NETWORK_HEADERS.to_vec())
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.block(Block::default().borders(Borders::ALL).border_style(
if app_state.current_widget.widget_id == widget_id {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
},
))
.style(painter.colours.text_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
draw_loc,
);
}

View File

@ -1,856 +0,0 @@
use crate::{
app::AppState,
canvas::{
drawing_utils::{get_column_widths, get_search_start_position, get_start_position},
Painter,
},
constants::*,
};
use indextree::NodeId;
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
terminal::Frame,
text::{Span, Spans, Text},
widgets::{Block, Borders, Paragraph, Row, Table},
};
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
use unicode_width::UnicodeWidthStr;
use once_cell::sync::Lazy;
static PROCESS_HEADERS_HARD_WIDTH_NO_GROUP: Lazy<Vec<Option<u16>>> = Lazy::new(|| {
vec![
Some(7),
None,
Some(8),
Some(8),
Some(8),
Some(8),
Some(7),
Some(8),
#[cfg(target_family = "unix")]
None,
None,
]
});
static PROCESS_HEADERS_HARD_WIDTH_GROUPED: Lazy<Vec<Option<u16>>> = Lazy::new(|| {
vec![
Some(7),
None,
Some(8),
Some(8),
Some(8),
Some(8),
Some(7),
Some(8),
]
});
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND: Lazy<Vec<Option<f64>>> =
Lazy::new(|| vec![None, Some(0.7), None, None, None, None, None, None]);
static PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE: Lazy<Vec<Option<f64>>> =
Lazy::new(|| vec![None, Some(0.3), None, None, None, None, None, None]);
static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND: Lazy<Vec<Option<f64>>> = Lazy::new(|| {
vec![
None,
Some(0.7),
None,
None,
None,
None,
None,
None,
#[cfg(target_family = "unix")]
Some(0.05),
Some(0.2),
]
});
static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE: Lazy<Vec<Option<f64>>> = Lazy::new(|| {
vec![
None,
Some(0.5),
None,
None,
None,
None,
None,
None,
#[cfg(target_family = "unix")]
Some(0.05),
Some(0.2),
]
});
static PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE: Lazy<Vec<Option<f64>>> = Lazy::new(|| {
vec![
None,
Some(0.3),
None,
None,
None,
None,
None,
None,
#[cfg(target_family = "unix")]
Some(0.05),
Some(0.2),
]
});
pub fn draw_process_features<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
draw_border: bool, widget_id: NodeId,
) {
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&1) {
let search_height = if draw_border { 5 } else { 3 };
let is_sort_open = process_widget_state.is_sort_open;
let header_len = process_widget_state.columns.longest_header_len;
let mut proc_draw_loc = draw_loc;
if process_widget_state.is_search_enabled() {
let processes_chunk = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(search_height)])
.split(draw_loc);
proc_draw_loc = processes_chunk[0];
draw_search_field(
painter,
f,
app_state,
processes_chunk[1],
draw_border,
widget_id,
);
}
if is_sort_open {
let processes_chunk = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(header_len + 4), Constraint::Min(0)])
.split(proc_draw_loc);
proc_draw_loc = processes_chunk[1];
draw_process_sort(painter, f, app_state, processes_chunk[0], draw_border, 1);
}
draw_processes_table(painter, f, app_state, proc_draw_loc, draw_border, widget_id);
}
}
fn draw_processes_table<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
draw_border: bool, widget_id: NodeId,
) {
let should_get_widget_bounds = app_state.should_get_widget_bounds();
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&1) {
let recalculate_column_widths =
should_get_widget_bounds || proc_widget_state.requires_redraw;
if proc_widget_state.requires_redraw {
proc_widget_state.requires_redraw = false;
}
let is_on_widget = false;
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
let (border_style, highlight_style) = if is_on_widget {
(
painter.colours.highlighted_border_style,
painter.colours.currently_selected_text_style,
)
} else {
(painter.colours.border_style, painter.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
if let Some(finalized_process_data) = app_state
.canvas_data
.stringified_process_data_map
.get(&widget_id)
{
let title = format!(
" Processes ({} of {}) ",
proc_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
finalized_process_data.len()
);
if title.len() <= draw_loc.width as usize {
title
} else {
" Processes ".to_string()
}
} else {
" Processes ".to_string()
}
} else {
" Processes ".to_string()
};
let title = if app_state.is_expanded
&& !proc_widget_state
.process_search_state
.search_state
.is_enabled
&& !proc_widget_state.is_sort_open
{
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Processes ".to_string(),
format!("{}{}", " Processes ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
};
let process_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
if let Some(process_data) = &app_state
.canvas_data
.stringified_process_data_map
.get(&widget_id)
{
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&proc_widget_state.scroll_state.scroll_direction,
&mut proc_widget_state.scroll_state.previous_scroll_position,
proc_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
// Sanity check
let start_position = if position >= process_data.len() {
process_data.len().saturating_sub(1)
} else {
position
};
let sliced_vec = &process_data[start_position..];
let processed_sliced_vec = sliced_vec.iter().map(|(data, disabled)| {
(
data.iter()
.map(|(entry, _alternative)| entry)
.collect::<Vec<_>>(),
disabled,
)
});
let proc_table_state = &mut proc_widget_state.scroll_state.table_state;
proc_table_state.select(Some(
proc_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
// Draw!
let process_headers = proc_widget_state.columns.get_column_headers(
&proc_widget_state.process_sorting_type,
proc_widget_state.is_process_sort_descending,
);
// Calculate widths
// FIXME: See if we can move this into the recalculate block? I want to move column widths into the column widths
let hard_widths = if proc_widget_state.is_grouped {
&*PROCESS_HEADERS_HARD_WIDTH_GROUPED
} else {
&*PROCESS_HEADERS_HARD_WIDTH_NO_GROUP
};
if recalculate_column_widths {
let mut column_widths = process_headers
.iter()
.map(|entry| UnicodeWidthStr::width(entry.as_str()) as u16)
.collect::<Vec<_>>();
let soft_widths_min = column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>();
proc_widget_state.table_width_state.desired_column_widths = {
for (row, _disabled) in processed_sliced_vec.clone() {
for (col, entry) in row.iter().enumerate() {
if let Some(col_width) = column_widths.get_mut(col) {
let grapheme_len = UnicodeWidthStr::width(entry.as_str());
if grapheme_len as u16 > *col_width {
*col_width = grapheme_len as u16;
}
}
}
}
column_widths
};
proc_widget_state.table_width_state.desired_column_widths = proc_widget_state
.table_width_state
.desired_column_widths
.iter()
.zip(hard_widths)
.map(|(current, hard)| {
if let Some(hard) = hard {
if *hard > *current {
*hard
} else {
*current
}
} else {
*current
}
})
.collect::<Vec<_>>();
let soft_widths_max = if proc_widget_state.is_grouped {
// Note grouped trees are not a thing.
if proc_widget_state.is_using_command {
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_COMMAND
} else {
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_GROUPED_ELSE
}
} else if proc_widget_state.is_using_command {
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_COMMAND
} else if proc_widget_state.is_tree_mode {
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_TREE
} else {
&*PROCESS_HEADERS_SOFT_WIDTH_MAX_NO_GROUP_ELSE
};
proc_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
hard_widths,
&soft_widths_min,
soft_widths_max,
&(proc_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
true,
);
// debug!(
// "DCW: {:?}",
// proc_widget_state.table_width_state.desired_column_widths
// );
// debug!(
// "CCW: {:?}",
// proc_widget_state.table_width_state.calculated_column_widths
// );
}
let dcw = &proc_widget_state.table_width_state.desired_column_widths;
let ccw = &proc_widget_state.table_width_state.calculated_column_widths;
let process_rows = sliced_vec.iter().map(|(data, disabled)| {
let truncated_data = data.iter().zip(hard_widths).enumerate().map(
|(itx, ((entry, alternative), width))| {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
{
if width.is_none() {
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
if let Some(alternative) = alternative {
Text::raw(alternative)
} else if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
Text::raw(format!("{}", first_n))
} else {
Text::raw(entry)
}
} else {
Text::raw(entry)
}
} else {
Text::raw(entry)
}
} else {
Text::raw(entry)
}
},
);
if *disabled {
Row::new(truncated_data).style(painter.colours.disabled_text_style)
} else {
Row::new(truncated_data)
}
});
f.render_stateful_widget(
Table::new(process_rows)
.header(
Row::new(process_headers)
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.block(process_block)
.highlight_style(highlight_style)
.style(painter.colours.text_style)
.widths(
&(proc_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
proc_table_state,
);
} else {
f.render_widget(process_block, margined_draw_loc);
}
// Check if we need to update columnar bounds...
if recalculate_column_widths
|| proc_widget_state.columns.column_header_x_locs.is_none()
|| proc_widget_state.columns.column_header_y_loc.is_none()
{
// y location is just the y location of the widget + border size (1 normally, 0 in basic)
proc_widget_state.columns.column_header_y_loc =
Some(draw_loc.y + if draw_border { 1 } else { 0 });
// x location is determined using the x locations of the widget; just offset from the left bound
// as appropriate, and use the right bound as limiter.
let mut current_x_left = draw_loc.x + 1;
let max_x_right = draw_loc.x + draw_loc.width - 1;
let mut x_locs = vec![];
for width in proc_widget_state
.table_width_state
.calculated_column_widths
.iter()
{
let right_bound = current_x_left + width;
if right_bound < max_x_right {
x_locs.push((current_x_left, right_bound));
current_x_left = right_bound + 1;
} else {
x_locs.push((current_x_left, max_x_right));
break;
}
}
proc_widget_state.columns.column_header_x_locs = Some(x_locs);
}
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&1) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}
}
fn draw_search_field<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
draw_border: bool, _widget_id: NodeId,
) {
fn build_query<'a>(
is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize,
cursor_position: usize, query: &str, currently_selected_text_style: tui::style::Style,
text_style: tui::style::Style,
) -> Vec<Span<'a>> {
let mut current_grapheme_posn = 0;
if is_on_widget {
let mut res = grapheme_indices
.filter_map(|grapheme| {
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
if current_grapheme_posn <= start_position {
None
} else {
let styled = if grapheme.0 == cursor_position {
Span::styled(grapheme.1, currently_selected_text_style)
} else {
Span::styled(grapheme.1, text_style)
};
Some(styled)
}
})
.collect::<Vec<_>>();
if cursor_position == query.len() {
res.push(Span::styled(" ", currently_selected_text_style))
}
res
} else {
// This is easier - we just need to get a range of graphemes, rather than
// dealing with possibly inserting a cursor (as none is shown!)
vec![Span::styled(query.to_string(), text_style)]
}
}
// TODO: Make the cursor scroll back if there's space!
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&1) {
let is_on_widget = false;
let num_columns = usize::from(draw_loc.width);
let search_title = "> ";
let num_chars_for_text = search_title.len();
let cursor_position = proc_widget_state.get_search_cursor_position();
let current_cursor_position = proc_widget_state.get_char_cursor_position();
let start_position: usize = get_search_start_position(
num_columns - num_chars_for_text - 5,
&proc_widget_state
.process_search_state
.search_state
.cursor_direction,
&mut proc_widget_state
.process_search_state
.search_state
.cursor_bar,
current_cursor_position,
app_state.is_force_redraw,
);
let query = proc_widget_state.get_current_search_query().as_str();
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
// TODO: [CURSOR] blank cursor if not selected
// TODO: [CURSOR] blinking cursor?
let query_with_cursor = build_query(
is_on_widget,
grapheme_indices,
start_position,
cursor_position,
query,
painter.colours.currently_selected_text_style,
painter.colours.text_style,
);
let mut search_text = vec![Spans::from({
let mut search_vec = vec![Span::styled(
search_title,
if is_on_widget {
painter.colours.table_header_style
} else {
painter.colours.text_style
},
)];
search_vec.extend(query_with_cursor);
search_vec
})];
// Text options shamelessly stolen from VS Code.
let case_style = if !proc_widget_state.process_search_state.is_ignoring_case {
painter.colours.currently_selected_text_style
} else {
painter.colours.text_style
};
let whole_word_style = if proc_widget_state
.process_search_state
.is_searching_whole_word
{
painter.colours.currently_selected_text_style
} else {
painter.colours.text_style
};
let regex_style = if proc_widget_state
.process_search_state
.is_searching_with_regex
{
painter.colours.currently_selected_text_style
} else {
painter.colours.text_style
};
// FIXME: [MOUSE] Mouse support for these in search
// FIXME: [MOVEMENT] Movement support for these in search
let option_text = Spans::from(vec![
Span::styled(
format!("Case({})", if painter.is_mac_os { "F1" } else { "Alt+C" }),
case_style,
),
Span::raw(" "),
Span::styled(
format!("Whole({})", if painter.is_mac_os { "F2" } else { "Alt+W" }),
whole_word_style,
),
Span::raw(" "),
Span::styled(
format!("Regex({})", if painter.is_mac_os { "F3" } else { "Alt+R" }),
regex_style,
),
]);
search_text.push(Spans::from(Span::styled(
if let Some(err) = &proc_widget_state
.process_search_state
.search_state
.error_message
{
err.as_str()
} else {
""
},
painter.colours.invalid_query_style,
)));
search_text.push(option_text);
let current_border_style = if proc_widget_state
.process_search_state
.search_state
.is_invalid_search
{
painter.colours.invalid_query_style
} else if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let title = Span::styled(
if draw_border {
const TITLE_BASE: &str = " Esc to close ";
let repeat_num =
usize::from(draw_loc.width).saturating_sub(TITLE_BASE.chars().count() + 2);
format!("{} Esc to close ", "".repeat(repeat_num))
} else {
String::new()
},
current_border_style,
);
let process_search_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(current_border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(current_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
f.render_widget(
Paragraph::new(search_text)
.block(process_search_block)
.style(painter.colours.text_style)
.alignment(Alignment::Left),
margined_draw_loc,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&1) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}
}
fn draw_process_sort<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let is_on_widget = widget_id == app_state.current_widget.widget_id;
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&(widget_id - 2)) {
let current_scroll_position = proc_widget_state.columns.current_scroll_position;
let sort_string = proc_widget_state
.columns
.ordered_columns
.iter()
.filter(|column_type| {
proc_widget_state
.columns
.column_mapping
.get(column_type)
.unwrap()
.enabled
})
.map(|column_type| column_type.to_string())
.collect::<Vec<_>>();
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&proc_widget_state.columns.scroll_direction,
&mut proc_widget_state.columns.previous_scroll_position,
current_scroll_position,
app_state.is_force_redraw,
);
// Sanity check
let start_position = if position >= sort_string.len() {
sort_string.len().saturating_sub(1)
} else {
position
};
let sliced_vec = &sort_string[start_position..];
let sort_options = sliced_vec
.iter()
.map(|column| Row::new(vec![column.as_str()]));
let column_state = &mut proc_widget_state.columns.column_state;
column_state.select(Some(
proc_widget_state
.columns
.current_scroll_position
.saturating_sub(start_position),
));
let current_border_style = if proc_widget_state
.process_search_state
.search_state
.is_invalid_search
{
painter.colours.invalid_query_style
} else if is_on_widget {
painter.colours.highlighted_border_style
} else {
painter.colours.border_style
};
let process_sort_block = if draw_border {
Block::default()
.borders(Borders::ALL)
.border_style(current_border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(current_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let highlight_style = if is_on_widget {
painter.colours.currently_selected_text_style
} else {
painter.colours.text_style
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
f.render_stateful_widget(
Table::new(sort_options)
.header(
Row::new(vec!["Sort By"])
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.block(process_sort_block)
.highlight_style(highlight_style)
.style(painter.colours.text_style)
.widths(&[Constraint::Percentage(100)]),
margined_draw_loc,
column_state,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}
}

View File

@ -1,256 +0,0 @@
use once_cell::sync::Lazy;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
text::Span,
text::{Spans, Text},
widgets::{Block, Borders, Row, Table},
};
use crate::{
app,
canvas::{
drawing_utils::{get_column_widths, get_start_position},
Painter,
},
constants::*,
};
use unicode_segmentation::UnicodeSegmentation;
const TEMP_HEADERS: [&str; 2] = ["Sensor", "Temp"];
static TEMP_HEADERS_LENS: Lazy<Vec<u16>> = Lazy::new(|| {
TEMP_HEADERS
.iter()
.map(|entry| entry.len() as u16)
.collect::<Vec<_>>()
});
pub fn draw_temp_table<B: Backend>(
painter: &Painter, f: &mut Frame<'_, B>, app_state: &mut app::AppState, draw_loc: Rect,
draw_border: bool, widget_id: u64,
) {
let recalculate_column_widths = app_state.should_get_widget_bounds();
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
0
} else {
app_state.app_config_fields.table_gap
};
let start_position = get_start_position(
usize::from(
(draw_loc.height + (1 - table_gap)).saturating_sub(painter.table_height_offset),
),
&temp_widget_state.scroll_state.scroll_direction,
&mut temp_widget_state.scroll_state.previous_scroll_position,
temp_widget_state.scroll_state.current_scroll_position,
app_state.is_force_redraw,
);
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let temp_table_state = &mut temp_widget_state.scroll_state.table_state;
temp_table_state.select(Some(
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_sub(start_position),
));
let sliced_vec = &app_state.canvas_data.temp_sensor_data[start_position..];
// Calculate widths
let hard_widths = [None, None];
if recalculate_column_widths {
temp_widget_state.table_width_state.desired_column_widths = {
let mut column_widths = TEMP_HEADERS_LENS.clone();
for row in sliced_vec {
for (col, entry) in row.iter().enumerate() {
if entry.len() as u16 > column_widths[col] {
column_widths[col] = entry.len() as u16;
}
}
}
column_widths
};
temp_widget_state.table_width_state.calculated_column_widths = get_column_widths(
draw_loc.width,
&hard_widths,
&(TEMP_HEADERS_LENS
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>()),
&[Some(0.80), Some(-1.0)],
&temp_widget_state
.table_width_state
.desired_column_widths
.iter()
.map(|width| Some(*width))
.collect::<Vec<_>>(),
false,
);
}
let dcw = &temp_widget_state.table_width_state.desired_column_widths;
let ccw = &temp_widget_state.table_width_state.calculated_column_widths;
let temperature_rows = sliced_vec.iter().map(|temp_row| {
let truncated_data =
temp_row
.iter()
.zip(&hard_widths)
.enumerate()
.map(|(itx, (entry, width))| {
if width.is_none() {
if let (Some(desired_col_width), Some(calculated_col_width)) =
(dcw.get(itx), ccw.get(itx))
{
if *desired_col_width > *calculated_col_width
&& *calculated_col_width > 0
{
let graphemes =
UnicodeSegmentation::graphemes(entry.as_str(), true)
.collect::<Vec<&str>>();
if graphemes.len() > *calculated_col_width as usize
&& *calculated_col_width > 1
{
// Truncate with ellipsis
let first_n = graphemes
[..(*calculated_col_width as usize - 1)]
.concat();
Text::raw(format!("{}", first_n))
} else {
Text::raw(entry)
}
} else {
Text::raw(entry)
}
} else {
Text::raw(entry)
}
} else {
Text::raw(entry)
}
});
Row::new(truncated_data)
});
let (border_style, highlight_style) = if is_on_widget {
(
painter.colours.highlighted_border_style,
painter.colours.currently_selected_text_style,
)
} else {
(painter.colours.border_style, painter.colours.text_style)
};
let title_base = if app_state.app_config_fields.show_table_scroll_position {
let title_string = format!(
" Temperatures ({} of {}) ",
temp_widget_state
.scroll_state
.current_scroll_position
.saturating_add(1),
app_state.canvas_data.temp_sensor_data.len()
);
if title_string.len() <= draw_loc.width as usize {
title_string
} else {
" Temperatures ".to_string()
}
} else {
" Temperatures ".to_string()
};
let title = if app_state.is_expanded {
const ESCAPE_ENDING: &str = "── Esc to go back ";
let (chosen_title_base, expanded_title_base) = {
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
if temp_title_base.len() > draw_loc.width as usize {
(
" Temperatures ".to_string(),
format!("{}{}", " Temperatures ".to_string(), ESCAPE_ENDING),
)
} else {
(title_base, temp_title_base)
}
};
Spans::from(vec![
Span::styled(chosen_title_base, painter.colours.widget_title_style),
Span::styled(
format!(
"─{}─ Esc to go back ",
"".repeat(
usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(expanded_title_base.as_str(), true)
.count()
+ 2
)
)
),
border_style,
),
])
} else {
Spans::from(Span::styled(title_base, painter.colours.widget_title_style))
};
let temp_block = if draw_border {
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(painter.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)])
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc)[0];
// Draw
f.render_stateful_widget(
Table::new(temperature_rows)
.header(
Row::new(TEMP_HEADERS.to_vec())
.style(painter.colours.table_header_style)
.bottom_margin(table_gap),
)
.block(temp_block)
.highlight_style(highlight_style)
.style(painter.colours.text_style)
.widths(
&(temp_widget_state
.table_width_state
.calculated_column_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
),
margined_draw_loc,
temp_table_state,
);
if app_state.should_get_widget_bounds() {
// Update draw loc in widget map
// Note there is no difference between this and using draw_loc, but I'm too lazy to fix it.
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
widget.bottom_right_corner = Some((
margined_draw_loc.x + margined_draw_loc.width,
margined_draw_loc.y + margined_draw_loc.height,
));
}
}
}
}

View File

@ -1,211 +0,0 @@
use std::cmp::{max, min};
/// Return a (hard)-width vector for column widths.
///
/// * `total_width` is the, well, total width available. **NOTE:** This function automatically
/// takes away 2 from the width as part of the left/right
/// bounds.
/// * `hard_widths` is inflexible column widths. Use a `None` to represent a soft width.
/// * `soft_widths_min` is the lower limit for a soft width. Use `None` if a hard width goes there.
/// * `soft_widths_max` is the upper limit for a soft width, in percentage of the total width. Use
/// `None` if a hard width goes there.
/// * `soft_widths_desired` is the desired soft width. Use `None` if a hard width goes there.
/// * `left_to_right` is a boolean whether to go from left to right if true, or right to left if
/// false.
///
/// **NOTE:** This function ASSUMES THAT ALL PASSED SLICES ARE OF THE SAME SIZE.
///
/// **NOTE:** The returned vector may not be the same size as the slices, this is because including
/// 0-constraints breaks tui-rs.
pub fn get_column_widths(
total_width: u16, hard_widths: &[Option<u16>], soft_widths_min: &[Option<u16>],
soft_widths_max: &[Option<f64>], soft_widths_desired: &[Option<u16>], left_to_right: bool,
) -> Vec<u16> {
debug_assert!(
hard_widths.len() == soft_widths_min.len(),
"hard width length != soft width min length!"
);
debug_assert!(
soft_widths_min.len() == soft_widths_max.len(),
"soft width min length != soft width max length!"
);
debug_assert!(
soft_widths_max.len() == soft_widths_desired.len(),
"soft width max length != soft width desired length!"
);
let initial_width = total_width - 2;
let mut total_width_left = initial_width;
let mut column_widths: Vec<u16> = vec![0; hard_widths.len()];
let range: Vec<usize> = if left_to_right {
(0..hard_widths.len()).collect()
} else {
(0..hard_widths.len()).rev().collect()
};
for itx in &range {
if let Some(Some(hard_width)) = hard_widths.get(*itx) {
// Hard width...
let space_taken = min(*hard_width, total_width_left);
// TODO [COLUMN MOVEMENT]: Remove this
if *hard_width > space_taken {
break;
}
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
total_width_left = total_width_left.saturating_sub(1);
} else if let (
Some(Some(soft_width_max)),
Some(Some(soft_width_min)),
Some(Some(soft_width_desired)),
) = (
soft_widths_max.get(*itx),
soft_widths_min.get(*itx),
soft_widths_desired.get(*itx),
) {
// Soft width...
let soft_limit = max(
if soft_width_max.is_sign_negative() {
*soft_width_desired
} else {
(*soft_width_max * initial_width as f64).ceil() as u16
},
*soft_width_min,
);
let space_taken = min(min(soft_limit, *soft_width_desired), total_width_left);
// TODO [COLUMN MOVEMENT]: Remove this
if *soft_width_min > space_taken {
break;
}
column_widths[*itx] = space_taken;
total_width_left -= space_taken;
total_width_left = total_width_left.saturating_sub(1);
}
}
// Redistribute remaining.
while total_width_left > 0 {
for itx in &range {
if column_widths[*itx] > 0 {
column_widths[*itx] += 1;
total_width_left -= 1;
if total_width_left == 0 {
break;
}
}
}
}
let mut filtered_column_widths: Vec<u16> = vec![];
let mut still_seeing_zeros = true;
column_widths.iter().rev().for_each(|width| {
if still_seeing_zeros {
if *width != 0 {
still_seeing_zeros = false;
filtered_column_widths.push(*width);
}
} else {
filtered_column_widths.push(*width);
}
});
filtered_column_widths.reverse();
filtered_column_widths
}
// pub fn get_search_start_position(
// num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
// current_cursor_position: usize, is_force_redraw: bool,
// ) -> usize {
// if is_force_redraw {
// *cursor_bar = 0;
// }
// match cursor_direction {
// app::CursorDirection::Right => {
// if current_cursor_position < *cursor_bar + num_columns {
// // If, using previous_scrolled_position, we can see the element
// // (so within that and + num_rows) just reuse the current previously scrolled position
// *cursor_bar
// } else if current_cursor_position >= num_columns {
// // Else if the current position past the last element visible in the list, omit
// // until we can see that element
// *cursor_bar = current_cursor_position - num_columns;
// *cursor_bar
// } else {
// // Else, if it is not past the last element visible, do not omit anything
// 0
// }
// }
// app::CursorDirection::Left => {
// if current_cursor_position <= *cursor_bar {
// // If it's past the first element, then show from that element downwards
// *cursor_bar = current_cursor_position;
// } else if current_cursor_position >= *cursor_bar + num_columns {
// *cursor_bar = current_cursor_position - num_columns;
// }
// // Else, don't change what our start position is from whatever it is set to!
// *cursor_bar
// }
// }
// }
// pub fn get_start_position(
// num_rows: usize, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut usize,
// currently_selected_position: usize, is_force_redraw: bool,
// ) -> usize {
// if is_force_redraw {
// *scroll_position_bar = 0;
// }
// match scroll_direction {
// app::ScrollDirection::Down => {
// if currently_selected_position < *scroll_position_bar + num_rows {
// // If, using previous_scrolled_position, we can see the element
// // (so within that and + num_rows) just reuse the current previously scrolled position
// *scroll_position_bar
// } else if currently_selected_position >= num_rows {
// // Else if the current position past the last element visible in the list, omit
// // until we can see that element
// *scroll_position_bar = currently_selected_position - num_rows;
// *scroll_position_bar
// } else {
// // Else, if it is not past the last element visible, do not omit anything
// 0
// }
// }
// app::ScrollDirection::Up => {
// if currently_selected_position <= *scroll_position_bar {
// // If it's past the first element, then show from that element downwards
// *scroll_position_bar = currently_selected_position;
// } else if currently_selected_position >= *scroll_position_bar + num_rows {
// *scroll_position_bar = currently_selected_position - num_rows;
// }
// // Else, don't change what our start position is from whatever it is set to!
// *scroll_position_bar
// }
// }
// }
/// Calculate how many bars are to be
/// drawn within basic mode's components.
pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize {
std::cmp::min(
(num_bars_available as f64 * use_percentage / 100.0).round() as usize,
num_bars_available,
)
}
/// Interpolates between two points. Mainly used to help fill in tui-rs blanks in certain situations.
/// It is expected point_one is "further left" compared to point_two.
/// A point is two floats, in (x, y) form. x is time, y is value.
pub fn interpolate_points(point_one: &(f64, f64), point_two: &(f64, f64), time: f64) -> f64 {
let delta_x = point_two.0 - point_one.0;
let delta_y = point_two.1 - point_one.1;
let slope = delta_y / delta_x;
(point_one.1 + (time - point_one.0) * slope).max(0.0)
}

View File

@ -739,7 +739,7 @@ const BRANCH_VERTICAL: char = '│';
const BRANCH_SPLIT: char = '├';
const BRANCH_HORIZONTAL: char = '─';
pub fn tree_process_data(
fn tree_process_data(
filtered_process_data: &[ConvertedProcessData], is_using_command: bool,
sorting_type: &ProcessSorting, is_sort_descending: bool,
) -> Vec<ConvertedProcessData> {
@ -1207,7 +1207,7 @@ pub fn tree_process_data(
}
// FIXME: [OPT] This is an easy target for optimization, too many to_strings!
pub fn stringify_process_data(
fn stringify_process_data(
proc_widget_state: &ProcWidgetState, finalized_process_data: &[ConvertedProcessData],
) -> Vec<(Vec<(String, Option<String>)>, bool)> {
let is_proc_widget_grouped = proc_widget_state.is_grouped;
@ -1282,7 +1282,7 @@ pub fn stringify_process_data(
/// Takes a set of converted process data and groups it together.
///
/// To be honest, I really don't like how this is done, even though I've rewritten this like 3 times.
pub fn group_process_data(
fn group_process_data(
single_process_data: &[ConvertedProcessData], is_using_command: bool,
) -> Vec<ConvertedProcessData> {
#[derive(Clone, Default, Debug)]

View File

@ -21,7 +21,7 @@ use std::{
use crossterm::{
event::{
read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent,
read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent,
MouseEventKind,
},
execute,
@ -30,13 +30,12 @@ use crossterm::{
};
use app::{
data_harvester::{self, processes::ProcessSorting},
data_harvester::{self},
event::EventResult,
layout_manager::WidgetDirection,
AppState, UsedWidgets,
};
use constants::*;
use data_conversion::*;
use options::*;
use utils::error;
@ -76,24 +75,6 @@ pub enum ThreadControlEvent {
UpdateUpdateTime(u64),
}
pub fn handle_mouse_event(event: MouseEvent, app: &mut AppState) -> EventResult {
match event.kind {
MouseEventKind::Down(MouseButton::Left) => {
app.on_left_mouse_up(event.column, event.row);
EventResult::Redraw
}
MouseEventKind::ScrollUp => {
app.handle_scroll_up();
EventResult::Redraw
}
MouseEventKind::ScrollDown => {
app.handle_scroll_down();
EventResult::Redraw
}
_ => EventResult::NoRedraw,
}
}
pub fn handle_key_event(
event: KeyEvent, app: &mut AppState, reset_sender: &std::sync::mpsc::Sender<ThreadControlEvent>,
) -> EventResult {
@ -309,296 +290,6 @@ pub fn panic_hook(panic_info: &PanicInfo<'_>) {
.unwrap();
}
pub fn force_redraw(app: &mut AppState) {
// Currently we use an Option... because we might want to future-proof this
// if we eventually get widget-specific redrawing!
if app.proc_state.force_update_all {
update_all_process_lists(app);
app.proc_state.force_update_all = false;
} else if let Some(widget_id) = app.proc_state.force_update {
update_final_process_list(app, widget_id);
app.proc_state.force_update = None;
}
if app.cpu_state.force_update.is_some() {
convert_cpu_data_points(
&app.data_collection,
&mut app.canvas_data.cpu_data,
app.is_frozen,
);
app.canvas_data.load_avg_data = app.data_collection.load_avg_harvest;
app.cpu_state.force_update = None;
}
// FIXME: [OPT] Prefer reassignment over new vectors?
if app.mem_state.force_update.is_some() {
app.canvas_data.mem_data = convert_mem_data_points(&app.data_collection, app.is_frozen);
app.canvas_data.swap_data = convert_swap_data_points(&app.data_collection, app.is_frozen);
app.mem_state.force_update = None;
}
if app.net_state.force_update.is_some() {
let (rx, tx) = get_rx_tx_data_points(
&app.data_collection,
app.is_frozen,
&app.app_config_fields.network_scale_type,
&app.app_config_fields.network_unit_type,
app.app_config_fields.network_use_binary_prefix,
);
app.canvas_data.network_data_rx = rx;
app.canvas_data.network_data_tx = tx;
app.net_state.force_update = None;
}
}
#[allow(clippy::needless_collect)]
pub fn update_all_process_lists(app: &mut AppState) {
// 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 !app.is_frozen {
let widget_ids = app
.proc_state
.widget_states
.keys()
.cloned()
.collect::<Vec<_>>();
widget_ids.into_iter().for_each(|widget_id| {
update_final_process_list(app, widget_id);
});
}
}
fn update_final_process_list(_app: &mut AppState, _widget_id: u64) {
// TODO: [STATE] FINISH THIS
// let process_states = app
// .proc_state
// .widget_states
// .get(&widget_id)
// .map(|process_state| {
// (
// process_state
// .process_search_state
// .search_state
// .is_invalid_or_blank_search(),
// process_state.is_using_command,
// process_state.is_grouped,
// process_state.is_tree_mode,
// )
// });
// if let Some((is_invalid_or_blank, is_using_command, is_grouped, is_tree)) = process_states {
// if !app.is_frozen {
// convert_process_data(
// &app.data_collection,
// &mut app.canvas_data.single_process_data,
// #[cfg(target_family = "unix")]
// &mut app.user_table,
// );
// }
// let process_filter = app.get_process_filter(widget_id);
// let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
// app.canvas_data
// .single_process_data
// .iter()
// .map(|(_pid, process)| {
// let mut process_clone = process.clone();
// if !is_invalid_or_blank {
// if let Some(process_filter) = process_filter {
// process_clone.is_disabled_entry =
// !process_filter.check(&process_clone, is_using_command);
// }
// }
// process_clone
// })
// .collect::<Vec<_>>()
// } else {
// app.canvas_data
// .single_process_data
// .iter()
// .filter_map(|(_pid, process)| {
// if !is_invalid_or_blank {
// if let Some(process_filter) = process_filter {
// if process_filter.check(process, is_using_command) {
// Some(process)
// } else {
// None
// }
// } else {
// Some(process)
// }
// } else {
// Some(process)
// }
// })
// .cloned()
// .collect::<Vec<_>>()
// };
// if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
// let mut finalized_process_data = if is_tree {
// tree_process_data(
// &filtered_process_data,
// is_using_command,
// &proc_widget_state.process_sorting_type,
// proc_widget_state.is_process_sort_descending,
// )
// } else if is_grouped {
// group_process_data(&filtered_process_data, is_using_command)
// } else {
// filtered_process_data
// };
// // Note tree mode is sorted well before this, as it's special.
// if !is_tree {
// sort_process_data(&mut finalized_process_data, proc_widget_state);
// }
// if proc_widget_state.scroll_state.current_scroll_position
// >= finalized_process_data.len()
// {
// proc_widget_state.scroll_state.current_scroll_position =
// finalized_process_data.len().saturating_sub(1);
// proc_widget_state.scroll_state.previous_scroll_position = 0;
// proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
// }
// app.canvas_data.stringified_process_data_map.insert(
// widget_id,
// stringify_process_data(proc_widget_state, &finalized_process_data),
// );
// app.canvas_data
// .finalized_process_data_map
// .insert(widget_id, finalized_process_data);
// }
// }
}
fn _sort_process_data(
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
) {
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());
match &proc_widget_state.process_sorting_type {
ProcessSorting::CpuPercent => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.cpu_percent_usage,
b.cpu_percent_usage,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::Mem => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.mem_usage_bytes,
b.mem_usage_bytes,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::MemPercent => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.mem_percent_usage,
b.mem_percent_usage,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::ProcessName => {
// Don't repeat if false... it sorts by name by default anyways.
if proc_widget_state.is_process_sort_descending {
to_sort_vec.sort_by_cached_key(|c| c.name.to_lowercase());
if proc_widget_state.is_process_sort_descending {
to_sort_vec.reverse();
}
}
}
ProcessSorting::Command => {
to_sort_vec.sort_by_cached_key(|c| c.command.to_lowercase());
if proc_widget_state.is_process_sort_descending {
to_sort_vec.reverse();
}
}
ProcessSorting::Pid => {
if !proc_widget_state.is_grouped {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.pid,
b.pid,
proc_widget_state.is_process_sort_descending,
)
});
}
}
ProcessSorting::ReadPerSecond => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.rps_f64,
b.rps_f64,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::WritePerSecond => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.wps_f64,
b.wps_f64,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::TotalRead => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.tr_f64,
b.tr_f64,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::TotalWrite => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.tw_f64,
b.tw_f64,
proc_widget_state.is_process_sort_descending,
)
});
}
ProcessSorting::State => {
to_sort_vec.sort_by_cached_key(|c| c.process_state.to_lowercase());
if proc_widget_state.is_process_sort_descending {
to_sort_vec.reverse();
}
}
ProcessSorting::User => to_sort_vec.sort_by(|a, b| match (&a.user, &b.user) {
(Some(user_a), Some(user_b)) => utils::gen_util::get_ordering(
user_a.to_lowercase(),
user_b.to_lowercase(),
proc_widget_state.is_process_sort_descending,
),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Less,
}),
ProcessSorting::Count => {
if proc_widget_state.is_grouped {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.group_pids.len(),
b.group_pids.len(),
proc_widget_state.is_process_sort_descending,
)
});
}
}
}
}
pub fn create_input_thread(
sender: std::sync::mpsc::Sender<BottomEvent>, termination_ctrl_lock: Arc<Mutex<bool>>,
) -> std::thread::JoinHandle<()> {

View File

@ -1,6 +1,6 @@
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, str::FromStr};
use std::str::FromStr;
use crate::{
app::{layout_manager::*, *},
@ -105,18 +105,6 @@ pub struct WidgetIdEnabled {
enabled: bool,
}
impl WidgetIdEnabled {
pub fn create_from_hashmap(hashmap: &HashMap<u64, bool>) -> Vec<WidgetIdEnabled> {
hashmap
.iter()
.map(|(id, enabled)| WidgetIdEnabled {
id: *id,
enabled: *enabled,
})
.collect()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct ConfigColours {
pub table_header_color: Option<String>,
@ -275,27 +263,6 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
net_filter,
};
// Ok(AppState::builder()
// .app_config_fields(app_config_fields)
// .cpu_state(CpuState::init(cpu_state_map))
// .mem_state(MemState::init(mem_state_map))
// .net_state(NetState::init(net_state_map))
// .proc_state(ProcState::init(proc_state_map))
// .disk_state(DiskState::init(disk_state_map))
// .temp_state(TempState::init(temp_state_map))
// .battery_state(BatteryState::init(battery_state_map))
// .basic_table_widget_state(basic_table_widget_state)
// .current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // TODO: [UNWRAP] - many of the unwraps are fine (like this one) but do a once-over and/or switch to expect?
// .widget_map(widget_map)
// .used_widgets(used_widgets)
// .filters(DataFilters {
// disk_filter,
// mount_filter,
// temp_filter,
// net_filter,
// })
// .build())
Ok(AppState::new(
app_config_fields,
data_filter,
@ -303,71 +270,6 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &mut Config) -> Re
))
}
// pub fn get_widget_layout(
// matches: &clap::ArgMatches<'static>, config: &Config,
// ) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
// let left_legend = get_use_left_legend(matches, config);
// let (default_widget_type, mut default_widget_count) =
// get_default_widget_and_count(matches, config)?;
// let mut default_widget_id = 1;
// let bottom_layout = if get_use_basic_mode(matches, config) {
// default_widget_id = DEFAULT_WIDGET_ID;
// BottomLayout::init_basic_default(get_use_battery(matches, config))
// } else {
// let ref_row: Vec<Row>; // Required to handle reference
// let rows = match &config.row {
// Some(r) => r,
// None => {
// // This cannot (like it really shouldn't) fail!
// ref_row = toml::from_str::<Config>(if get_use_battery(matches, config) {
// DEFAULT_BATTERY_LAYOUT
// } else {
// DEFAULT_LAYOUT
// })?
// .row
// .unwrap();
// &ref_row
// }
// };
// let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
// let mut total_height_ratio = 0;
// let mut ret_bottom_layout = BottomLayout {
// rows: rows
// .iter()
// .map(|row| {
// row.convert_row_to_bottom_row(
// &mut iter_id,
// &mut total_height_ratio,
// &mut default_widget_id,
// &default_widget_type,
// &mut default_widget_count,
// left_legend,
// )
// })
// .collect::<error::Result<Vec<_>>>()?,
// total_row_height_ratio: total_height_ratio,
// };
// // Confirm that we have at least ONE widget left - if not, error out!
// if iter_id > 0 {
// ret_bottom_layout.get_movement_mappings();
// // debug!("Bottom layout: {:#?}", ret_bottom_layout);
// ret_bottom_layout
// } else {
// return Err(error::BottomError::ConfigError(
// "please have at least one widget under the '[[row]]' section.".to_string(),
// ));
// }
// };
// Ok((bottom_layout, default_widget_id, default_widget_type))
// }
fn get_update_rate_in_milliseconds(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<u64> {
@ -605,59 +507,6 @@ fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bo
false
}
fn get_default_widget_and_count(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<(Option<BottomWidgetType>, u64)> {
let widget_type = if let Some(widget_type) = matches.value_of("default_widget_type") {
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
if let BottomWidgetType::Empty = parsed_widget {
None
} else {
Some(parsed_widget)
}
} else if let Some(flags) = &config.flags {
if let Some(widget_type) = &flags.default_widget_type {
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
if let BottomWidgetType::Empty = parsed_widget {
None
} else {
Some(parsed_widget)
}
} else {
None
}
} else {
None
};
let widget_count = if let Some(widget_count) = matches.value_of("default_widget_count") {
Some(widget_count.parse::<u128>()?)
} else if let Some(flags) = &config.flags {
flags
.default_widget_count
.map(|widget_count| widget_count as u128)
} else {
None
};
match (widget_type, widget_count) {
(Some(widget_type), Some(widget_count)) => {
if widget_count > std::u64::MAX as u128 {
Err(BottomError::ConfigError(
"set your widget count to be at most unsigned INT_MAX.".to_string(),
))
} else {
Ok((Some(widget_type), widget_count as u64))
}
}
(Some(widget_type), None) => Ok((Some(widget_type), 1)),
(None, Some(_widget_count)) => Err(BottomError::ConfigError(
"cannot set 'default_widget_count' by itself, it must be used with 'default_widget_type'.".to_string(),
)),
(None, None) => Ok((None, 1))
}
}
fn get_disable_click(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("disable_click") {
return true;

View File

@ -9,328 +9,6 @@ pub struct Row {
pub ratio: Option<u32>,
}
// impl Row {
// pub fn convert_row_to_bottom_row(
// &self, iter_id: &mut u64, total_height_ratio: &mut u32, default_widget_id: &mut u64,
// default_widget_type: &Option<BottomWidgetType>, default_widget_count: &mut u64,
// left_legend: bool,
// ) -> Result<OldBottomRow> {
// // TODO: In the future we want to also add percentages.
// // But for MVP, we aren't going to bother.
// let row_ratio = self.ratio.unwrap_or(1);
// let mut children = Vec::new();
// *total_height_ratio += row_ratio;
// let mut total_col_ratio = 0;
// if let Some(row_children) = &self.child {
// for row_child in row_children {
// match row_child {
// RowChildren::Widget(widget) => {
// *iter_id += 1;
// let width_ratio = widget.ratio.unwrap_or(1);
// total_col_ratio += width_ratio;
// let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
// if let Some(default_widget_type_val) = default_widget_type {
// if *default_widget_type_val == widget_type && *default_widget_count > 0
// {
// *default_widget_count -= 1;
// if *default_widget_count == 0 {
// *default_widget_id = *iter_id;
// }
// }
// } else {
// // Check default flag
// if let Some(default_widget_flag) = widget.default {
// if default_widget_flag {
// *default_widget_id = *iter_id;
// }
// }
// }
// children.push(match widget_type {
// BottomWidgetType::Cpu => {
// let cpu_id = *iter_id;
// *iter_id += 1;
// OldBottomCol::builder()
// .col_width_ratio(width_ratio)
// .children(if left_legend {
// vec![BottomColRow::builder()
// .total_widget_ratio(20)
// .children(vec![
// BottomWidget::builder()
// .width_ratio(3)
// .widget_type(BottomWidgetType::CpuLegend)
// .widget_id(*iter_id)
// .canvas_handle_width(true)
// .parent_reflector(Some((
// WidgetDirection::Right,
// 1,
// )))
// .build(),
// BottomWidget::builder()
// .width_ratio(17)
// .widget_type(BottomWidgetType::Cpu)
// .widget_id(cpu_id)
// .flex_grow(true)
// .build(),
// ])
// .build()]
// } else {
// vec![BottomColRow::builder()
// .total_widget_ratio(20)
// .children(vec![
// BottomWidget::builder()
// .width_ratio(17)
// .widget_type(BottomWidgetType::Cpu)
// .widget_id(cpu_id)
// .flex_grow(true)
// .build(),
// BottomWidget::builder()
// .width_ratio(3)
// .widget_type(BottomWidgetType::CpuLegend)
// .widget_id(*iter_id)
// .canvas_handle_width(true)
// .parent_reflector(Some((
// WidgetDirection::Left,
// 1,
// )))
// .build(),
// ])
// .build()]
// })
// .build()
// }
// BottomWidgetType::Proc => {
// let proc_id = *iter_id;
// let proc_search_id = *iter_id + 1;
// *iter_id += 2;
// OldBottomCol::builder()
// .total_col_row_ratio(2)
// .col_width_ratio(width_ratio)
// .children(vec![
// BottomColRow::builder()
// .children(vec![
// BottomWidget::builder()
// .widget_type(BottomWidgetType::ProcSort)
// .widget_id(*iter_id)
// .canvas_handle_width(true)
// .parent_reflector(Some((
// WidgetDirection::Right,
// 2,
// )))
// .width_ratio(1)
// .build(),
// BottomWidget::builder()
// .widget_type(BottomWidgetType::Proc)
// .widget_id(proc_id)
// .width_ratio(2)
// .build(),
// ])
// .total_widget_ratio(3)
// .flex_grow(true)
// .build(),
// BottomColRow::builder()
// .children(vec![BottomWidget::builder()
// .widget_type(BottomWidgetType::ProcSearch)
// .widget_id(proc_search_id)
// .parent_reflector(Some((WidgetDirection::Up, 1)))
// .build()])
// .canvas_handle_height(true)
// .build(),
// ])
// .build()
// }
// _ => OldBottomCol::builder()
// .col_width_ratio(width_ratio)
// .children(vec![BottomColRow::builder()
// .children(vec![BottomWidget::builder()
// .widget_type(widget_type)
// .widget_id(*iter_id)
// .build()])
// .build()])
// .build(),
// });
// }
// RowChildren::Col { ratio, child } => {
// let col_width_ratio = ratio.unwrap_or(1);
// total_col_ratio += col_width_ratio;
// let mut total_col_row_ratio = 0;
// let mut contains_proc = false;
// let mut col_row_children: Vec<BottomColRow> = Vec::new();
// for widget in child {
// let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
// *iter_id += 1;
// let col_row_height_ratio = widget.ratio.unwrap_or(1);
// total_col_row_ratio += col_row_height_ratio;
// if let Some(default_widget_type_val) = default_widget_type {
// if *default_widget_type_val == widget_type
// && *default_widget_count > 0
// {
// *default_widget_count -= 1;
// if *default_widget_count == 0 {
// *default_widget_id = *iter_id;
// }
// }
// } else {
// // Check default flag
// if let Some(default_widget_flag) = widget.default {
// if default_widget_flag {
// *default_widget_id = *iter_id;
// }
// }
// }
// match widget_type {
// BottomWidgetType::Cpu => {
// let cpu_id = *iter_id;
// *iter_id += 1;
// if left_legend {
// col_row_children.push(
// BottomColRow::builder()
// .col_row_height_ratio(col_row_height_ratio)
// .total_widget_ratio(20)
// .children(vec![
// BottomWidget::builder()
// .width_ratio(3)
// .widget_type(BottomWidgetType::CpuLegend)
// .widget_id(*iter_id)
// .canvas_handle_width(true)
// .parent_reflector(Some((
// WidgetDirection::Right,
// 1,
// )))
// .build(),
// BottomWidget::builder()
// .width_ratio(17)
// .widget_type(BottomWidgetType::Cpu)
// .widget_id(cpu_id)
// .flex_grow(true)
// .build(),
// ])
// .build(),
// );
// } else {
// col_row_children.push(
// BottomColRow::builder()
// .col_row_height_ratio(col_row_height_ratio)
// .total_widget_ratio(20)
// .children(vec![
// BottomWidget::builder()
// .width_ratio(17)
// .widget_type(BottomWidgetType::Cpu)
// .widget_id(cpu_id)
// .flex_grow(true)
// .build(),
// BottomWidget::builder()
// .width_ratio(3)
// .widget_type(BottomWidgetType::CpuLegend)
// .widget_id(*iter_id)
// .canvas_handle_width(true)
// .parent_reflector(Some((
// WidgetDirection::Left,
// 1,
// )))
// .build(),
// ])
// .build(),
// );
// }
// }
// BottomWidgetType::Proc => {
// contains_proc = true;
// let proc_id = *iter_id;
// let proc_search_id = *iter_id + 1;
// *iter_id += 2;
// col_row_children.push(
// BottomColRow::builder()
// .children(vec![
// BottomWidget::builder()
// .widget_type(BottomWidgetType::ProcSort)
// .widget_id(*iter_id)
// .canvas_handle_width(true)
// .parent_reflector(Some((
// WidgetDirection::Right,
// 2,
// )))
// .width_ratio(1)
// .build(),
// BottomWidget::builder()
// .widget_type(BottomWidgetType::Proc)
// .widget_id(proc_id)
// .width_ratio(2)
// .build(),
// ])
// .col_row_height_ratio(col_row_height_ratio)
// .total_widget_ratio(3)
// .build(),
// );
// col_row_children.push(
// BottomColRow::builder()
// .col_row_height_ratio(col_row_height_ratio)
// .children(vec![BottomWidget::builder()
// .widget_type(BottomWidgetType::ProcSearch)
// .widget_id(proc_search_id)
// .parent_reflector(Some((WidgetDirection::Up, 1)))
// .build()])
// .canvas_handle_height(true)
// .build(),
// );
// }
// _ => col_row_children.push(
// BottomColRow::builder()
// .col_row_height_ratio(col_row_height_ratio)
// .children(vec![BottomWidget::builder()
// .widget_type(widget_type)
// .widget_id(*iter_id)
// .build()])
// .build(),
// ),
// }
// }
// if contains_proc {
// // Must adjust ratios to work with proc
// total_col_row_ratio *= 2;
// for child in &mut col_row_children {
// // Multiply all non-proc or proc-search ratios by 2
// if !child.children.is_empty() {
// match child.children[0].widget_type {
// BottomWidgetType::ProcSearch => {}
// _ => child.col_row_height_ratio *= 2,
// }
// }
// }
// }
// children.push(
// OldBottomCol::builder()
// .total_col_row_ratio(total_col_row_ratio)
// .col_width_ratio(col_width_ratio)
// .children(col_row_children)
// .build(),
// );
// }
// RowChildren::Carousel {
// carousel_children: _,
// default: _,
// } => {}
// }
// }
// }
// Ok(OldBottomRow::builder()
// .total_col_ratio(total_col_ratio)
// .row_height_ratio(row_ratio)
// .children(children)
// .build())
// }
// }
/// Represents a child of a Row - either a Col (column) or a FinalWidget.
///
/// A Col can also have an optional length and children. We only allow columns