mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-24 21:23:08 +00:00
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:
parent
84f63f2f83
commit
f3897f0538
23 changed files with 1384 additions and 756 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -31,6 +31,7 @@
|
|||
"shilangyu",
|
||||
"softirq",
|
||||
"stime",
|
||||
"subwidget",
|
||||
"sysinfo",
|
||||
"tokei",
|
||||
"twrite",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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...
|
||||
|
|
13
README.md
13
README.md
|
@ -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
|
||||
|
||||
| | |
|
||||
|
|
943
src/app.rs
943
src/app.rs
File diff suppressed because it is too large
Load diff
|
@ -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.
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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()),
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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<_>>()
|
||||
|
|
95
src/main.rs
95
src/main.rs
|
@ -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,
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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(),
|
||||
|
|
1
tests/widget_movement_tests.rs
Normal file
1
tests/widget_movement_tests.rs
Normal file
|
@ -0,0 +1 @@
|
|||
// TODO: See if we can mock widget movements and test if they work
|
Loading…
Reference in a new issue