mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-22 04:03:06 +00:00
refactor: rewrite column algorithm (#227)
Update how we position and generate column widths to look less terrible. This also adds truncation w/ ellipsis to the columns, and for processes, the state will automatically shrink to a short form (just a character) if there isn't enough space.
This commit is contained in:
parent
c426b0c7c4
commit
c58b2c2bb9
18 changed files with 708 additions and 247 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -38,6 +38,7 @@
|
||||||
"choco",
|
"choco",
|
||||||
"cmdline",
|
"cmdline",
|
||||||
"commandline",
|
"commandline",
|
||||||
|
"concat",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"curr",
|
"curr",
|
||||||
"czvf",
|
"czvf",
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
cognitive-complexity-threshold = 100
|
cognitive-complexity-threshold = 100
|
||||||
|
type-complexity-threshold = 500
|
|
@ -298,6 +298,7 @@ impl App {
|
||||||
.columns
|
.columns
|
||||||
.toggle(&processes::ProcessSorting::Pid);
|
.toggle(&processes::ProcessSorting::Pid);
|
||||||
|
|
||||||
|
proc_widget_state.requires_redraw = true;
|
||||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,6 +503,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
|
proc_widget_state.requires_redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1176,7 +1178,7 @@ impl App {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
proc_widget_state.requires_redraw = true;
|
||||||
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
self.proc_state.force_update = Some(self.current_widget.widget_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,7 @@ impl DataCollection {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eat_network(&mut self, network: &network::NetworkHarvest, new_entry: &mut TimedData) {
|
fn eat_network(&mut self, network: &network::NetworkHarvest, new_entry: &mut TimedData) {
|
||||||
|
// FIXME [NETWORKING; CONFIG]: The ability to config this?
|
||||||
// FIXME [NETWORKING]: Support bits, support switching between decimal and binary units (move the log part to conversion and switch on the fly)
|
// FIXME [NETWORKING]: Support bits, support switching between decimal and binary units (move the log part to conversion and switch on the fly)
|
||||||
// RX
|
// RX
|
||||||
new_entry.rx_data = if network.rx > 0 {
|
new_entry.rx_data = if network.rx > 0 {
|
||||||
|
|
|
@ -2,7 +2,8 @@ use sysinfo::{ProcessorExt, System, SystemExt};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct CpuData {
|
pub struct CpuData {
|
||||||
pub cpu_name: String,
|
pub cpu_prefix: String,
|
||||||
|
pub cpu_count: Option<usize>,
|
||||||
pub cpu_usage: f64,
|
pub cpu_usage: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,14 +16,16 @@ pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> CpuHarvest {
|
||||||
|
|
||||||
if show_average_cpu {
|
if show_average_cpu {
|
||||||
cpu_vec.push(CpuData {
|
cpu_vec.push(CpuData {
|
||||||
cpu_name: "AVG".to_string(),
|
cpu_prefix: "AVG".to_string(),
|
||||||
|
cpu_count: None,
|
||||||
cpu_usage: avg_cpu_usage as f64,
|
cpu_usage: avg_cpu_usage as f64,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (itx, cpu) in cpu_data.iter().enumerate() {
|
for (itx, cpu) in cpu_data.iter().enumerate() {
|
||||||
cpu_vec.push(CpuData {
|
cpu_vec.push(CpuData {
|
||||||
cpu_name: format!("CPU{}", itx),
|
cpu_prefix: "CPU".to_string(),
|
||||||
|
cpu_count: Some(itx),
|
||||||
cpu_usage: f64::from(cpu.get_cpu_usage()),
|
cpu_usage: f64::from(cpu.get_cpu_usage()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ pub async fn get_sysinfo_io_usage_list(
|
||||||
std::collections::HashMap::new();
|
std::collections::HashMap::new();
|
||||||
Ok(Some(io_hash))
|
Ok(Some(io_hash))
|
||||||
|
|
||||||
|
// TODO: Rename these functions to be like "get_arm_io_usage_list"
|
||||||
|
|
||||||
// TODO: Sysinfo disk I/O usage.
|
// TODO: Sysinfo disk I/O usage.
|
||||||
// ...sadly, this cannot be done as of now (other than me writing my own), it requires further
|
// ...sadly, this cannot be done as of now (other than me writing my own), it requires further
|
||||||
// work. See https://github.com/GuillaumeGomez/sysinfo/issues/304.
|
// work. See https://github.com/GuillaumeGomez/sysinfo/issues/304.
|
||||||
|
|
|
@ -109,6 +109,13 @@ impl AppSearchState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Meant for canvas operations involving table column widths.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CanvasTableWidthState {
|
||||||
|
pub desired_column_widths: Vec<u16>,
|
||||||
|
pub calculated_column_widths: Vec<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
/// ProcessSearchState only deals with process' search's current settings and state.
|
/// ProcessSearchState only deals with process' search's current settings and state.
|
||||||
pub struct ProcessSearchState {
|
pub struct ProcessSearchState {
|
||||||
pub search_state: AppSearchState,
|
pub search_state: AppSearchState,
|
||||||
|
@ -321,6 +328,9 @@ impl ProcColumn {
|
||||||
pub fn get_column_headers(
|
pub fn get_column_headers(
|
||||||
&self, proc_sorting_type: &ProcessSorting, sort_reverse: bool,
|
&self, proc_sorting_type: &ProcessSorting, sort_reverse: bool,
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
|
const DOWN_ARROW: char = '▼';
|
||||||
|
const UP_ARROW: char = '▲';
|
||||||
|
|
||||||
// TODO: Gonna have to figure out how to do left/right GUI notation if we add it.
|
// TODO: Gonna have to figure out how to do left/right GUI notation if we add it.
|
||||||
self.ordered_columns
|
self.ordered_columns
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -332,13 +342,20 @@ impl ProcColumn {
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapping.enabled {
|
if mapping.enabled {
|
||||||
Some(if proc_sorting_type == column_type {
|
Some(format!(
|
||||||
column_type.to_string()
|
"{}{}{}",
|
||||||
+ command_str.as_str()
|
column_type.to_string(),
|
||||||
+ if sort_reverse { "▼" } else { "▲" }
|
command_str.as_str(),
|
||||||
} else {
|
if proc_sorting_type == column_type {
|
||||||
column_type.to_string() + command_str.as_str()
|
if sort_reverse {
|
||||||
})
|
DOWN_ARROW
|
||||||
|
} else {
|
||||||
|
UP_ARROW
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
' '
|
||||||
|
}
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -358,6 +375,8 @@ pub struct ProcWidgetState {
|
||||||
pub is_sort_open: bool,
|
pub is_sort_open: bool,
|
||||||
pub columns: ProcColumn,
|
pub columns: ProcColumn,
|
||||||
pub is_tree_mode: bool,
|
pub is_tree_mode: bool,
|
||||||
|
pub table_width_state: CanvasTableWidthState,
|
||||||
|
pub requires_redraw: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcWidgetState {
|
impl ProcWidgetState {
|
||||||
|
@ -397,6 +416,8 @@ impl ProcWidgetState {
|
||||||
is_sort_open: false,
|
is_sort_open: false,
|
||||||
columns,
|
columns,
|
||||||
is_tree_mode: false,
|
is_tree_mode: false,
|
||||||
|
table_width_state: CanvasTableWidthState::default(),
|
||||||
|
requires_redraw: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,6 +616,7 @@ pub struct CpuWidgetState {
|
||||||
pub autohide_timer: Option<Instant>,
|
pub autohide_timer: Option<Instant>,
|
||||||
pub scroll_state: AppScrollWidgetState,
|
pub scroll_state: AppScrollWidgetState,
|
||||||
pub is_multi_graph_mode: bool,
|
pub is_multi_graph_mode: bool,
|
||||||
|
pub table_width_state: CanvasTableWidthState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CpuWidgetState {
|
impl CpuWidgetState {
|
||||||
|
@ -605,6 +627,7 @@ impl CpuWidgetState {
|
||||||
autohide_timer,
|
autohide_timer,
|
||||||
scroll_state: AppScrollWidgetState::default(),
|
scroll_state: AppScrollWidgetState::default(),
|
||||||
is_multi_graph_mode: false,
|
is_multi_graph_mode: false,
|
||||||
|
table_width_state: CanvasTableWidthState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,12 +691,14 @@ impl MemState {
|
||||||
|
|
||||||
pub struct TempWidgetState {
|
pub struct TempWidgetState {
|
||||||
pub scroll_state: AppScrollWidgetState,
|
pub scroll_state: AppScrollWidgetState,
|
||||||
|
pub table_width_state: CanvasTableWidthState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TempWidgetState {
|
impl TempWidgetState {
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
TempWidgetState {
|
TempWidgetState {
|
||||||
scroll_state: AppScrollWidgetState::default(),
|
scroll_state: AppScrollWidgetState::default(),
|
||||||
|
table_width_state: CanvasTableWidthState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -698,12 +723,14 @@ impl TempState {
|
||||||
|
|
||||||
pub struct DiskWidgetState {
|
pub struct DiskWidgetState {
|
||||||
pub scroll_state: AppScrollWidgetState,
|
pub scroll_state: AppScrollWidgetState,
|
||||||
|
pub table_width_state: CanvasTableWidthState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiskWidgetState {
|
impl DiskWidgetState {
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
DiskWidgetState {
|
DiskWidgetState {
|
||||||
scroll_state: AppScrollWidgetState::default(),
|
scroll_state: AppScrollWidgetState::default(),
|
||||||
|
table_width_state: CanvasTableWidthState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ fn main() -> Result<()> {
|
||||||
termination_hook();
|
termination_hook();
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let mut first_run = true;
|
||||||
|
|
||||||
while !is_terminated.load(Ordering::SeqCst) {
|
while !is_terminated.load(Ordering::SeqCst) {
|
||||||
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
||||||
|
@ -123,6 +124,13 @@ fn main() -> Result<()> {
|
||||||
BottomEvent::Update(data) => {
|
BottomEvent::Update(data) => {
|
||||||
app.data_collection.eat_data(&data);
|
app.data_collection.eat_data(&data);
|
||||||
|
|
||||||
|
// This thing is required as otherwise, some widgets can't draw correctly w/o
|
||||||
|
// some data (or they need to be re-drawn).
|
||||||
|
if first_run {
|
||||||
|
first_run = false;
|
||||||
|
app.is_force_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
if !app.is_frozen {
|
if !app.is_frozen {
|
||||||
// Convert all data into tui-compliant components
|
// Convert all data into tui-compliant components
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ pub struct DisplayableData {
|
||||||
pub temp_sensor_data: Vec<Vec<String>>,
|
pub temp_sensor_data: Vec<Vec<String>>,
|
||||||
pub single_process_data: Vec<ConvertedProcessData>, // Contains single process data
|
pub single_process_data: Vec<ConvertedProcessData>, // Contains single process data
|
||||||
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed
|
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>, // What's actually displayed
|
||||||
|
pub stringified_process_data_map: HashMap<u64, Vec<(Vec<(String, Option<String>)>, bool)>>, // Represents the row and whether it is disabled
|
||||||
pub mem_label_percent: String,
|
pub mem_label_percent: String,
|
||||||
pub swap_label_percent: String,
|
pub swap_label_percent: String,
|
||||||
pub mem_label_frac: String,
|
pub mem_label_frac: String,
|
||||||
|
@ -67,7 +68,6 @@ pub struct Painter {
|
||||||
widget_layout: BottomLayout,
|
widget_layout: BottomLayout,
|
||||||
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
|
derived_widget_draw_locs: Vec<Vec<Vec<Vec<Rect>>>>,
|
||||||
table_height_offset: u16,
|
table_height_offset: u16,
|
||||||
requires_boundary_recalculation: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
@ -152,7 +152,6 @@ impl Painter {
|
||||||
widget_layout,
|
widget_layout,
|
||||||
derived_widget_draw_locs: Vec::default(),
|
derived_widget_draw_locs: Vec::default(),
|
||||||
table_height_offset: if is_basic_mode { 2 } else { 4 } + table_gap,
|
table_height_offset: if is_basic_mode { 2 } else { 4 } + table_gap,
|
||||||
requires_boundary_recalculation: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,25 +400,18 @@ impl Painter {
|
||||||
// Basic mode. This basically removes all graphs but otherwise
|
// Basic mode. This basically removes all graphs but otherwise
|
||||||
// the same info.
|
// the same info.
|
||||||
|
|
||||||
let cpu_height = (app_state.canvas_data.cpu_data.len() / 4) as u16
|
|
||||||
+ (if app_state.canvas_data.cpu_data.len() % 4 == 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
});
|
|
||||||
|
|
||||||
// A little hack to force the widget boundary recalculation. This is required here
|
|
||||||
// as basic mode has a height of 0 initially, which breaks things.
|
|
||||||
if self.requires_boundary_recalculation {
|
|
||||||
app_state.is_determining_widget_boundary = true;
|
|
||||||
}
|
|
||||||
self.requires_boundary_recalculation = cpu_height == 0;
|
|
||||||
|
|
||||||
let vertical_chunks = Layout::default()
|
let vertical_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(
|
.constraints(
|
||||||
[
|
[
|
||||||
Constraint::Length(cpu_height),
|
Constraint::Length(
|
||||||
|
(app_state.canvas_data.cpu_data.len() / 4) as u16
|
||||||
|
+ (if app_state.canvas_data.cpu_data.len() % 4 == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}),
|
||||||
|
),
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
Constraint::Length(2),
|
Constraint::Length(2),
|
||||||
Constraint::Length(2),
|
Constraint::Length(2),
|
||||||
|
@ -486,7 +478,7 @@ impl Painter {
|
||||||
self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3], widget_id);
|
self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3], widget_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Draws using the passed in (or default) layout. NOT basic so far.
|
// Draws using the passed in (or default) layout.
|
||||||
if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw {
|
if self.derived_widget_draw_locs.is_empty() || app_state.is_force_redraw {
|
||||||
let row_draw_locs = Layout::default()
|
let row_draw_locs = Layout::default()
|
||||||
.margin(0)
|
.margin(0)
|
||||||
|
|
|
@ -1,78 +1,107 @@
|
||||||
use crate::app;
|
use crate::app;
|
||||||
use itertools::izip;
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
// TODO: Reverse intrinsic?
|
/// Return a (hard)-width vector for column widths.
|
||||||
/// A somewhat jury-rigged solution to simulate a variable intrinsic layout for
|
|
||||||
/// table widths. Note that this will do one main pass to try to properly
|
|
||||||
/// allocate widths. This will thus potentially cut off latter elements
|
|
||||||
/// (return size of 0) if it is too small (threshold), but will try its best.
|
|
||||||
///
|
///
|
||||||
/// `width thresholds` and `desired_widths_ratio` should be the same length.
|
/// * `total_width` is the, well, total width available. **NOTE:** This function automatically
|
||||||
/// Otherwise bad things happen.
|
/// takes away 2 from the width as part of the left/right
|
||||||
pub fn get_variable_intrinsic_widths(
|
/// bounds.
|
||||||
total_width: u16, desired_widths_ratio: &[f64], width_thresholds: &[usize],
|
/// * `hard_widths` is inflexible column widths. Use a `None` to represent a soft width.
|
||||||
) -> (Vec<u16>, usize) {
|
/// * `soft_widths_min` is the lower limit for a soft width. Use `None` if a hard width goes there.
|
||||||
let num_widths = desired_widths_ratio.len();
|
/// * `soft_widths_max` is the upper limit for a soft width, in percentage of the total width. Use
|
||||||
let mut resulting_widths: Vec<u16> = vec![0; num_widths];
|
/// `None` if a hard width goes there.
|
||||||
let mut last_index = 0;
|
/// * `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> {
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
let mut remaining_width = (total_width - (num_widths as u16 - 1)) as i32; // Required for spaces...
|
for itx in &range {
|
||||||
let desired_widths = desired_widths_ratio
|
if let Some(Some(hard_width)) = hard_widths.get(*itx) {
|
||||||
.iter()
|
// Hard width...
|
||||||
.map(|&desired_width_ratio| (desired_width_ratio * total_width as f64) as i32);
|
let space_taken = min(*hard_width, total_width_left);
|
||||||
|
|
||||||
for (desired_width, resulting_width, width_threshold) in izip!(
|
// TODO [COLUMN MOVEMENT]: Remove this
|
||||||
desired_widths,
|
if *hard_width > space_taken {
|
||||||
resulting_widths.iter_mut(),
|
break;
|
||||||
width_thresholds
|
|
||||||
) {
|
|
||||||
*resulting_width = if desired_width < *width_threshold as i32 {
|
|
||||||
// Try to take threshold, else, 0
|
|
||||||
if remaining_width < *width_threshold as i32 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
remaining_width -= *width_threshold as i32;
|
|
||||||
*width_threshold as u16
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Take as large as possible
|
column_widths[*itx] = space_taken;
|
||||||
if remaining_width < desired_width {
|
total_width_left -= space_taken;
|
||||||
// Check the biggest chunk possible
|
total_width_left = total_width_left.saturating_sub(1);
|
||||||
if remaining_width < *width_threshold as i32 {
|
} else if let (
|
||||||
0
|
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 {
|
} else {
|
||||||
let temp_width = remaining_width;
|
(*soft_width_max * initial_width as f64).ceil() as u16
|
||||||
remaining_width = 0;
|
},
|
||||||
temp_width 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;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
remaining_width -= desired_width;
|
|
||||||
desired_width as u16
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if *resulting_width == 0 {
|
let mut filtered_column_widths: Vec<u16> = vec![];
|
||||||
break;
|
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 {
|
} else {
|
||||||
last_index += 1;
|
filtered_column_widths.push(*width);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
filtered_column_widths.reverse();
|
||||||
// Simple redistribution tactic - if there's any space left, split it evenly amongst all members
|
filtered_column_widths
|
||||||
if last_index < num_widths && last_index != 0 {
|
|
||||||
let for_all_widths = (remaining_width / last_index as i32) as u16;
|
|
||||||
let mut remainder = remaining_width % last_index as i32;
|
|
||||||
|
|
||||||
for resulting_width in &mut resulting_widths {
|
|
||||||
*resulting_width += for_all_widths;
|
|
||||||
if remainder > 0 {
|
|
||||||
*resulting_width += 1;
|
|
||||||
remainder -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(resulting_widths, last_index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_search_start_position(
|
pub fn get_search_start_position(
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp::max;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{layout_manager::WidgetDirection, App},
|
app::{layout_manager::WidgetDirection, App},
|
||||||
canvas::{
|
canvas::{
|
||||||
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
|
drawing_utils::{get_column_widths, get_start_position},
|
||||||
Painter,
|
Painter,
|
||||||
},
|
},
|
||||||
constants::*,
|
constants::*,
|
||||||
|
@ -20,19 +19,14 @@ use tui::{
|
||||||
widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table},
|
widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table},
|
||||||
};
|
};
|
||||||
|
|
||||||
const CPU_SELECT_LEGEND_HEADER: [&str; 2] = ["CPU", "Show"];
|
|
||||||
const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
|
const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
|
||||||
const AVG_POSITION: usize = 1;
|
const AVG_POSITION: usize = 1;
|
||||||
const ALL_POSITION: usize = 0;
|
const ALL_POSITION: usize = 0;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CPU_LEGEND_HEADER_LENS: Vec<usize> = CPU_LEGEND_HEADER
|
static ref CPU_LEGEND_HEADER_LENS: Vec<u16> = CPU_LEGEND_HEADER
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
|
.map(|entry| entry.len() as u16)
|
||||||
.collect::<Vec<_>>();
|
|
||||||
static ref CPU_SELECT_LEGEND_HEADER_LENS: Vec<usize> = CPU_SELECT_LEGEND_HEADER
|
|
||||||
.iter()
|
|
||||||
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,6 +267,7 @@ impl CpuGraphWidget for Painter {
|
||||||
fn draw_cpu_legend<B: Backend>(
|
fn draw_cpu_legend<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
&self, f: &mut Frame<'_, B>, app_state: &mut App, 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))
|
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1))
|
||||||
{
|
{
|
||||||
cpu_widget_state.is_legend_hidden = false;
|
cpu_widget_state.is_legend_hidden = false;
|
||||||
|
@ -308,11 +303,54 @@ impl CpuGraphWidget for Painter {
|
||||||
.saturating_sub(start_position);
|
.saturating_sub(start_position);
|
||||||
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
|
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().filter_map(|(itx, cpu)| {
|
let cpu_rows = sliced_cpu_data.iter().enumerate().filter_map(|(itx, cpu)| {
|
||||||
let cpu_string_row: Vec<Cow<'_, str>> = vec![
|
let truncated_name: Cow<'_, str> =
|
||||||
Cow::Borrowed(&cpu.cpu_name),
|
if let (Some(desired_column_width), Some(calculated_column_width)) =
|
||||||
Cow::Borrowed(&cpu.legend_value),
|
(dcw.get(0), ccw.get(0))
|
||||||
];
|
{
|
||||||
|
if *desired_column_width > *calculated_column_width {
|
||||||
|
Cow::Borrowed(&cpu.short_cpu_name)
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(&cpu.cpu_name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(&cpu.cpu_name)
|
||||||
|
};
|
||||||
|
let truncated_legend: Cow<'_, str> =
|
||||||
|
if let Some(calculated_column_width) = ccw.get(0) {
|
||||||
|
if *calculated_column_width == 0 && cpu.legend_value.is_empty() {
|
||||||
|
Cow::Borrowed("All")
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(&cpu.legend_value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(&cpu.legend_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
let cpu_string_row: Vec<Cow<'_, str>> = vec![truncated_name, truncated_legend];
|
||||||
|
|
||||||
if cpu_string_row.is_empty() {
|
if cpu_string_row.is_empty() {
|
||||||
offset_scroll_index += 1;
|
offset_scroll_index += 1;
|
||||||
|
@ -341,14 +379,6 @@ impl CpuGraphWidget for Painter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate widths
|
|
||||||
let width = f64::from(draw_loc.width);
|
|
||||||
let width_ratios = vec![0.5, 0.5];
|
|
||||||
|
|
||||||
let variable_intrinsic_results =
|
|
||||||
get_variable_intrinsic_widths(width as u16, &width_ratios, &CPU_LEGEND_HEADER_LENS);
|
|
||||||
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
|
||||||
|
|
||||||
// Note we don't set highlight_style, as it should always be shown for this widget.
|
// 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 {
|
let border_and_title_style = if is_on_widget {
|
||||||
self.colours.highlighted_border_style
|
self.colours.highlighted_border_style
|
||||||
|
@ -367,7 +397,9 @@ impl CpuGraphWidget for Painter {
|
||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.highlight_style(self.colours.currently_selected_text_style)
|
.highlight_style(self.colours.currently_selected_text_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(cpu_widget_state
|
||||||
|
.table_width_state
|
||||||
|
.calculated_column_widths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::cmp::max;
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
@ -10,18 +9,20 @@ use tui::{
|
||||||
use crate::{
|
use crate::{
|
||||||
app,
|
app,
|
||||||
canvas::{
|
canvas::{
|
||||||
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
|
drawing_utils::{get_column_widths, get_start_position},
|
||||||
Painter,
|
Painter,
|
||||||
},
|
},
|
||||||
constants::*,
|
constants::*,
|
||||||
};
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"];
|
const DISK_HEADERS: [&str; 7] = ["Disk", "Mount", "Used", "Free", "Total", "R/s", "W/s"];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref DISK_HEADERS_LENS: Vec<usize> = DISK_HEADERS
|
static ref DISK_HEADERS_LENS: Vec<u16> = DISK_HEADERS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
|
.map(|entry| entry.len() as u16)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ impl DiskTableWidget for Painter {
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
widget_id: u64,
|
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) {
|
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
|
||||||
let disk_data: &mut [Vec<String>] = &mut app_state.canvas_data.disk_data;
|
let disk_data: &mut [Vec<String>] = &mut app_state.canvas_data.disk_data;
|
||||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||||
|
@ -61,16 +63,100 @@ impl DiskTableWidget for Painter {
|
||||||
.current_scroll_position
|
.current_scroll_position
|
||||||
.saturating_sub(start_position),
|
.saturating_sub(start_position),
|
||||||
));
|
));
|
||||||
let sliced_vec = &mut disk_data[start_position..];
|
let sliced_vec = &disk_data[start_position..];
|
||||||
let disk_rows = sliced_vec.iter().map(|disk| Row::Data(disk.iter()));
|
|
||||||
|
|
||||||
// Calculate widths
|
// Calculate widths
|
||||||
// TODO: [PRETTY] Ellipsis on strings?
|
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
|
||||||
let width = f64::from(draw_loc.width);
|
if recalculate_column_widths {
|
||||||
let width_ratios = [0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13];
|
disk_widget_state.table_width_state.desired_column_widths = {
|
||||||
let variable_intrinsic_results =
|
let mut column_widths = DISK_HEADERS_LENS.clone();
|
||||||
get_variable_intrinsic_widths(width as u16, &width_ratios, &DISK_HEADERS_LENS);
|
for row in sliced_vec {
|
||||||
let intrinsic_widths = &variable_intrinsic_results.0[0..variable_intrinsic_results.1];
|
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();
|
||||||
|
Cow::Owned(format!("{}…", first_n))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Row::Data(truncated_data)
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: This seems to be bugged? The selected text style gets "stuck"? I think this gets fixed with tui 0.10?
|
// TODO: This seems to be bugged? The selected text style gets "stuck"? I think this gets fixed with tui 0.10?
|
||||||
let (border_and_title_style, highlight_style) = if is_on_widget {
|
let (border_and_title_style, highlight_style) = if is_on_widget {
|
||||||
|
@ -148,7 +234,9 @@ impl DiskTableWidget for Painter {
|
||||||
.highlight_style(highlight_style)
|
.highlight_style(highlight_style)
|
||||||
.style(self.colours.text_style)
|
.style(self.colours.text_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(disk_widget_state
|
||||||
|
.table_width_state
|
||||||
|
.calculated_column_widths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::cmp::max;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
canvas::{drawing_utils::get_variable_intrinsic_widths, Painter},
|
canvas::{drawing_utils::get_column_widths, Painter},
|
||||||
constants::*,
|
constants::*,
|
||||||
utils::gen_util::*,
|
utils::gen_util::*,
|
||||||
};
|
};
|
||||||
|
@ -19,9 +19,9 @@ use tui::{
|
||||||
const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
|
const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref NETWORK_HEADERS_LENS: Vec<usize> = NETWORK_HEADERS
|
static ref NETWORK_HEADERS_LENS: Vec<u16> = NETWORK_HEADERS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
|
.map(|entry| entry.len() as u16)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,6 +342,7 @@ impl NetworkGraphWidget for Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: [DEPRECATED] Get rid of this in, like, 0.6...?
|
||||||
fn draw_network_labels<B: Backend>(
|
fn draw_network_labels<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
|
||||||
) {
|
) {
|
||||||
|
@ -362,13 +363,17 @@ impl NetworkGraphWidget for Painter {
|
||||||
.map(|val| Row::StyledData(val.iter(), self.colours.text_style));
|
.map(|val| Row::StyledData(val.iter(), self.colours.text_style));
|
||||||
|
|
||||||
// Calculate widths
|
// Calculate widths
|
||||||
let width_ratios: Vec<f64> = vec![0.25, 0.25, 0.25, 0.25];
|
let intrinsic_widths = get_column_widths(
|
||||||
let lens: &[usize] = &NETWORK_HEADERS_LENS;
|
draw_loc.width,
|
||||||
let width = f64::from(draw_loc.width);
|
&[None, None, None, None],
|
||||||
|
&[Some(6); 4],
|
||||||
let variable_intrinsic_results =
|
&[Some(0.25); 4],
|
||||||
get_variable_intrinsic_widths(width as u16, &width_ratios, lens);
|
&(NETWORK_HEADERS_LENS
|
||||||
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
.iter()
|
||||||
|
.map(|s| Some(*s))
|
||||||
|
.collect::<Vec<_>>()),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{data_harvester::processes::ProcessSorting, App},
|
app::App,
|
||||||
canvas::{
|
canvas::{
|
||||||
drawing_utils::{
|
drawing_utils::{get_column_widths, get_search_start_position, get_start_position},
|
||||||
get_search_start_position, get_start_position, get_variable_intrinsic_widths,
|
|
||||||
},
|
|
||||||
Painter,
|
Painter,
|
||||||
},
|
},
|
||||||
constants::*,
|
constants::*,
|
||||||
|
@ -16,6 +14,7 @@ use tui::{
|
||||||
widgets::{Block, Borders, Paragraph, Row, Table, Text},
|
widgets::{Block, Borders, Paragraph, Row, Table, Text},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
|
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
@ -108,7 +107,14 @@ impl ProcessTableWidget for Painter {
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
|
||||||
widget_id: u64,
|
widget_id: u64,
|
||||||
) {
|
) {
|
||||||
|
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(&widget_id) {
|
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) {
|
||||||
|
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 = widget_id == app_state.current_widget.widget_id;
|
let is_on_widget = widget_id == app_state.current_widget.widget_id;
|
||||||
let margined_draw_loc = Layout::default()
|
let margined_draw_loc = Layout::default()
|
||||||
.constraints([Constraint::Percentage(100)].as_ref())
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
@ -170,7 +176,7 @@ impl ProcessTableWidget for Painter {
|
||||||
|
|
||||||
if let Some(process_data) = &app_state
|
if let Some(process_data) = &app_state
|
||||||
.canvas_data
|
.canvas_data
|
||||||
.finalized_process_data_map
|
.stringified_process_data_map
|
||||||
.get(&widget_id)
|
.get(&widget_id)
|
||||||
{
|
{
|
||||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||||
|
@ -197,6 +203,14 @@ impl ProcessTableWidget for Painter {
|
||||||
};
|
};
|
||||||
|
|
||||||
let sliced_vec = &process_data[start_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;
|
let proc_table_state = &mut proc_widget_state.scroll_state.table_state;
|
||||||
proc_table_state.select(Some(
|
proc_table_state.select(Some(
|
||||||
proc_widget_state
|
proc_widget_state
|
||||||
|
@ -206,85 +220,198 @@ impl ProcessTableWidget for Painter {
|
||||||
));
|
));
|
||||||
|
|
||||||
// Draw!
|
// Draw!
|
||||||
let is_proc_widget_grouped = proc_widget_state.is_grouped;
|
|
||||||
let is_using_command = proc_widget_state.is_using_command;
|
|
||||||
let is_tree = proc_widget_state.is_tree_mode;
|
|
||||||
let mem_enabled = proc_widget_state.columns.is_enabled(&ProcessSorting::Mem);
|
|
||||||
|
|
||||||
// FIXME: [PROC OPTIMIZE] This can definitely be optimized; string references work fine here!
|
|
||||||
let process_rows = sliced_vec.iter().map(|process| {
|
|
||||||
let data = vec![
|
|
||||||
if is_proc_widget_grouped {
|
|
||||||
process.group_pids.len().to_string()
|
|
||||||
} else {
|
|
||||||
process.pid.to_string()
|
|
||||||
},
|
|
||||||
if is_tree {
|
|
||||||
if let Some(prefix) = &process.process_description_prefix {
|
|
||||||
prefix.clone()
|
|
||||||
} else {
|
|
||||||
String::default()
|
|
||||||
}
|
|
||||||
} else if is_using_command {
|
|
||||||
process.command.clone()
|
|
||||||
} else {
|
|
||||||
process.name.clone()
|
|
||||||
},
|
|
||||||
format!("{:.1}%", process.cpu_percent_usage),
|
|
||||||
if mem_enabled {
|
|
||||||
format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
|
||||||
} else {
|
|
||||||
format!("{:.1}%", process.mem_percent_usage)
|
|
||||||
},
|
|
||||||
process.read_per_sec.clone(),
|
|
||||||
process.write_per_sec.clone(),
|
|
||||||
process.total_read.clone(),
|
|
||||||
process.total_write.clone(),
|
|
||||||
process.process_state.clone(),
|
|
||||||
]
|
|
||||||
.into_iter();
|
|
||||||
|
|
||||||
if process.is_disabled_entry {
|
|
||||||
Row::StyledData(data, self.colours.disabled_text_style)
|
|
||||||
} else {
|
|
||||||
Row::Data(data)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let process_headers = proc_widget_state.columns.get_column_headers(
|
let process_headers = proc_widget_state.columns.get_column_headers(
|
||||||
&proc_widget_state.process_sorting_type,
|
&proc_widget_state.process_sorting_type,
|
||||||
proc_widget_state.is_process_sort_descending,
|
proc_widget_state.is_process_sort_descending,
|
||||||
);
|
);
|
||||||
|
|
||||||
let process_headers_lens: Vec<usize> = process_headers
|
|
||||||
.iter()
|
|
||||||
.map(|entry| entry.len())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Calculate widths
|
// Calculate widths
|
||||||
let width = f64::from(draw_loc.width);
|
let hard_widths = if proc_widget_state.is_grouped {
|
||||||
|
vec![
|
||||||
// TODO: This is a ugly work-around for now.
|
Some(7),
|
||||||
let width_ratios = if proc_widget_state.is_grouped {
|
None,
|
||||||
if proc_widget_state.is_using_command {
|
Some(8),
|
||||||
vec![0.05, 0.7, 0.05, 0.05, 0.0375, 0.0375, 0.0375, 0.0375]
|
Some(8),
|
||||||
} else {
|
Some(8),
|
||||||
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15]
|
Some(8),
|
||||||
}
|
Some(7),
|
||||||
} else if proc_widget_state.is_using_command {
|
Some(8),
|
||||||
vec![0.05, 0.7, 0.05, 0.05, 0.03, 0.03, 0.03, 0.03]
|
]
|
||||||
} else if proc_widget_state.is_tree_mode {
|
|
||||||
vec![0.05, 0.4, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
|
||||||
} else {
|
} else {
|
||||||
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
|
vec![
|
||||||
|
Some(7),
|
||||||
|
None,
|
||||||
|
Some(8),
|
||||||
|
Some(8),
|
||||||
|
Some(8),
|
||||||
|
Some(8),
|
||||||
|
Some(7),
|
||||||
|
Some(8),
|
||||||
|
None,
|
||||||
|
]
|
||||||
};
|
};
|
||||||
let variable_intrinsic_results = get_variable_intrinsic_widths(
|
|
||||||
width as u16,
|
if recalculate_column_widths {
|
||||||
&width_ratios,
|
let mut column_widths = process_headers
|
||||||
&process_headers_lens,
|
.iter()
|
||||||
);
|
.map(|entry| UnicodeWidthStr::width(entry.as_str()) as u16)
|
||||||
let intrinsic_widths =
|
.collect::<Vec<_>>();
|
||||||
&(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
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 {
|
||||||
|
if proc_widget_state.is_using_command {
|
||||||
|
vec![None, Some(0.7), None, None, None, None, None, None]
|
||||||
|
} else if proc_widget_state.is_tree_mode {
|
||||||
|
vec![None, Some(0.5), None, None, None, None, None, None]
|
||||||
|
} else {
|
||||||
|
vec![None, Some(0.4), None, None, None, None, None, None]
|
||||||
|
}
|
||||||
|
} else if proc_widget_state.is_using_command {
|
||||||
|
vec![
|
||||||
|
None,
|
||||||
|
Some(0.7),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(0.2),
|
||||||
|
]
|
||||||
|
} else if proc_widget_state.is_tree_mode {
|
||||||
|
vec![
|
||||||
|
None,
|
||||||
|
Some(0.5),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(0.2),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
None,
|
||||||
|
Some(0.3),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(0.2),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Cow::Borrowed(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();
|
||||||
|
Cow::Owned(format!("{}…", first_n))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if *disabled {
|
||||||
|
Row::StyledData(truncated_data, self.colours.disabled_text_style)
|
||||||
|
} else {
|
||||||
|
Row::Data(truncated_data)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: gotop's "x out of y" thing is really nice to help keep track of the scroll position.
|
// TODO: gotop's "x out of y" thing is really nice to help keep track of the scroll position.
|
||||||
f.render_stateful_widget(
|
f.render_stateful_widget(
|
||||||
|
@ -294,7 +421,9 @@ impl ProcessTableWidget for Painter {
|
||||||
.highlight_style(highlight_style)
|
.highlight_style(highlight_style)
|
||||||
.style(self.colours.text_style)
|
.style(self.colours.text_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(proc_widget_state
|
||||||
|
.table_width_state
|
||||||
|
.calculated_column_widths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|calculated_width| {
|
.map(|calculated_width| {
|
||||||
Constraint::Length(*calculated_width as u16)
|
Constraint::Length(*calculated_width as u16)
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::cmp::max;
|
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
@ -11,18 +9,20 @@ use tui::{
|
||||||
use crate::{
|
use crate::{
|
||||||
app,
|
app,
|
||||||
canvas::{
|
canvas::{
|
||||||
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
|
drawing_utils::{get_column_widths, get_start_position},
|
||||||
Painter,
|
Painter,
|
||||||
},
|
},
|
||||||
constants::*,
|
constants::*,
|
||||||
};
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
const TEMP_HEADERS: [&str; 2] = ["Sensor", "Temp"];
|
const TEMP_HEADERS: [&str; 2] = ["Sensor", "Temp"];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref TEMP_HEADERS_LENS: Vec<usize> = TEMP_HEADERS
|
static ref TEMP_HEADERS_LENS: Vec<u16> = TEMP_HEADERS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| max(FORCE_MIN_THRESHOLD, entry.len()))
|
.map(|entry| entry.len() as u16)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
}
|
}
|
||||||
pub trait TempTableWidget {
|
pub trait TempTableWidget {
|
||||||
|
@ -37,6 +37,7 @@ impl TempTableWidget for Painter {
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
widget_id: u64,
|
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) {
|
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
|
||||||
let temp_sensor_data: &mut [Vec<String>] = &mut app_state.canvas_data.temp_sensor_data;
|
let temp_sensor_data: &mut [Vec<String>] = &mut app_state.canvas_data.temp_sensor_data;
|
||||||
|
|
||||||
|
@ -63,14 +64,82 @@ impl TempTableWidget for Painter {
|
||||||
.saturating_sub(start_position),
|
.saturating_sub(start_position),
|
||||||
));
|
));
|
||||||
let sliced_vec = &temp_sensor_data[start_position..];
|
let sliced_vec = &temp_sensor_data[start_position..];
|
||||||
let temperature_rows = sliced_vec.iter().map(|temp_row| Row::Data(temp_row.iter()));
|
|
||||||
|
|
||||||
// Calculate widths
|
// Calculate widths
|
||||||
let width = f64::from(draw_loc.width);
|
let hard_widths = [None, None];
|
||||||
let width_ratios = [0.5, 0.5];
|
if recalculate_column_widths {
|
||||||
let variable_intrinsic_results =
|
temp_widget_state.table_width_state.desired_column_widths = {
|
||||||
get_variable_intrinsic_widths(width as u16, &width_ratios, &TEMP_HEADERS_LENS);
|
let mut column_widths = TEMP_HEADERS_LENS.clone();
|
||||||
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
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();
|
||||||
|
Cow::Owned(format!("{}…", first_n))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Row::Data(truncated_data)
|
||||||
|
});
|
||||||
|
|
||||||
let (border_and_title_style, highlight_style) = if is_on_widget {
|
let (border_and_title_style, highlight_style) = if is_on_widget {
|
||||||
(
|
(
|
||||||
|
@ -128,7 +197,9 @@ impl TempTableWidget for Painter {
|
||||||
.highlight_style(highlight_style)
|
.highlight_style(highlight_style)
|
||||||
.style(self.colours.text_style)
|
.style(self.colours.text_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(temp_widget_state
|
||||||
|
.table_width_state
|
||||||
|
.calculated_column_widths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
|
|
|
@ -19,10 +19,6 @@ pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u64 = 1000;
|
||||||
// Number of colours to generate for the CPU chart/table
|
// Number of colours to generate for the CPU chart/table
|
||||||
pub const NUM_COLOURS: usize = 256;
|
pub const NUM_COLOURS: usize = 256;
|
||||||
|
|
||||||
// Canvas stuff
|
|
||||||
// The minimum threshold when resizing tables
|
|
||||||
pub const FORCE_MIN_THRESHOLD: usize = 5;
|
|
||||||
|
|
||||||
// Limits for when we should stop showing table gaps/labels (anything less means not shown)
|
// Limits for when we should stop showing table gaps/labels (anything less means not shown)
|
||||||
pub const TABLE_GAP_HEIGHT_LIMIT: u16 = 7;
|
pub const TABLE_GAP_HEIGHT_LIMIT: u16 = 7;
|
||||||
pub const TIME_LABEL_HEIGHT_LIMIT: u16 = 7;
|
pub const TIME_LABEL_HEIGHT_LIMIT: u16 = 7;
|
||||||
|
@ -249,7 +245,7 @@ pub const DEFAULT_BATTERY_LAYOUT: &str = r##"
|
||||||
pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml";
|
pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml";
|
||||||
|
|
||||||
// Default config file
|
// Default config file
|
||||||
// FIXME: Update the default config
|
// FIXME [CHORE]: Update the default config
|
||||||
pub const DEFAULT_CONFIG_CONTENT: &str = r##"
|
pub const DEFAULT_CONFIG_CONTENT: &str = r##"
|
||||||
# This is a default config file for bottom. All of the settings are commented
|
# This is a default config file for bottom. All of the settings are commented
|
||||||
# out by default; if you wish to change them uncomment and modify as you see
|
# out by default; if you wish to change them uncomment and modify as you see
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! can actually handle.
|
//! can actually handle.
|
||||||
use crate::Pid;
|
use crate::Pid;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{data_farmer, data_harvester, App, Filter},
|
app::{data_farmer, data_harvester, App, Filter, ProcWidgetState},
|
||||||
utils::{self, gen_util::*},
|
utils::{self, gen_util::*},
|
||||||
};
|
};
|
||||||
use data_harvester::processes::ProcessSorting;
|
use data_harvester::processes::ProcessSorting;
|
||||||
|
@ -71,6 +71,7 @@ pub struct ConvertedProcessData {
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct ConvertedCpuData {
|
pub struct ConvertedCpuData {
|
||||||
pub cpu_name: String,
|
pub cpu_name: String,
|
||||||
|
pub short_cpu_name: String,
|
||||||
/// Tuple is time, value
|
/// Tuple is time, value
|
||||||
pub cpu_data: Vec<Point>,
|
pub cpu_data: Vec<Point>,
|
||||||
/// Represents the value displayed on the legend.
|
/// Represents the value displayed on the legend.
|
||||||
|
@ -196,10 +197,24 @@ pub fn convert_cpu_data_points(
|
||||||
let mut new_cpu_data = ConvertedCpuData::default();
|
let mut new_cpu_data = ConvertedCpuData::default();
|
||||||
new_cpu_data.cpu_name = if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx)
|
new_cpu_data.cpu_name = if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx)
|
||||||
{
|
{
|
||||||
cpu_harvest.cpu_name.to_string()
|
if let Some(cpu_count) = cpu_harvest.cpu_count {
|
||||||
|
format!("{}{}", cpu_harvest.cpu_prefix, cpu_count)
|
||||||
|
} else {
|
||||||
|
cpu_harvest.cpu_prefix.to_string()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String::default()
|
String::default()
|
||||||
};
|
};
|
||||||
|
new_cpu_data.short_cpu_name =
|
||||||
|
if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx) {
|
||||||
|
if let Some(cpu_count) = cpu_harvest.cpu_count {
|
||||||
|
cpu_count.to_string()
|
||||||
|
} else {
|
||||||
|
cpu_harvest.cpu_prefix.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::default()
|
||||||
|
};
|
||||||
cpu_data_vector.push(new_cpu_data);
|
cpu_data_vector.push(new_cpu_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +231,7 @@ pub fn convert_cpu_data_points(
|
||||||
|
|
||||||
let mut extended_vec = vec![ConvertedCpuData {
|
let mut extended_vec = vec![ConvertedCpuData {
|
||||||
cpu_name: "All".to_string(),
|
cpu_name: "All".to_string(),
|
||||||
|
short_cpu_name: "All".to_string(),
|
||||||
cpu_data: vec![],
|
cpu_data: vec![],
|
||||||
legend_value: String::new(),
|
legend_value: String::new(),
|
||||||
}];
|
}];
|
||||||
|
@ -413,7 +429,7 @@ pub enum ProcessNamingType {
|
||||||
pub fn convert_process_data(
|
pub fn convert_process_data(
|
||||||
current_data: &data_farmer::DataCollection,
|
current_data: &data_farmer::DataCollection,
|
||||||
) -> Vec<ConvertedProcessData> {
|
) -> Vec<ConvertedProcessData> {
|
||||||
// FIXME: Thread highlighting and hiding support
|
// TODO [THREAD]: Thread highlighting and hiding support
|
||||||
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
// For macOS see https://github.com/hishamhm/htop/pull/848/files
|
||||||
|
|
||||||
current_data
|
current_data
|
||||||
|
@ -471,6 +487,7 @@ pub fn tree_process_data(
|
||||||
sort_type: &ProcessSorting, is_sort_descending: bool,
|
sort_type: &ProcessSorting, is_sort_descending: bool,
|
||||||
) -> Vec<ConvertedProcessData> {
|
) -> Vec<ConvertedProcessData> {
|
||||||
// TODO: [TREE] Allow for collapsing entries.
|
// TODO: [TREE] Allow for collapsing entries.
|
||||||
|
// TODO: [TREE] Option to sort usage by total branch usage or individual value usage?
|
||||||
|
|
||||||
// Let's first build up a (really terrible) parent -> child mapping...
|
// Let's first build up a (really terrible) parent -> child mapping...
|
||||||
// At the same time, let's make a mapping of PID -> process data!
|
// At the same time, let's make a mapping of PID -> process data!
|
||||||
|
@ -565,18 +582,14 @@ pub fn tree_process_data(
|
||||||
pid_process_mapping: &HashMap<Pid, &ConvertedProcessData>,
|
pid_process_mapping: &HashMap<Pid, &ConvertedProcessData>,
|
||||||
) {
|
) {
|
||||||
// Sorting is special for tree data. So, by default, things are "sorted"
|
// Sorting is special for tree data. So, by default, things are "sorted"
|
||||||
// via the DFS, except for (at least Unix) PID 1 and 2, which are in that order.
|
// via the DFS. Otherwise, since this is DFS of the scanned PIDs (which are in order),
|
||||||
// Otherwise, since this is DFS of the scanned PIDs (which are in order), you actually
|
// you actually get a REVERSE order --- so, you get higher PIDs earlier than lower ones.
|
||||||
// get a REVERSE order --- so, you get higher PIDs earlier than lower ones.
|
|
||||||
// But this is a tree. So, you'll get a bit of a combination, but the general idea
|
|
||||||
// is that in a tree level, it's descending order, except, again, for the first layer.
|
|
||||||
// This is how htop does it by default.
|
|
||||||
//
|
//
|
||||||
// So how do we "sort"? The current idea is that:
|
// So how do we "sort"? The current idea is that:
|
||||||
// - We sort *per-level*. Say, I want to sort by CPU. The "first level" is sorted
|
// - We sort *per-level*. Say, I want to sort by CPU. The "first level" is sorted
|
||||||
// by CPU in terms of its usage. All its direct children are sorted by CPU
|
// by CPU in terms of its usage. All its direct children are sorted by CPU
|
||||||
// with *their* siblings. Etc.
|
// with *their* siblings. Etc.
|
||||||
// - The default is thus PIDs in reverse order (descending). We set it to this when
|
// - The default is thus PIDs in ascending order. We set it to this when
|
||||||
// we first enable the mode.
|
// we first enable the mode.
|
||||||
|
|
||||||
// So first, let's look at the children... (post-order again)
|
// So first, let's look at the children... (post-order again)
|
||||||
|
@ -701,8 +714,7 @@ pub fn tree_process_data(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A DFS traversal to correctly build the prefix lines (the pretty '├' and '─' lines) and
|
/// A DFS traversal to correctly build the prefix lines (the pretty '├' and '─' lines) and
|
||||||
/// the correct order to the PID tree as a vector (DFS is the default order htop seems to use
|
/// the correct order to the PID tree as a vector.
|
||||||
/// so we're shamelessly copying that).
|
|
||||||
fn build_explored_pids(
|
fn build_explored_pids(
|
||||||
current_pid: Pid, parent_child_mapping: &HashMap<Pid, IndexSet<Pid>>,
|
current_pid: Pid, parent_child_mapping: &HashMap<Pid, IndexSet<Pid>>,
|
||||||
prev_drawn_lines: &str,
|
prev_drawn_lines: &str,
|
||||||
|
@ -801,6 +813,65 @@ pub fn tree_process_data(
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub 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;
|
||||||
|
let is_using_command = proc_widget_state.is_using_command;
|
||||||
|
let is_tree = proc_widget_state.is_tree_mode;
|
||||||
|
let mem_enabled = proc_widget_state.columns.is_enabled(&ProcessSorting::Mem);
|
||||||
|
|
||||||
|
finalized_process_data
|
||||||
|
.iter()
|
||||||
|
.map(|process| {
|
||||||
|
(
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
if is_proc_widget_grouped {
|
||||||
|
process.group_pids.len().to_string()
|
||||||
|
} else {
|
||||||
|
process.pid.to_string()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
if is_tree {
|
||||||
|
if let Some(prefix) = &process.process_description_prefix {
|
||||||
|
prefix.clone()
|
||||||
|
} else {
|
||||||
|
String::default()
|
||||||
|
}
|
||||||
|
} else if is_using_command {
|
||||||
|
process.command.clone()
|
||||||
|
} else {
|
||||||
|
process.name.clone()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(format!("{:.1}%", process.cpu_percent_usage), None),
|
||||||
|
(
|
||||||
|
if mem_enabled {
|
||||||
|
format!("{:.0}{}", process.mem_usage_str.0, process.mem_usage_str.1)
|
||||||
|
} else {
|
||||||
|
format!("{:.1}%", process.mem_percent_usage)
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(process.read_per_sec.clone(), None),
|
||||||
|
(process.write_per_sec.clone(), None),
|
||||||
|
(process.total_read.clone(), None),
|
||||||
|
(process.total_write.clone(), None),
|
||||||
|
(
|
||||||
|
process.process_state.clone(),
|
||||||
|
Some(process.process_char.to_string()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
process.is_disabled_entry,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn group_process_data(
|
pub fn group_process_data(
|
||||||
single_process_data: &[ConvertedProcessData], is_using_command: bool,
|
single_process_data: &[ConvertedProcessData], is_using_command: bool,
|
||||||
) -> Vec<ConvertedProcessData> {
|
) -> Vec<ConvertedProcessData> {
|
||||||
|
@ -878,9 +949,9 @@ pub fn group_process_data(
|
||||||
wps_f64: p.write_per_sec,
|
wps_f64: p.write_per_sec,
|
||||||
tr_f64: p.total_read,
|
tr_f64: p.total_read,
|
||||||
tw_f64: p.total_write,
|
tw_f64: p.total_write,
|
||||||
process_state: p.process_state, // TODO: What the heck
|
process_state: p.process_state,
|
||||||
process_description_prefix: None,
|
process_description_prefix: None,
|
||||||
process_char: char::default(), // TODO: What the heck
|
process_char: char::default(),
|
||||||
is_disabled_entry: false,
|
is_disabled_entry: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -483,7 +483,6 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||||
if !app.is_frozen {
|
if !app.is_frozen {
|
||||||
app.canvas_data.single_process_data = convert_process_data(&app.data_collection);
|
app.canvas_data.single_process_data = convert_process_data(&app.data_collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
let process_filter = app.get_process_filter(widget_id);
|
let process_filter = app.get_process_filter(widget_id);
|
||||||
let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
let filtered_process_data: Vec<ConvertedProcessData> = if is_tree {
|
||||||
app.canvas_data
|
app.canvas_data
|
||||||
|
@ -547,6 +546,10 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||||
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
|
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
|
app.canvas_data
|
||||||
.finalized_process_data_map
|
.finalized_process_data_map
|
||||||
.insert(widget_id, finalized_process_data);
|
.insert(widget_id, finalized_process_data);
|
||||||
|
|
Loading…
Reference in a new issue