feature: Allow sorting by any column

This feature allows any column to be sortable.

This also adds:
- Inverting sort for current column with `I`
- Invoking a sort widget with `s` or `F6`.  Close with same key or esc.

And:
- A bugfix in regards the basic menu and battery widget
- A lot of refactoring
This commit is contained in:
Clement Tsang 2020-08-15 17:35:49 -07:00 committed by GitHub
parent 84f63f2f83
commit f3897f0538
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1384 additions and 756 deletions

View file

@ -31,6 +31,7 @@
"shilangyu",
"softirq",
"stime",
"subwidget",
"sysinfo",
"tokei",
"twrite",

View file

@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [179](https://github.com/ClementTsang/bottom/pull/179): Show full command/process path as an option.
- [183](https://github.com/ClementTsang/bottom/pull/183): Added sorting capabilities to any column.
### Changes
- Added `WASD` as an alternative widget movement system.
@ -25,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Bug Fixes
- [183](https://github.com/ClementTsang/bottom/pull/183): Fixed bug in basic mode where the battery widget was placed incorrectly.
## [0.4.5] - 2020-07-08
- No changes here, just an uptick for Crates.io using the wrong Cargo.lock.

View file

@ -38,6 +38,7 @@ backtrace = "0.3"
serde = {version = "1.0", features = ["derive"] }
unicode-segmentation = "1.6.0"
unicode-width = "0.1.7"
# tui = {version = "0.10.0", features = ["crossterm"], default-features = false, git = "https://github.com/fdehau/tui-rs.git"}
tui = {version = "0.10.0", features = ["crossterm"], default-features = false }
# For debugging only...

View file

@ -28,6 +28,7 @@ A cross-platform graphical process/system monitor with a customizable interface
- [CPU bindings](#cpu-bindings)
- [Process bindings](#process-bindings)
- [Process search bindings](#process-search-bindings)
- [Process sort bindings](#process-sort-bindings)
- [Battery bindings](#battery-bindings)
- [Process searching keywords](#process-searching-keywords)
- [Supported keywords](#supported-keywords)
@ -222,6 +223,8 @@ Run using `btm`.
| `Tab` | Group/un-group processes with the same name |
| `Ctrl-f`, `/` | Open process search widget |
| `P` | Toggle between showing the full path or just the process name |
| `s, F6` | Open process sort widget |
| `I` | Invert current sort |
#### Process search bindings
@ -240,6 +243,16 @@ Run using `btm`.
| `Left` | Move cursor left |
| `Right` | Move cursor right |
### Process sort bindings
| | |
| -------------- | ------------------------------- |
| `Down`, `j` | Scroll down in list |
| `Up`, `k` | Scroll up in list |
| `Mouse scroll` | Scroll through sort widget |
| `Esc` | Close the sort widget |
| `Enter` | Sort by current selected column |
#### Battery bindings
| | |

File diff suppressed because it is too large Load diff

View file

@ -49,7 +49,7 @@ pub struct DataCollection {
pub network_harvest: network::NetworkHarvest,
pub memory_harvest: mem::MemHarvest,
pub swap_harvest: mem::MemHarvest,
pub cpu_harvest: cpu::CPUHarvest,
pub cpu_harvest: cpu::CpuHarvest,
pub process_harvest: Vec<processes::ProcessHarvest>,
pub disk_harvest: Vec<disks::DiskHarvest>,
pub io_harvest: disks::IOHarvest,
@ -67,7 +67,7 @@ impl Default for DataCollection {
network_harvest: network::NetworkHarvest::default(),
memory_harvest: mem::MemHarvest::default(),
swap_harvest: mem::MemHarvest::default(),
cpu_harvest: cpu::CPUHarvest::default(),
cpu_harvest: cpu::CpuHarvest::default(),
process_harvest: Vec::default(),
disk_harvest: Vec::default(),
io_harvest: disks::IOHarvest::default(),
@ -84,7 +84,7 @@ impl DataCollection {
self.network_harvest = network::NetworkHarvest::default();
self.memory_harvest = mem::MemHarvest::default();
self.swap_harvest = mem::MemHarvest::default();
self.cpu_harvest = cpu::CPUHarvest::default();
self.cpu_harvest = cpu::CpuHarvest::default();
self.process_harvest = Vec::default();
self.disk_harvest = Vec::default();
self.io_harvest = disks::IOHarvest::default();
@ -205,7 +205,7 @@ impl DataCollection {
self.network_harvest = network.clone();
}
fn eat_cpu(&mut self, cpu: &[cpu::CPUData], new_entry: &mut TimedData) {
fn eat_cpu(&mut self, cpu: &[cpu::CpuData], new_entry: &mut TimedData) {
// Note this only pre-calculates the data points - the names will be
// within the local copy of cpu_harvest. Since it's all sequential
// it probably doesn't matter anyways.

View file

@ -24,7 +24,7 @@ pub mod temperature;
#[derive(Clone, Debug)]
pub struct Data {
pub last_collection_time: Instant,
pub cpu: Option<cpu::CPUHarvest>,
pub cpu: Option<cpu::CpuHarvest>,
pub memory: Option<mem::MemHarvest>,
pub swap: Option<mem::MemHarvest>,
pub temperature_sensors: Option<Vec<temperature::TempHarvest>>,

View file

@ -1,27 +1,27 @@
use sysinfo::{ProcessorExt, System, SystemExt};
#[derive(Default, Debug, Clone)]
pub struct CPUData {
pub struct CpuData {
pub cpu_name: String,
pub cpu_usage: f64,
}
pub type CPUHarvest = Vec<CPUData>;
pub type CpuHarvest = Vec<CpuData>;
pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> CPUHarvest {
pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> CpuHarvest {
let cpu_data = sys.get_processors();
let avg_cpu_usage = sys.get_global_processor_info().get_cpu_usage();
let mut cpu_vec = vec![];
if show_average_cpu {
cpu_vec.push(CPUData {
cpu_vec.push(CpuData {
cpu_name: "AVG".to_string(),
cpu_usage: avg_cpu_usage as f64,
});
}
for (itx, cpu) in cpu_data.iter().enumerate() {
cpu_vec.push(CPUData {
cpu_vec.push(CpuData {
cpu_name: format!("CPU{}", itx),
cpu_usage: f64::from(cpu.get_cpu_usage()),
});

View file

@ -12,17 +12,48 @@ use std::{
#[cfg(not(target_os = "linux"))]
use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt};
#[derive(Clone)]
// TODO: Add value so we know if it's sorted ascending or descending by default?
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum ProcessSorting {
CPU,
MEM,
PID,
IDENTIFIER,
CpuPercent,
Mem,
MemPercent,
Pid,
ProcessName,
Command,
ReadPerSecond,
WritePerSecond,
TotalRead,
TotalWrite,
State,
}
impl std::fmt::Display for ProcessSorting {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use ProcessSorting::*;
write!(
f,
"{}",
match &self {
CpuPercent => "CPU%",
MemPercent => "Mem%",
Mem => "Mem",
ReadPerSecond => "R/s",
WritePerSecond => "W/s",
TotalRead => "Read",
TotalWrite => "Write",
State => "State",
ProcessName => "Name",
Command => "Command",
Pid => "PID",
}
)
}
}
impl Default for ProcessSorting {
fn default() -> Self {
ProcessSorting::CPU
ProcessSorting::CpuPercent
}
}

View file

@ -541,7 +541,7 @@ impl BottomLayout {
.widget_id(4)
.up_neighbour(Some(100))
.left_neighbour(Some(8))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(DEFAULT_WIDGET_ID + 2))
.build()])
.build()])
.build(),
@ -550,15 +550,29 @@ impl BottomLayout {
.children(vec![
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(8))
.build()])
.total_widget_ratio(3)
.children(vec![
BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::ProcSort)
.widget_id(DEFAULT_WIDGET_ID + 2)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.width_ratio(1)
.build(),
BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(DEFAULT_WIDGET_ID + 2))
.right_neighbour(Some(7))
.width_ratio(2)
.build(),
])
.build(),
BottomColRow::builder()
.canvas_handle_height(true)
@ -614,7 +628,7 @@ impl BottomLayout {
.widget_id(4)
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(DEFAULT_WIDGET_ID + 2))
.build()])
.build()])
.build(),
@ -623,15 +637,26 @@ impl BottomLayout {
.children(vec![
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.children(vec![
BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::ProcSort)
.widget_id(DEFAULT_WIDGET_ID + 2)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.build(),
BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(DEFAULT_WIDGET_ID + 2))
.right_neighbour(Some(7))
.build(),
])
.build(),
BottomColRow::builder()
.canvas_handle_height(true)
@ -730,180 +755,6 @@ impl BottomLayout {
],
}
}
pub fn init_default(left_legend: bool, use_battery: bool) -> Self {
let cpu_layout = if left_legend {
vec![
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(11))
.right_neighbour(Some(1))
.canvas_handle_width(true)
.build(),
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(12))
.left_neighbour(Some(2))
.right_neighbour(if use_battery { Some(99) } else { None })
.flex_grow(true)
.build(),
]
} else {
vec![
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(11))
.right_neighbour(Some(2))
.flex_grow(true)
.build(),
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(12))
.left_neighbour(Some(1))
.right_neighbour(if use_battery { Some(99) } else { None })
.canvas_handle_width(true)
.build(),
]
};
let first_row_layout = if use_battery {
vec![
BottomCol::builder()
.col_width_ratio(2)
.children(vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(cpu_layout)
.build()])
.build(),
BottomCol::builder()
.col_width_ratio(1)
.children(vec![BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Battery)
.widget_id(99)
.down_neighbour(Some(12))
.left_neighbour(Some(if left_legend { 1 } else { 2 }))
.canvas_handle_width(true)
.build()])
.build()])
.build(),
]
} else {
vec![BottomCol::builder()
.children(vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(cpu_layout)
.build()])
.build()]
};
BottomLayout {
total_row_height_ratio: 100,
rows: vec![
BottomRow::builder()
.row_height_ratio(30)
.total_col_ratio(if use_battery { 3 } else { 1 })
.children(first_row_layout)
.build(),
BottomRow::builder()
.total_col_ratio(7)
.row_height_ratio(40)
.children(vec![
BottomCol::builder()
.col_width_ratio(4)
.children(vec![BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Mem)
.widget_id(11)
.right_neighbour(Some(12))
.up_neighbour(Some(1))
.down_neighbour(Some(21))
.build()])
.build()])
.build(),
BottomCol::builder()
.total_col_row_ratio(2)
.col_width_ratio(3)
.children(vec![
BottomColRow::builder()
.col_row_height_ratio(1)
.total_widget_ratio(2)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Temp)
.widget_id(12)
.left_neighbour(Some(11))
.up_neighbour(Some(1))
.down_neighbour(Some(13))
.build()])
.build(),
BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Disk)
.widget_id(13)
.left_neighbour(Some(11))
.up_neighbour(Some(12))
.down_neighbour(Some(DEFAULT_WIDGET_ID))
.build()])
.build(),
])
.build(),
])
.build(),
BottomRow::builder()
.total_col_ratio(2)
.row_height_ratio(30)
.children(vec![
BottomCol::builder()
.children(vec![BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Net)
.widget_id(21)
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.up_neighbour(Some(11))
.build()])
.build()])
.build(),
BottomCol::builder()
.total_col_row_ratio(2)
.children(vec![
BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.left_neighbour(Some(21))
.up_neighbour(Some(13))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.build()])
.flex_grow(true)
.build(),
BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(DEFAULT_WIDGET_ID + 1)
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(21))
.build()])
.canvas_handle_height(true)
.build(),
])
.build(),
])
.build(),
],
}
}
}
/// Represents a single row in the layout.
@ -961,6 +812,25 @@ pub struct BottomColRow {
pub flex_grow: bool,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum WidgetDirection {
Left,
Right,
Up,
Down,
}
impl WidgetDirection {
pub fn is_opposite(&self, other_direction: &WidgetDirection) -> bool {
match &self {
WidgetDirection::Left => *other_direction == WidgetDirection::Right,
WidgetDirection::Right => *other_direction == WidgetDirection::Left,
WidgetDirection::Up => *other_direction == WidgetDirection::Down,
WidgetDirection::Down => *other_direction == WidgetDirection::Up,
}
}
}
/// Represents a single widget.
#[derive(Debug, Default, Clone, TypedBuilder)]
pub struct BottomWidget {
@ -982,11 +852,17 @@ pub struct BottomWidget {
#[builder(default = None)]
pub down_neighbour: Option<u64>,
/// If set to true, the canvas will override any ratios.
#[builder(default = false)]
pub canvas_handle_width: bool,
/// Whether we want this widget to take up all available room (and ignore any ratios).
#[builder(default = false)]
pub flex_grow: bool,
/// The value is the direction to bounce, as well as the parent offset.
#[builder(default = None)]
pub parent_reflector: Option<(WidgetDirection, u64)>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
@ -998,6 +874,7 @@ pub enum BottomWidgetType {
Net,
Proc,
ProcSearch,
ProcSort,
Temp,
Disk,
BasicCpu,
@ -1011,7 +888,7 @@ impl BottomWidgetType {
pub fn is_widget_table(&self) -> bool {
use BottomWidgetType::*;
match self {
Disk | Proc | Temp | CpuLegend => true,
Disk | Proc | ProcSort | Temp | CpuLegend => true,
_ => false,
}
}

View file

@ -7,27 +7,27 @@ use tui::widgets::TableState;
use crate::{
app::{layout_manager::BottomWidgetType, query::*},
constants,
data_harvester::processes,
data_harvester::processes::{self, ProcessSorting},
};
#[derive(Debug)]
pub enum ScrollDirection {
// UP means scrolling up --- this usually DECREMENTS
UP,
Up,
// DOWN means scrolling down --- this usually INCREMENTS
DOWN,
Down,
}
impl Default for ScrollDirection {
fn default() -> Self {
ScrollDirection::DOWN
ScrollDirection::Down
}
}
#[derive(Debug)]
pub enum CursorDirection {
LEFT,
RIGHT,
Left,
Right,
}
/// AppScrollWidgetState deals with fields for a scrollable app's current state.
@ -85,7 +85,7 @@ impl Default for AppSearchState {
is_invalid_search: false,
is_blank_search: true,
grapheme_cursor: GraphemeCursor::new(0, 0, true),
cursor_direction: CursorDirection::RIGHT,
cursor_direction: CursorDirection::Right,
cursor_bar: 0,
char_cursor_position: 0,
query: None,
@ -141,15 +141,183 @@ impl ProcessSearchState {
}
}
pub struct ColumnInfo {
pub enabled: bool,
pub shortcut: Option<&'static str>,
}
pub struct ProcColumn {
pub ordered_columns: Vec<ProcessSorting>,
pub column_mapping: HashMap<ProcessSorting, ColumnInfo>,
pub longest_header_len: u16,
pub column_state: TableState,
pub scroll_direction: ScrollDirection,
pub current_scroll_position: usize,
pub previous_scroll_position: usize,
pub backup_prev_scroll_position: usize,
}
impl Default for ProcColumn {
fn default() -> Self {
use ProcessSorting::*;
let ordered_columns = vec![
Pid,
ProcessName,
Command,
CpuPercent,
MemPercent,
ReadPerSecond,
WritePerSecond,
TotalRead,
TotalWrite,
State,
];
let mut column_mapping = HashMap::new();
let mut longest_header_len = 0;
for column in ordered_columns.clone() {
longest_header_len = std::cmp::max(longest_header_len, column.to_string().len());
match column {
CpuPercent => {
column_mapping.insert(
column,
ColumnInfo {
enabled: true,
shortcut: Some("c"),
},
);
}
MemPercent | Mem => {
column_mapping.insert(
column,
ColumnInfo {
enabled: true,
shortcut: Some("m"),
},
);
}
ProcessName => {
column_mapping.insert(
column,
ColumnInfo {
enabled: true,
shortcut: Some("n"),
},
);
}
Command => {
column_mapping.insert(
column,
ColumnInfo {
enabled: false,
shortcut: Some("n"),
},
);
}
Pid => {
column_mapping.insert(
column,
ColumnInfo {
enabled: true,
shortcut: Some("p"),
},
);
}
_ => {
column_mapping.insert(
column,
ColumnInfo {
enabled: true,
shortcut: None,
},
);
}
}
}
let longest_header_len = longest_header_len as u16;
ProcColumn {
ordered_columns,
column_mapping,
longest_header_len,
column_state: TableState::default(),
scroll_direction: ScrollDirection::default(),
current_scroll_position: 0,
previous_scroll_position: 0,
backup_prev_scroll_position: 0,
}
}
}
impl ProcColumn {
pub fn get_enabled_columns_len(&self) -> usize {
self.ordered_columns
.iter()
.filter_map(|column_type| {
if self.column_mapping.get(&column_type).unwrap().enabled {
Some(1)
} else {
None
}
})
.sum()
}
pub fn set_to_sorted_index(&mut self, proc_sorting_type: &ProcessSorting) {
// TODO [Custom Columns]: If we add custom columns, this may be needed! Since column indices will change, this runs the risk of OOB. So, when you change columns, CALL THIS AND ADAPT!
let mut true_index = 0;
for column in &self.ordered_columns {
if *column == *proc_sorting_type {
break;
}
if self.column_mapping.get(column).unwrap().enabled {
true_index += 1;
}
}
self.current_scroll_position = true_index;
self.backup_prev_scroll_position = self.previous_scroll_position;
}
pub fn get_column_headers(
&self, proc_sorting_type: &ProcessSorting, sort_reverse: bool,
) -> Vec<String> {
// TODO: Gonna have to figure out how to do left/right GUI notation if we add it.
self.ordered_columns
.iter()
.filter_map(|column_type| {
let mapping = self.column_mapping.get(&column_type).unwrap();
let mut command_str = String::default();
if let Some(command) = mapping.shortcut {
command_str = format!("({})", command);
}
if mapping.enabled {
Some(if proc_sorting_type == column_type {
column_type.to_string()
+ command_str.as_str()
+ if sort_reverse { "" } else { "" }
} else {
column_type.to_string() + command_str.as_str()
})
} else {
None
}
})
.collect()
}
}
pub struct ProcWidgetState {
pub process_search_state: ProcessSearchState,
pub is_grouped: bool,
pub scroll_state: AppScrollWidgetState,
pub process_sorting_type: processes::ProcessSorting,
pub process_sorting_reverse: bool,
pub is_using_full_path: bool,
pub is_using_command: bool,
pub current_column_index: usize,
pub num_columns: usize,
pub is_sort_open: bool,
pub columns: ProcColumn,
}
impl ProcWidgetState {
@ -168,18 +336,79 @@ impl ProcWidgetState {
process_search_state.search_toggle_regex();
}
let process_sorting_type = processes::ProcessSorting::CpuPercent;
// TODO: If we add customizable columns, this should pull from config
let mut columns = ProcColumn::default();
columns.set_to_sorted_index(&process_sorting_type);
ProcWidgetState {
process_search_state,
is_grouped,
scroll_state: AppScrollWidgetState::default(),
process_sorting_type: processes::ProcessSorting::CPU,
process_sorting_type,
process_sorting_reverse: true,
is_using_full_path: false,
is_using_command: false,
current_column_index: 0,
num_columns: 1,
is_sort_open: false,
columns,
}
}
/// 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.
///
/// Sorry, future me, you're gonna have to refactor this later. Too busy getting
/// the feature to work in the first place! :)
pub fn update_sorting_with_columns(&mut self) {
let mut true_index = 0;
let mut enabled_index = 0;
let target_itx = self.columns.current_scroll_position;
for column in &self.columns.ordered_columns {
let enabled = self.columns.column_mapping.get(column).unwrap().enabled;
if enabled_index == target_itx && enabled {
break;
}
if enabled {
enabled_index += 1;
}
true_index += 1;
}
if let Some(new_sort_type) = self.columns.ordered_columns.get(true_index) {
if *new_sort_type == self.process_sorting_type {
// Just reverse the search if we're reselecting!
self.process_sorting_reverse = !(self.process_sorting_reverse);
} else {
self.process_sorting_type = new_sort_type.clone();
match self.process_sorting_type {
ProcessSorting::State
| ProcessSorting::Pid
| ProcessSorting::ProcessName
| ProcessSorting::Command => {
// Also invert anything that uses alphabetical sorting by default.
self.process_sorting_reverse = false;
}
_ => {}
}
}
}
}
pub fn toggle_command_and_name(&mut self, is_using_command: bool) {
self.columns
.column_mapping
.get_mut(&ProcessSorting::ProcessName)
.unwrap()
.enabled = !is_using_command;
self.columns
.column_mapping
.get_mut(&ProcessSorting::Command)
.unwrap()
.enabled = is_using_command;
}
pub fn get_cursor_position(&self) -> usize {
self.process_search_state
.search_state

View file

@ -356,20 +356,16 @@ impl Painter {
app_state.current_widget.widget_id,
false,
),
Proc => self.draw_process_and_search(
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
ProcSearch => self.draw_process_and_search(
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id - 1,
),
proc_type @ Proc | proc_type @ ProcSearch | proc_type @ ProcSort => {
let widget_id = app_state.current_widget.widget_id
- match proc_type {
ProcSearch => 1,
ProcSort => 2,
_ => 0,
};
self.draw_process_features(&mut f, app_state, rect[0], true, widget_id);
}
Battery => self.draw_battery_display(
&mut f,
app_state,
@ -430,13 +426,20 @@ impl Painter {
false,
widget_id,
),
Proc => self.draw_process_and_search(
&mut f,
app_state,
vertical_chunks[4],
false,
widget_id,
),
Proc | ProcSort => {
let wid = widget_id
- match basic_table_widget_state.currently_displayed_widget_type {
ProcSort => 2,
_ => 0,
};
self.draw_process_features(
&mut f,
app_state,
vertical_chunks[4],
false,
wid,
);
}
Temp => self.draw_temp_table(
&mut f,
app_state,
@ -575,7 +578,7 @@ impl Painter {
Disk => {
self.draw_disk_table(f, app_state, *widget_draw_loc, true, widget.widget_id)
}
Proc => self.draw_process_and_search(
Proc => self.draw_process_features(
f,
app_state,
*widget_draw_loc,

View file

@ -72,7 +72,7 @@ impl HelpDialog for Painter {
app_state.help_dialog_state.scroll_state.max_scroll_index =
(self.styled_help_text.len() as u16
+ (constants::HELP_TEXT.len() as u16 - 3)
+ (constants::HELP_TEXT.len() as u16 - 4)
+ overflow_buffer)
.saturating_sub(draw_loc.height);

View file

@ -85,7 +85,7 @@ pub fn get_search_start_position(
}
match cursor_direction {
app::CursorDirection::RIGHT => {
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
@ -100,7 +100,7 @@ pub fn get_search_start_position(
0
}
}
app::CursorDirection::LEFT => {
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;
@ -125,7 +125,7 @@ pub fn get_start_position(
}
match scroll_direction {
app::ScrollDirection::DOWN => {
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
@ -140,7 +140,7 @@ pub fn get_start_position(
0
}
}
app::ScrollDirection::UP => {
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;

View file

@ -24,6 +24,15 @@ impl BasicTableArrows for Painter {
fn draw_basic_table_arrows<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &App, draw_loc: Rect, current_table: &BottomWidget,
) {
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
};
// Effectively a paragraph with a ton of spacing
let (left_table, right_table) = (
{
@ -33,7 +42,21 @@ impl BasicTableArrows for Painter {
app_state
.widget_map
.get(&left_widget_id)
.map(|left_widget| &left_widget.widget_type)
.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_else(|| Some(&BottomWidgetType::Temp))
.unwrap_or_else(|| &BottomWidgetType::Temp)
} else {
&left_widget.widget_type
}
})
.unwrap_or_else(|| &BottomWidgetType::Temp)
})
.unwrap_or_else(|| &BottomWidgetType::Temp)
@ -45,7 +68,23 @@ impl BasicTableArrows for Painter {
app_state
.widget_map
.get(&right_widget_id)
.map(|right_widget| &right_widget.widget_type)
.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_else(|| Some(&BottomWidgetType::Disk))
.unwrap_or_else(|| &BottomWidgetType::Disk)
} else {
&right_widget.widget_type
}
})
.unwrap_or_else(|| &BottomWidgetType::Disk)
})
.unwrap_or_else(|| &BottomWidgetType::Disk)

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use std::cmp::max;
use crate::{
app::App,
app::{layout_manager::WidgetDirection, App},
canvas::{
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
Painter,
@ -57,9 +57,9 @@ impl CpuGraphWidget for Painter {
// 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_right();
app_state.move_widget_selection(&WidgetDirection::Right);
} else {
app_state.move_widget_selection_left();
app_state.move_widget_selection(&WidgetDirection::Left);
}
}
self.draw_cpu_graph(f, app_state, draw_loc, widget_id);

View file

@ -1,5 +1,5 @@
use crate::{
app::{self, App},
app::App,
canvas::{
drawing_utils::{
get_search_start_position, get_start_position, get_variable_intrinsic_widths,
@ -14,43 +14,68 @@ use tui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
terminal::Frame,
text::{Span, Spans},
widgets::{Block, Borders, Paragraph, Row, Table, Wrap},
widgets::{Block, Borders, Paragraph, Row, Table},
};
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
use unicode_width::UnicodeWidthStr;
pub trait ProcessTableWidget {
fn draw_process_and_search<B: Backend>(
/// Draws and handles all process-related drawing. Use this.
/// - `widget_id` here represents the widget ID of the process widget itself!
fn draw_process_features<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
/// Draws the process sort box.
/// - `widget_id` represents the widget ID of the process widget itself.
///
/// This should not be directly called.
fn draw_processes_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
/// Draws the process sort box.
/// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget
/// state that is stored.
///
/// This should not be directly called.
fn draw_search_field<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
/// Draws the process sort box.
/// - `widget_id` represents the widget ID of the sort box itself --- NOT the process widget
/// state that is stored.
///
/// This should not be directly called.
fn draw_process_sort<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
}
impl ProcessTableWidget for Painter {
fn draw_process_and_search<B: Backend>(
fn draw_process_features<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) {
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)].as_ref())
.split(draw_loc);
proc_draw_loc = processes_chunk[0];
self.draw_processes_table(f, app_state, processes_chunk[0], draw_border, widget_id);
self.draw_search_field(
f,
app_state,
@ -58,9 +83,25 @@ impl ProcessTableWidget for Painter {
draw_border,
widget_id + 1,
);
} else {
self.draw_processes_table(f, app_state, draw_loc, 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)].as_ref())
.split(proc_draw_loc);
proc_draw_loc = processes_chunk[1];
self.draw_process_sort(
f,
app_state,
processes_chunk[0],
draw_border,
widget_id + 2,
);
}
self.draw_processes_table(f, app_state, proc_draw_loc, draw_border, widget_id);
}
}
@ -127,71 +168,17 @@ impl ProcessTableWidget for Painter {
process.write_per_sec.to_string(),
process.total_read.to_string(),
process.total_write.to_string(),
process.process_states.to_string(),
process.process_state.to_string(),
]
.into_iter(),
)
});
use app::data_harvester::processes::ProcessSorting;
let mut pid_or_count = if proc_widget_state.is_grouped {
"Count"
} else {
"PID(p)"
}
.to_string();
let mut identifier = if proc_widget_state.is_using_full_path {
"Command(n)".to_string()
} else {
"Name(n)".to_string()
};
let mut cpu = "CPU%(c)".to_string();
let mut mem = "Mem%(m)".to_string();
let rps = "R/s".to_string();
let wps = "W/s".to_string();
let total_read = "Read".to_string();
let total_write = "Write".to_string();
let process_state = "State ".to_string();
let process_headers = proc_widget_state.columns.get_column_headers(
&proc_widget_state.process_sorting_type,
proc_widget_state.process_sorting_reverse,
);
let direction_val = if proc_widget_state.process_sorting_reverse {
"".to_string()
} else {
"".to_string()
};
match proc_widget_state.process_sorting_type {
ProcessSorting::CPU => cpu += &direction_val,
ProcessSorting::MEM => mem += &direction_val,
ProcessSorting::PID => pid_or_count += &direction_val,
ProcessSorting::IDENTIFIER => identifier += &direction_val,
};
// TODO: Gonna have to figure out how to do left/right GUI notation.
let process_headers = if proc_widget_state.is_grouped {
vec![
pid_or_count,
identifier,
cpu,
mem,
rps,
wps,
total_read,
total_write,
]
} else {
vec![
pid_or_count,
identifier,
cpu,
mem,
rps,
wps,
total_read,
total_write,
process_state,
]
};
proc_widget_state.num_columns = process_headers.len();
let process_headers_lens: Vec<usize> = process_headers
.iter()
.map(|entry| entry.len())
@ -202,12 +189,12 @@ impl ProcessTableWidget for Painter {
// TODO: This is a ugly work-around for now.
let width_ratios = if proc_widget_state.is_grouped {
if proc_widget_state.is_using_full_path {
if proc_widget_state.is_using_command {
vec![0.05, 0.7, 0.05, 0.05, 0.0375, 0.0375, 0.0375, 0.0375]
} else {
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15]
}
} else if proc_widget_state.is_using_full_path {
} else if proc_widget_state.is_using_command {
vec![0.05, 0.7, 0.05, 0.05, 0.03, 0.03, 0.03, 0.03]
} else {
vec![0.1, 0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
@ -235,6 +222,7 @@ impl ProcessTableWidget for Painter {
.process_search_state
.search_state
.is_enabled
&& !proc_widget_state.is_sort_open
{
const TITLE_BASE: &str = " Processes ── Esc to go back ";
Span::styled(
@ -502,10 +490,108 @@ impl ProcessTableWidget for Painter {
Paragraph::new(search_text)
.block(process_search_block)
.style(self.colours.text_style)
.alignment(Alignment::Left)
.wrap(Wrap { trim: false }),
.alignment(Alignment::Left),
margined_draw_loc[0],
);
}
}
fn draw_process_sort<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, 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
})
.enumerate()
.map(|(itx, column_type)| {
if current_scroll_position == itx {
(
column_type.to_string(),
self.colours.currently_selected_text_style,
)
} else {
(column_type.to_string(), self.colours.text_style)
}
})
.collect::<Vec<_>>();
let position = get_start_position(
usize::from(draw_loc.height.saturating_sub(self.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, style)| Row::StyledData(vec![column].into_iter(), *style));
let column_state = &mut proc_widget_state.columns.column_state;
let current_border_style = if proc_widget_state
.process_search_state
.search_state
.is_invalid_search
{
self.colours.invalid_query_style
} else if is_on_widget {
self.colours.highlighted_border_style
} else {
self.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 margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc);
f.render_stateful_widget(
Table::new(["Sort By"].iter(), sort_options)
.block(process_sort_block)
.header_style(self.colours.table_header_style)
.widths(&[Constraint::Percentage(100)])
.header_gap(1),
margined_draw_loc[0],
column_state,
);
}
}
}

View file

@ -42,13 +42,14 @@ lazy_static! {
}
// Help text
pub const HELP_CONTENTS_TEXT: [&str; 6] = [
pub const HELP_CONTENTS_TEXT: [&str; 7] = [
"Press the corresponding numbers to jump to the section, or scroll:",
"1 - General",
"2 - CPU widget",
"3 - Process widget",
"4 - Process search widget",
"5 - Battery widget",
"5 - Process sort widget",
"6 - Battery widget",
];
pub const GENERAL_HELP_TEXT: [&str; 29] = [
@ -88,7 +89,9 @@ pub const CPU_HELP_TEXT: [&str; 2] = [
"Mouse scroll Scrolling over an CPU core/average shows only that entry on the chart",
];
pub const PROCESS_HELP_TEXT: [&str; 9] = [
// TODO [Help]: Search in help?
// TODO [Help]: Move to using tables for easier formatting?
pub const PROCESS_HELP_TEXT: [&str; 11] = [
"3 - Process widget",
"dd Kill the selected process",
"c Sort by CPU usage, press again to reverse sorting order",
@ -98,6 +101,8 @@ pub const PROCESS_HELP_TEXT: [&str; 9] = [
"Tab Group/un-group processes with the same name",
"Ctrl-f, / Open process search widget",
"P Toggle between showing the full path or just the process name",
"s, F6 Open process sort widget",
"I Invert current sort",
];
pub const SEARCH_HELP_TEXT: [&str; 43] = [
@ -146,8 +151,17 @@ pub const SEARCH_HELP_TEXT: [&str; 43] = [
"TiB ex: read > 1 tib",
];
pub const SORT_HELP_TEXT: [&str; 6] = [
"5 - Sort widget",
"Down, 'j' Scroll down in list",
"Up, 'k' Scroll up in list",
"Mouse scroll Scroll through sort widget",
"Esc Close the sort widget",
"Enter Sort by current selected column",
];
pub const BATTERY_HELP_TEXT: [&str; 3] = [
"5 - Battery widget",
"6 - Battery widget",
"Left Go to previous battery",
"Right Go to next battery",
];
@ -159,10 +173,37 @@ lazy_static! {
CPU_HELP_TEXT.to_vec(),
PROCESS_HELP_TEXT.to_vec(),
SEARCH_HELP_TEXT.to_vec(),
SORT_HELP_TEXT.to_vec(),
BATTERY_HELP_TEXT.to_vec(),
];
}
// Default layout
pub const DEFAULT_LAYOUT: &str = r##"
[[row]]
ratio=30
[[row.child]]
type="cpu"
[[row]]
ratio=40
[[row.child]]
ratio=4
type="mem"
[[row.child]]
ratio=3
[[row.child.child]]
type="temp"
[[row.child.child]]
type="disk"
[[row]]
ratio=30
[[row.child]]
type="net"
[[row.child]]
type="proc"
default=true
"##;
// Config and flags
pub const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml";

View file

@ -45,7 +45,7 @@ pub struct ConvertedProcessData {
pub wps_f64: f64,
pub tr_f64: f64,
pub tw_f64: f64,
pub process_states: String,
pub process_state: String,
}
#[derive(Clone, Default, Debug)]
@ -409,7 +409,7 @@ pub fn convert_process_data(
wps_f64: process.write_bytes_per_sec as f64,
tr_f64: process.total_read_bytes as f64,
tw_f64: process.total_write_bytes as f64,
process_states: process.process_state.to_owned(),
process_state: process.process_state.to_owned(),
}
})
.collect::<Vec<_>>(),
@ -469,7 +469,7 @@ pub fn convert_process_data(
wps_f64: p.write_per_sec as f64,
tr_f64: p.total_read as f64,
tw_f64: p.total_write as f64,
process_states: p.process_state,
process_state: p.process_state,
}
})
.collect::<Vec<_>>()

View file

@ -28,7 +28,7 @@ use tui::{backend::CrosstermBackend, Terminal};
use app::{
data_harvester::{self, processes::ProcessSorting},
layout_manager::UsedWidgets,
layout_manager::{UsedWidgets, WidgetDirection},
App,
};
use constants::*;
@ -286,15 +286,10 @@ fn handle_key_event_or_break(
KeyCode::Tab => app.on_tab(),
KeyCode::Backspace => app.on_backspace(),
KeyCode::Delete => app.on_delete(),
KeyCode::F(1) => {
app.toggle_ignore_case();
}
KeyCode::F(2) => {
app.toggle_search_whole_word();
}
KeyCode::F(3) => {
app.toggle_search_regex();
}
KeyCode::F(1) => app.toggle_ignore_case(),
KeyCode::F(2) => app.toggle_search_whole_word(),
KeyCode::F(3) => app.toggle_search_regex(),
KeyCode::F(6) => app.toggle_sort(),
_ => {}
}
} else {
@ -315,10 +310,10 @@ fn handle_key_event_or_break(
match event.code {
KeyCode::Char('f') => app.on_slash(),
KeyCode::Left => app.move_widget_selection_left(),
KeyCode::Right => app.move_widget_selection_right(),
KeyCode::Up => app.move_widget_selection_up(),
KeyCode::Down => app.move_widget_selection_down(),
KeyCode::Left => app.move_widget_selection(&WidgetDirection::Left),
KeyCode::Right => app.move_widget_selection(&WidgetDirection::Right),
KeyCode::Up => app.move_widget_selection(&WidgetDirection::Up),
KeyCode::Down => app.move_widget_selection(&WidgetDirection::Down),
KeyCode::Char('r') => {
if reset_sender.send(ResetEvent::Reset).is_ok() {
app.reset();
@ -338,10 +333,10 @@ fn handle_key_event_or_break(
}
} else if let KeyModifiers::SHIFT = event.modifiers {
match event.code {
KeyCode::Left => app.move_widget_selection_left(),
KeyCode::Right => app.move_widget_selection_right(),
KeyCode::Up => app.move_widget_selection_up(),
KeyCode::Down => app.move_widget_selection_down(),
KeyCode::Left => app.move_widget_selection(&WidgetDirection::Left),
KeyCode::Right => app.move_widget_selection(&WidgetDirection::Right),
KeyCode::Up => app.move_widget_selection(&WidgetDirection::Up),
KeyCode::Down => app.move_widget_selection(&WidgetDirection::Down),
KeyCode::Char(caught_char) => app.on_char_key(caught_char),
_ => {}
}
@ -607,7 +602,7 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
} else {
ProcessGroupingType::Ungrouped
},
if proc_widget_state.is_using_full_path {
if proc_widget_state.is_using_command {
ProcessNamingType::Path
} else {
ProcessNamingType::Name
@ -636,12 +631,12 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
// Quick fix for tab updating the table headers
if let Some(proc_widget_state) = app.proc_state.get_mut_widget_state(widget_id) {
if let data_harvester::processes::ProcessSorting::PID =
if let data_harvester::processes::ProcessSorting::Pid =
proc_widget_state.process_sorting_type
{
if proc_widget_state.is_grouped {
proc_widget_state.process_sorting_type =
data_harvester::processes::ProcessSorting::CPU; // Go back to default, negate PID for group
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
proc_widget_state.process_sorting_reverse = true;
}
}
@ -653,7 +648,7 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
proc_widget_state.scroll_state.current_scroll_position =
resulting_processes.len().saturating_sub(1);
proc_widget_state.scroll_state.previous_scroll_position = 0;
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::DOWN;
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::Down;
}
app.canvas_data
@ -670,7 +665,7 @@ fn sort_process_data(
});
match proc_widget_state.process_sorting_type {
ProcessSorting::CPU => {
ProcessSorting::CpuPercent => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.cpu_usage,
@ -679,7 +674,10 @@ fn sort_process_data(
)
});
}
ProcessSorting::MEM => {
ProcessSorting::Mem => {
// TODO: Do when I do mem values in processes
}
ProcessSorting::MemPercent => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.mem_usage,
@ -688,8 +686,8 @@ fn sort_process_data(
)
});
}
ProcessSorting::IDENTIFIER => {
// Don't repeat if false...
ProcessSorting::ProcessName | ProcessSorting::Command => {
// Don't repeat if false... it sorts by name by default anyways.
if proc_widget_state.process_sorting_reverse {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
@ -700,7 +698,7 @@ fn sort_process_data(
})
}
}
ProcessSorting::PID => {
ProcessSorting::Pid => {
if !proc_widget_state.is_grouped {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
@ -711,6 +709,49 @@ fn sort_process_data(
});
}
}
ProcessSorting::ReadPerSecond => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.rps_f64,
b.rps_f64,
proc_widget_state.process_sorting_reverse,
)
});
}
ProcessSorting::WritePerSecond => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.wps_f64,
b.wps_f64,
proc_widget_state.process_sorting_reverse,
)
});
}
ProcessSorting::TotalRead => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.tr_f64,
b.tr_f64,
proc_widget_state.process_sorting_reverse,
)
});
}
ProcessSorting::TotalWrite => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.tw_f64,
b.tw_f64,
proc_widget_state.process_sorting_reverse,
)
});
}
ProcessSorting::State => to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
&a.process_state.to_lowercase(),
&b.process_state.to_lowercase(),
proc_widget_state.process_sorting_reverse,
)
}),
}
}

View file

@ -180,8 +180,7 @@ pub fn build_app(
battery_state_map
.insert(widget.widget_id, BatteryWidgetState::default());
}
Empty | BasicCpu | BasicMem | BasicNet | BasicTables | ProcSearch
| CpuLegend => {}
_ => {}
}
}
}
@ -262,7 +261,17 @@ pub fn get_widget_layout(
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 if let Some(rows) = &config.row {
} 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>(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;
@ -286,15 +295,14 @@ pub fn get_widget_layout(
// Confirm that we have at least ONE widget - 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(
"invalid layout config: please have at least one widget.".to_string(),
));
}
} else {
default_widget_id = DEFAULT_WIDGET_ID;
BottomLayout::init_default(left_legend, get_use_battery(matches, config))
};
Ok((bottom_layout, default_widget_id))

View file

@ -17,7 +17,7 @@ impl Row {
default_widget_type: &Option<BottomWidgetType>, default_widget_count: &mut u64,
left_legend: bool,
) -> Result<BottomRow> {
// In the future we want to also add percentages.
// 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();
@ -53,7 +53,7 @@ impl Row {
children.push(match widget_type {
BottomWidgetType::Cpu => {
let iter_old_id = *iter_id;
let cpu_id = *iter_id;
*iter_id += 1;
BottomCol::builder()
.col_width_ratio(width_ratio)
@ -66,11 +66,15 @@ impl Row {
.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(iter_old_id)
.widget_id(cpu_id)
.flex_grow(true)
.build(),
])
@ -82,7 +86,7 @@ impl Row {
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(iter_old_id)
.widget_id(cpu_id)
.flex_grow(true)
.build(),
BottomWidget::builder()
@ -90,6 +94,10 @@ impl Row {
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(*iter_id)
.canvas_handle_width(true)
.parent_reflector(Some((
WidgetDirection::Left,
1,
)))
.build(),
])
.build()]
@ -97,23 +105,39 @@ impl Row {
.build()
}
BottomWidgetType::Proc => {
let iter_old_id = *iter_id;
*iter_id += 1;
let proc_id = *iter_id;
let proc_search_id = *iter_id + 1;
*iter_id += 2;
BottomCol::builder()
.total_col_row_ratio(2)
.col_width_ratio(width_ratio)
.children(vec![
BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Proc)
.widget_id(iter_old_id)
.build()])
.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(*iter_id)
.widget_id(proc_search_id)
.parent_reflector(Some((WidgetDirection::Up, 1)))
.build()])
.canvas_handle_height(true)
.build(),
@ -137,7 +161,7 @@ impl Row {
let mut total_col_row_ratio = 0;
let mut contains_proc = false;
let mut col_row_children = Vec::new();
let mut col_row_children: Vec<BottomColRow> = Vec::new();
for widget in child {
let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
@ -165,7 +189,7 @@ impl Row {
match widget_type {
BottomWidgetType::Cpu => {
let iter_old_id = *iter_id;
let cpu_id = *iter_id;
*iter_id += 1;
if left_legend {
col_row_children.push(
@ -178,11 +202,15 @@ impl Row {
.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(iter_old_id)
.widget_id(cpu_id)
.flex_grow(true)
.build(),
])
@ -197,7 +225,7 @@ impl Row {
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(iter_old_id)
.widget_id(cpu_id)
.flex_grow(true)
.build(),
BottomWidget::builder()
@ -205,6 +233,10 @@ impl Row {
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(*iter_id)
.canvas_handle_width(true)
.parent_reflector(Some((
WidgetDirection::Left,
1,
)))
.build(),
])
.build(),
@ -213,15 +245,30 @@ impl Row {
}
BottomWidgetType::Proc => {
contains_proc = true;
let iter_old_id = *iter_id;
*iter_id += 1;
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)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Proc)
.widget_id(iter_old_id)
.build()])
.total_widget_ratio(3)
.build(),
);
col_row_children.push(
@ -229,7 +276,8 @@ impl Row {
.col_row_height_ratio(col_row_height_ratio)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(*iter_id)
.widget_id(proc_search_id)
.parent_reflector(Some((WidgetDirection::Up, 1)))
.build()])
.canvas_handle_height(true)
.build(),

View file

@ -0,0 +1 @@
// TODO: See if we can mock widget movements and test if they work