mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-09-23 17:58:06 +02:00
commit
22cdc005bc
12
README.md
12
README.md
@ -30,6 +30,8 @@ Features of bottom include:
|
|||||||
|
|
||||||
- Maximizing of widgets of interest to take up the entire window.
|
- Maximizing of widgets of interest to take up the entire window.
|
||||||
|
|
||||||
|
- Basic mode
|
||||||
|
|
||||||
More details about each widget and compatibility can be found [here](./docs/widgets.md).
|
More details about each widget and compatibility can be found [here](./docs/widgets.md).
|
||||||
|
|
||||||
## Config files
|
## Config files
|
||||||
@ -75,7 +77,7 @@ sudo dpkg -i bottom_0.2.2_amd64.deb
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
You can get release versions via [Chocolatey](https://chocolatey.org/packages/bottom/):
|
You can get release versions via [Chocolatey](https://chocolatey.org/packages/bottom/) (note it may take a while to be available due to moderation/review):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
choco install bottom --version=0.2.1
|
choco install bottom --version=0.2.1
|
||||||
@ -86,10 +88,10 @@ choco install bottom --version=0.2.1
|
|||||||
You can get release versions using Homebrew:
|
You can get release versions using Homebrew:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ brew tap clementtsang/bottom
|
brew tap clementtsang/bottom
|
||||||
$ brew install bottom
|
brew install bottom
|
||||||
# Or
|
# Or
|
||||||
$ brew install clementtsang/bottom/bottom
|
brew install clementtsang/bottom/bottom
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -134,6 +136,8 @@ Run using `btm`.
|
|||||||
|
|
||||||
- `-C`, `--config` takes in a file path leading to a TOML file. If doesn't exist, creates one.
|
- `-C`, `--config` takes in a file path leading to a TOML file. If doesn't exist, creates one.
|
||||||
|
|
||||||
|
- `-b`, `--basic` will enable basic mode, removing all graphs from the main interface and condensing data.
|
||||||
|
|
||||||
### Keybindings
|
### Keybindings
|
||||||
|
|
||||||
#### General
|
#### General
|
||||||
|
@ -3,7 +3,6 @@ max_width = 100
|
|||||||
newline_style = "Unix"
|
newline_style = "Unix"
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
fn_args_layout = "Compressed"
|
fn_args_layout = "Compressed"
|
||||||
hard_tabs = true
|
|
||||||
merge_derives = true
|
merge_derives = true
|
||||||
reorder_modules = true
|
reorder_modules = true
|
||||||
tab_spaces = 4
|
tab_spaces = 4
|
||||||
|
@ -34,6 +34,9 @@
|
|||||||
#default_widget = "network_default"
|
#default_widget = "network_default"
|
||||||
#default_widget = "process_default"
|
#default_widget = "process_default"
|
||||||
|
|
||||||
|
# Basic mode
|
||||||
|
#basic = true
|
||||||
|
|
||||||
|
|
||||||
# These are all the components that support custom theming. Currently, it only
|
# These are all the components that support custom theming. Currently, it only
|
||||||
# supports taking in a string representing a hex colour. Note that colour support
|
# supports taking in a string representing a hex colour. Note that colour support
|
||||||
|
124
src/app.rs
124
src/app.rs
@ -23,6 +23,33 @@ pub enum WidgetPosition {
|
|||||||
Network,
|
Network,
|
||||||
Process,
|
Process,
|
||||||
ProcessSearch,
|
ProcessSearch,
|
||||||
|
BasicCpu,
|
||||||
|
BasicMem,
|
||||||
|
BasicNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetPosition {
|
||||||
|
pub fn is_widget_table(self) -> bool {
|
||||||
|
match self {
|
||||||
|
WidgetPosition::Disk
|
||||||
|
| WidgetPosition::Process
|
||||||
|
| WidgetPosition::ProcessSearch
|
||||||
|
| WidgetPosition::Temp => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pretty_name(self) -> String {
|
||||||
|
match self {
|
||||||
|
WidgetPosition::Cpu | WidgetPosition::BasicCpu => "CPU",
|
||||||
|
WidgetPosition::Mem | WidgetPosition::BasicMem => "Memory",
|
||||||
|
WidgetPosition::Disk => "Disks",
|
||||||
|
WidgetPosition::Temp => "Temperature",
|
||||||
|
WidgetPosition::Network | WidgetPosition::BasicNet => "Network",
|
||||||
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => "Processes",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -180,6 +207,7 @@ pub struct AppConfigFields {
|
|||||||
pub show_average_cpu: bool,
|
pub show_average_cpu: bool,
|
||||||
pub use_current_cpu_total: bool,
|
pub use_current_cpu_total: bool,
|
||||||
pub show_disabled_data: bool,
|
pub show_disabled_data: bool,
|
||||||
|
pub use_basic_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Network specific
|
/// Network specific
|
||||||
@ -243,7 +271,7 @@ pub struct App {
|
|||||||
pub update_process_gui: bool,
|
pub update_process_gui: bool,
|
||||||
pub app_scroll_positions: AppScrollState,
|
pub app_scroll_positions: AppScrollState,
|
||||||
pub current_widget_selected: WidgetPosition,
|
pub current_widget_selected: WidgetPosition,
|
||||||
pub data: data_harvester::Data,
|
pub previous_basic_table_selected: WidgetPosition,
|
||||||
awaiting_second_char: bool,
|
awaiting_second_char: bool,
|
||||||
second_char: Option<char>,
|
second_char: Option<char>,
|
||||||
pub dd_err: Option<String>,
|
pub dd_err: Option<String>,
|
||||||
@ -270,15 +298,28 @@ impl App {
|
|||||||
show_average_cpu: bool, temperature_type: temperature::TemperatureType,
|
show_average_cpu: bool, temperature_type: temperature::TemperatureType,
|
||||||
update_rate_in_milliseconds: u64, use_dot: bool, left_legend: bool,
|
update_rate_in_milliseconds: u64, use_dot: bool, left_legend: bool,
|
||||||
use_current_cpu_total: bool, current_widget_selected: WidgetPosition,
|
use_current_cpu_total: bool, current_widget_selected: WidgetPosition,
|
||||||
show_disabled_data: bool,
|
show_disabled_data: bool, use_basic_mode: bool,
|
||||||
) -> App {
|
) -> App {
|
||||||
App {
|
App {
|
||||||
process_sorting_type: processes::ProcessSorting::CPU,
|
process_sorting_type: processes::ProcessSorting::CPU,
|
||||||
process_sorting_reverse: true,
|
process_sorting_reverse: true,
|
||||||
update_process_gui: false,
|
update_process_gui: false,
|
||||||
current_widget_selected,
|
current_widget_selected: if use_basic_mode {
|
||||||
|
match current_widget_selected {
|
||||||
|
WidgetPosition::Cpu => WidgetPosition::BasicCpu,
|
||||||
|
WidgetPosition::Network => WidgetPosition::BasicNet,
|
||||||
|
WidgetPosition::Mem => WidgetPosition::BasicMem,
|
||||||
|
_ => current_widget_selected,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_widget_selected
|
||||||
|
},
|
||||||
|
previous_basic_table_selected: if current_widget_selected.is_widget_table() {
|
||||||
|
current_widget_selected
|
||||||
|
} else {
|
||||||
|
WidgetPosition::Process
|
||||||
|
},
|
||||||
app_scroll_positions: AppScrollState::default(),
|
app_scroll_positions: AppScrollState::default(),
|
||||||
data: data_harvester::Data::default(),
|
|
||||||
awaiting_second_char: false,
|
awaiting_second_char: false,
|
||||||
second_char: None,
|
second_char: None,
|
||||||
dd_err: None,
|
dd_err: None,
|
||||||
@ -299,6 +340,7 @@ impl App {
|
|||||||
left_legend,
|
left_legend,
|
||||||
use_current_cpu_total,
|
use_current_cpu_total,
|
||||||
show_disabled_data,
|
show_disabled_data,
|
||||||
|
use_basic_mode,
|
||||||
},
|
},
|
||||||
is_expanded: false,
|
is_expanded: false,
|
||||||
is_resized: false,
|
is_resized: false,
|
||||||
@ -333,6 +375,12 @@ impl App {
|
|||||||
self.dd_err = None;
|
self.dd_err = None;
|
||||||
} else if self.is_filtering_or_searching() {
|
} else if self.is_filtering_or_searching() {
|
||||||
match self.current_widget_selected {
|
match self.current_widget_selected {
|
||||||
|
WidgetPosition::Cpu
|
||||||
|
if self.is_expanded && self.app_config_fields.use_basic_mode =>
|
||||||
|
{
|
||||||
|
self.current_widget_selected = WidgetPosition::BasicCpu;
|
||||||
|
self.cpu_state.is_showing_tray = false;
|
||||||
|
}
|
||||||
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
||||||
if self.process_search_state.search_state.is_enabled {
|
if self.process_search_state.search_state.is_enabled {
|
||||||
self.current_widget_selected = WidgetPosition::Process;
|
self.current_widget_selected = WidgetPosition::Process;
|
||||||
@ -436,6 +484,10 @@ impl App {
|
|||||||
pub fn on_slash(&mut self) {
|
pub fn on_slash(&mut self) {
|
||||||
if !self.is_in_dialog() {
|
if !self.is_in_dialog() {
|
||||||
match self.current_widget_selected {
|
match self.current_widget_selected {
|
||||||
|
WidgetPosition::BasicCpu if self.is_expanded => {
|
||||||
|
self.current_widget_selected = WidgetPosition::Cpu;
|
||||||
|
self.cpu_state.is_showing_tray = true;
|
||||||
|
}
|
||||||
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
||||||
// Toggle on
|
// Toggle on
|
||||||
self.process_search_state.search_state.is_enabled = true;
|
self.process_search_state.search_state.is_enabled = true;
|
||||||
@ -858,7 +910,8 @@ impl App {
|
|||||||
let current_key_press_inst = Instant::now();
|
let current_key_press_inst = Instant::now();
|
||||||
if current_key_press_inst
|
if current_key_press_inst
|
||||||
.duration_since(self.last_key_press)
|
.duration_since(self.last_key_press)
|
||||||
.as_millis() > constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS
|
.as_millis()
|
||||||
|
> constants::MAX_KEY_TIMEOUT_IN_MILLISECONDS
|
||||||
{
|
{
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
}
|
}
|
||||||
@ -1046,7 +1099,7 @@ impl App {
|
|||||||
self.to_delete_process_list.clone()
|
self.to_delete_process_list.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, these are hard coded --- in the future, they shouldn't be!
|
// TODO: [MODULARITY] Do NOT hard code this in thu future!
|
||||||
//
|
//
|
||||||
// General idea for now:
|
// General idea for now:
|
||||||
// CPU -(down)> MEM
|
// CPU -(down)> MEM
|
||||||
@ -1058,6 +1111,16 @@ impl App {
|
|||||||
// PROC_SEARCH -(up)> PROC, -(left)> Network
|
// PROC_SEARCH -(up)> PROC, -(left)> Network
|
||||||
pub fn move_widget_selection_left(&mut self) {
|
pub fn move_widget_selection_left(&mut self) {
|
||||||
if !self.is_in_dialog() && !self.is_expanded {
|
if !self.is_in_dialog() && !self.is_expanded {
|
||||||
|
if self.app_config_fields.use_basic_mode {
|
||||||
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
|
WidgetPosition::BasicNet => WidgetPosition::BasicMem,
|
||||||
|
WidgetPosition::Process => WidgetPosition::Disk,
|
||||||
|
WidgetPosition::ProcessSearch => WidgetPosition::Disk,
|
||||||
|
WidgetPosition::Disk => WidgetPosition::Temp,
|
||||||
|
WidgetPosition::Temp => WidgetPosition::Process,
|
||||||
|
_ => self.current_widget_selected,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
self.current_widget_selected = match self.current_widget_selected {
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
WidgetPosition::Process => WidgetPosition::Network,
|
WidgetPosition::Process => WidgetPosition::Network,
|
||||||
WidgetPosition::ProcessSearch => WidgetPosition::Network,
|
WidgetPosition::ProcessSearch => WidgetPosition::Network,
|
||||||
@ -1066,24 +1129,50 @@ impl App {
|
|||||||
_ => self.current_widget_selected,
|
_ => self.current_widget_selected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_widget_selection_right(&mut self) {
|
pub fn move_widget_selection_right(&mut self) {
|
||||||
if !self.is_in_dialog() && !self.is_expanded {
|
if !self.is_in_dialog() && !self.is_expanded {
|
||||||
|
if self.app_config_fields.use_basic_mode {
|
||||||
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
|
WidgetPosition::BasicMem => WidgetPosition::BasicNet,
|
||||||
|
WidgetPosition::Process => WidgetPosition::Temp,
|
||||||
|
WidgetPosition::ProcessSearch => WidgetPosition::Temp,
|
||||||
|
WidgetPosition::Disk => WidgetPosition::Process,
|
||||||
|
WidgetPosition::Temp => WidgetPosition::Disk,
|
||||||
|
_ => self.current_widget_selected,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
self.current_widget_selected = match self.current_widget_selected {
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
WidgetPosition::Mem => WidgetPosition::Temp,
|
WidgetPosition::Mem => WidgetPosition::Temp,
|
||||||
WidgetPosition::Network => WidgetPosition::Process,
|
WidgetPosition::Network => WidgetPosition::Process,
|
||||||
_ => self.current_widget_selected,
|
_ => self.current_widget_selected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_widget_selection_up(&mut self) {
|
pub fn move_widget_selection_up(&mut self) {
|
||||||
if !self.is_in_dialog() && !self.is_expanded {
|
if !self.is_in_dialog() && !self.is_expanded {
|
||||||
|
if self.app_config_fields.use_basic_mode {
|
||||||
|
if self.current_widget_selected.is_widget_table() {
|
||||||
|
self.previous_basic_table_selected = self.current_widget_selected;
|
||||||
|
}
|
||||||
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
|
WidgetPosition::BasicMem => WidgetPosition::BasicCpu,
|
||||||
|
WidgetPosition::BasicNet => WidgetPosition::BasicCpu,
|
||||||
|
WidgetPosition::Process => WidgetPosition::BasicMem,
|
||||||
|
WidgetPosition::ProcessSearch => WidgetPosition::Process,
|
||||||
|
WidgetPosition::Temp => WidgetPosition::BasicMem,
|
||||||
|
WidgetPosition::Disk => WidgetPosition::BasicMem,
|
||||||
|
_ => self.current_widget_selected,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
self.current_widget_selected = match self.current_widget_selected {
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
WidgetPosition::Mem => WidgetPosition::Cpu,
|
WidgetPosition::Mem => WidgetPosition::Cpu,
|
||||||
WidgetPosition::Network => WidgetPosition::Mem,
|
WidgetPosition::Network => WidgetPosition::Mem,
|
||||||
@ -1093,6 +1182,7 @@ impl App {
|
|||||||
WidgetPosition::Disk => WidgetPosition::Temp,
|
WidgetPosition::Disk => WidgetPosition::Temp,
|
||||||
_ => self.current_widget_selected,
|
_ => self.current_widget_selected,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
} else if self.is_expanded {
|
} else if self.is_expanded {
|
||||||
self.current_widget_selected = match self.current_widget_selected {
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
WidgetPosition::ProcessSearch => WidgetPosition::Process,
|
WidgetPosition::ProcessSearch => WidgetPosition::Process,
|
||||||
@ -1105,6 +1195,21 @@ impl App {
|
|||||||
|
|
||||||
pub fn move_widget_selection_down(&mut self) {
|
pub fn move_widget_selection_down(&mut self) {
|
||||||
if !self.is_in_dialog() && !self.is_expanded {
|
if !self.is_in_dialog() && !self.is_expanded {
|
||||||
|
if self.app_config_fields.use_basic_mode {
|
||||||
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
|
WidgetPosition::BasicMem => self.previous_basic_table_selected,
|
||||||
|
WidgetPosition::BasicNet => self.previous_basic_table_selected,
|
||||||
|
WidgetPosition::BasicCpu => WidgetPosition::BasicMem,
|
||||||
|
WidgetPosition::Process => {
|
||||||
|
if self.is_searching() {
|
||||||
|
WidgetPosition::ProcessSearch
|
||||||
|
} else {
|
||||||
|
WidgetPosition::Process
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.current_widget_selected,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
self.current_widget_selected = match self.current_widget_selected {
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
WidgetPosition::Cpu => WidgetPosition::Mem,
|
WidgetPosition::Cpu => WidgetPosition::Mem,
|
||||||
WidgetPosition::Mem => WidgetPosition::Network,
|
WidgetPosition::Mem => WidgetPosition::Network,
|
||||||
@ -1119,6 +1224,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
_ => self.current_widget_selected,
|
_ => self.current_widget_selected,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
} else if self.is_expanded {
|
} else if self.is_expanded {
|
||||||
self.current_widget_selected = match self.current_widget_selected {
|
self.current_widget_selected = match self.current_widget_selected {
|
||||||
WidgetPosition::Process => {
|
WidgetPosition::Process => {
|
||||||
@ -1172,12 +1278,14 @@ impl App {
|
|||||||
WidgetPosition::Process => {
|
WidgetPosition::Process => {
|
||||||
self.app_scroll_positions
|
self.app_scroll_positions
|
||||||
.process_scroll_state
|
.process_scroll_state
|
||||||
.current_scroll_position = self.canvas_data.finalized_process_data.len() as u64 - 1
|
.current_scroll_position =
|
||||||
|
self.canvas_data.finalized_process_data.len() as u64 - 1
|
||||||
}
|
}
|
||||||
WidgetPosition::Temp => {
|
WidgetPosition::Temp => {
|
||||||
self.app_scroll_positions
|
self.app_scroll_positions
|
||||||
.temp_scroll_state
|
.temp_scroll_state
|
||||||
.current_scroll_position = self.canvas_data.temp_sensor_data.len() as u64 - 1
|
.current_scroll_position =
|
||||||
|
self.canvas_data.temp_sensor_data.len() as u64 - 1
|
||||||
}
|
}
|
||||||
WidgetPosition::Disk => {
|
WidgetPosition::Disk => {
|
||||||
self.app_scroll_positions
|
self.app_scroll_positions
|
||||||
|
663
src/canvas.rs
663
src/canvas.rs
@ -1,8 +1,8 @@
|
|||||||
use std::cmp::max;
|
use std::cmp::{max, min};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
terminal::Frame,
|
terminal::Frame,
|
||||||
@ -35,6 +35,7 @@ const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
|
|||||||
const FORCE_MIN_THRESHOLD: usize = 5;
|
const FORCE_MIN_THRESHOLD: usize = 5;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
static ref SIDE_BORDERS: Borders = Borders::from_bits_truncate(20);
|
||||||
static ref DEFAULT_TEXT_STYLE: Style = Style::default().fg(Color::Gray);
|
static ref DEFAULT_TEXT_STYLE: Style = Style::default().fg(Color::Gray);
|
||||||
static ref DEFAULT_HEADER_STYLE: Style = Style::default().fg(Color::LightBlue);
|
static ref DEFAULT_HEADER_STYLE: Style = Style::default().fg(Color::LightBlue);
|
||||||
static ref DISK_HEADERS_LENS: Vec<usize> = DISK_HEADERS
|
static ref DISK_HEADERS_LENS: Vec<usize> = DISK_HEADERS
|
||||||
@ -73,9 +74,12 @@ pub struct DisplayableData {
|
|||||||
pub network_data_tx: Vec<(f64, f64)>,
|
pub network_data_tx: Vec<(f64, f64)>,
|
||||||
pub disk_data: Vec<Vec<String>>,
|
pub disk_data: Vec<Vec<String>>,
|
||||||
pub temp_sensor_data: Vec<Vec<String>>,
|
pub temp_sensor_data: Vec<Vec<String>>,
|
||||||
pub process_data: HashMap<u32, ProcessHarvest>, // Not the final value
|
pub process_data: HashMap<u32, ProcessHarvest>,
|
||||||
pub grouped_process_data: Vec<ConvertedProcessData>, // Not the final value
|
// Not the final value
|
||||||
pub finalized_process_data: Vec<ConvertedProcessData>, // What's actually displayed
|
pub grouped_process_data: Vec<ConvertedProcessData>,
|
||||||
|
// Not the final value
|
||||||
|
pub finalized_process_data: Vec<ConvertedProcessData>,
|
||||||
|
// What's actually displayed
|
||||||
pub mem_label: String,
|
pub mem_label: String,
|
||||||
pub swap_label: String,
|
pub swap_label: String,
|
||||||
pub mem_data: Vec<(f64, f64)>,
|
pub mem_data: Vec<(f64, f64)>,
|
||||||
@ -147,16 +151,33 @@ impl Painter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_specific_table<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
|
widget_selected: WidgetPosition,
|
||||||
|
) {
|
||||||
|
match widget_selected {
|
||||||
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
||||||
|
self.draw_process_and_search(f, app_state, draw_loc, draw_border)
|
||||||
|
}
|
||||||
|
WidgetPosition::Temp => self.draw_temp_table(f, app_state, draw_loc, draw_border),
|
||||||
|
WidgetPosition::Disk => self.draw_disk_table(f, app_state, draw_loc, draw_border),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: [REFACTOR] We should clean this up tbh
|
// TODO: [REFACTOR] We should clean this up tbh
|
||||||
// TODO: [FEATURE] Auto-resizing dialog sizes.
|
// TODO: [FEATURE] Auto-resizing dialog sizes.
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
pub fn draw_data<B: backend::Backend>(
|
pub fn draw_data<B: Backend>(
|
||||||
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
|
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()> {
|
||||||
let terminal_size = terminal.size()?;
|
let terminal_size = terminal.size()?;
|
||||||
let current_height = terminal_size.height;
|
let current_height = terminal_size.height;
|
||||||
let current_width = terminal_size.width;
|
let current_width = terminal_size.width;
|
||||||
|
|
||||||
|
// TODO: [OPT] we might be able to add an argument s.t. if there is
|
||||||
|
// no resize AND it's not a data update (or process refresh/search/etc.)
|
||||||
|
// then just... don't draw again!
|
||||||
if self.height == 0 && self.width == 0 {
|
if self.height == 0 && self.width == 0 {
|
||||||
self.height = current_height;
|
self.height = current_height;
|
||||||
self.width = current_width;
|
self.width = current_width;
|
||||||
@ -354,7 +375,7 @@ impl Painter {
|
|||||||
.constraints([Constraint::Percentage(100)].as_ref())
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
match &app_state.current_widget_selected {
|
match &app_state.current_widget_selected {
|
||||||
WidgetPosition::Cpu => {
|
WidgetPosition::Cpu | WidgetPosition::BasicCpu => {
|
||||||
let cpu_chunk = Layout::default()
|
let cpu_chunk = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
@ -382,35 +403,60 @@ impl Painter {
|
|||||||
self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]);
|
self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]);
|
||||||
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
|
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
|
||||||
}
|
}
|
||||||
WidgetPosition::Mem => {
|
WidgetPosition::Mem | WidgetPosition::BasicMem => {
|
||||||
self.draw_memory_graph(&mut f, &app_state, rect[0]);
|
self.draw_memory_graph(&mut f, &app_state, rect[0]);
|
||||||
}
|
}
|
||||||
WidgetPosition::Disk => {
|
WidgetPosition::Disk => {
|
||||||
self.draw_disk_table(&mut f, app_state, rect[0]);
|
self.draw_disk_table(&mut f, app_state, rect[0], true);
|
||||||
}
|
}
|
||||||
WidgetPosition::Temp => {
|
WidgetPosition::Temp => {
|
||||||
self.draw_temp_table(&mut f, app_state, rect[0]);
|
self.draw_temp_table(&mut f, app_state, rect[0], true);
|
||||||
}
|
}
|
||||||
WidgetPosition::Network => {
|
WidgetPosition::Network | WidgetPosition::BasicNet => {
|
||||||
self.draw_network_graph(&mut f, &app_state, rect[0]);
|
self.draw_network_graph(&mut f, &app_state, rect[0]);
|
||||||
}
|
}
|
||||||
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
||||||
if app_state.is_searching() {
|
self.draw_process_and_search(&mut f, app_state, rect[0], true);
|
||||||
let processes_chunk = Layout::default()
|
}
|
||||||
.direction(Direction::Vertical)
|
}
|
||||||
.margin(0)
|
} else if app_state.app_config_fields.use_basic_mode {
|
||||||
.constraints(
|
// Basic mode. This basically removes all graphs but otherwise
|
||||||
[Constraint::Percentage(85), Constraint::Percentage(15)]
|
// the same info.
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(rect[0]);
|
|
||||||
|
|
||||||
self.draw_processes_table(&mut f, app_state, processes_chunk[0]);
|
let cpu_height = (app_state.canvas_data.cpu_data.len() / 4) as u16
|
||||||
self.draw_search_field(&mut f, app_state, processes_chunk[1]);
|
+ (
|
||||||
|
if app_state.canvas_data.cpu_data.len() % 4 == 0 {
|
||||||
|
0
|
||||||
} else {
|
} else {
|
||||||
self.draw_processes_table(&mut f, app_state, rect[0]);
|
1
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
let vertical_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(cpu_height),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Length(2),
|
||||||
|
Constraint::Min(5),
|
||||||
|
].as_ref())
|
||||||
|
.split(f.size());
|
||||||
|
|
||||||
|
let middle_chunks= Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
].as_ref())
|
||||||
|
.split(vertical_chunks[2]);
|
||||||
|
self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0]);
|
||||||
|
self.draw_basic_memory(&mut f, app_state, middle_chunks[0]);
|
||||||
|
self.draw_basic_network(&mut f, app_state, middle_chunks[1]);
|
||||||
|
self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3]);
|
||||||
|
if app_state.current_widget_selected.is_widget_table() {
|
||||||
|
self.draw_specific_table(&mut f, app_state, vertical_chunks[4], false, app_state.current_widget_selected);
|
||||||
|
} else {
|
||||||
|
self.draw_specific_table(&mut f, app_state, vertical_chunks[4], false, app_state.previous_basic_table_selected);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: [TUI] Change this back to a more even 33/33/34 when TUI releases
|
// TODO: [TUI] Change this back to a more even 33/33/34 when TUI releases
|
||||||
@ -490,50 +536,14 @@ impl Painter {
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up blocks and their components
|
|
||||||
// CPU graph + legend
|
|
||||||
self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]);
|
self.draw_cpu_graph(&mut f, &app_state, cpu_chunk[graph_index]);
|
||||||
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
|
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
|
||||||
|
|
||||||
//Memory usage graph
|
|
||||||
self.draw_memory_graph(&mut f, &app_state, middle_chunks[0]);
|
self.draw_memory_graph(&mut f, &app_state, middle_chunks[0]);
|
||||||
|
|
||||||
// Network graph
|
|
||||||
self.draw_network_graph(&mut f, &app_state, network_chunk[0]);
|
self.draw_network_graph(&mut f, &app_state, network_chunk[0]);
|
||||||
self.draw_network_labels(&mut f, app_state, network_chunk[1]);
|
self.draw_network_labels(&mut f, app_state, network_chunk[1]);
|
||||||
|
self.draw_temp_table(&mut f, app_state, middle_divided_chunk_2[0], true);
|
||||||
// Temperature table
|
self.draw_disk_table(&mut f, app_state, middle_divided_chunk_2[1], true);
|
||||||
self.draw_temp_table(&mut f, app_state, middle_divided_chunk_2[0]);
|
self.draw_process_and_search(&mut f, app_state, bottom_chunks[1], true);
|
||||||
|
|
||||||
// Disk usage table
|
|
||||||
self.draw_disk_table(&mut f, app_state, middle_divided_chunk_2[1]);
|
|
||||||
|
|
||||||
// Processes table
|
|
||||||
if app_state.is_searching() {
|
|
||||||
let processes_chunk = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.margin(0)
|
|
||||||
.constraints(
|
|
||||||
if (bottom_chunks[1].height as f64 * 0.25) as u16 >= 4 {
|
|
||||||
[Constraint::Percentage(75), Constraint::Percentage(25)]
|
|
||||||
} else {
|
|
||||||
let required = if bottom_chunks[1].height < 10 {
|
|
||||||
bottom_chunks[1].height / 2
|
|
||||||
} else {
|
|
||||||
5
|
|
||||||
};
|
|
||||||
let remaining = bottom_chunks[1].height - required;
|
|
||||||
[Constraint::Length(remaining), Constraint::Length(required)]
|
|
||||||
}
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(bottom_chunks[1]);
|
|
||||||
|
|
||||||
self.draw_processes_table(&mut f, app_state, processes_chunk[0]);
|
|
||||||
self.draw_search_field(&mut f, app_state, processes_chunk[1]);
|
|
||||||
} else {
|
|
||||||
self.draw_processes_table(&mut f, app_state, bottom_chunks[1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -542,7 +552,25 @@ impl Painter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_cpu_graph<B: backend::Backend>(
|
fn draw_process_and_search<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
|
) {
|
||||||
|
let search_width = if draw_border { 5 } else { 3 };
|
||||||
|
|
||||||
|
if app_state.is_searching() {
|
||||||
|
let processes_chunk = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(0), Constraint::Length(search_width)].as_ref())
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
self.draw_processes_table(f, app_state, processes_chunk[0], draw_border);
|
||||||
|
self.draw_search_field(f, app_state, processes_chunk[1], draw_border);
|
||||||
|
} else {
|
||||||
|
self.draw_processes_table(f, app_state, draw_loc, draw_border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_cpu_graph<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
||||||
) {
|
) {
|
||||||
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
|
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
|
||||||
@ -609,7 +637,9 @@ impl Painter {
|
|||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Cpu => self.colours.highlighted_border_style,
|
WidgetPosition::Cpu | WidgetPosition::BasicCpu => {
|
||||||
|
self.colours.highlighted_border_style
|
||||||
|
}
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -619,7 +649,7 @@ impl Painter {
|
|||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_cpu_legend<B: backend::Backend>(
|
fn draw_cpu_legend<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||||
) {
|
) {
|
||||||
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
|
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
|
||||||
@ -671,12 +701,13 @@ impl Painter {
|
|||||||
Row::StyledData(
|
Row::StyledData(
|
||||||
cpu_string_row.iter(),
|
cpu_string_row.iter(),
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Cpu => {
|
WidgetPosition::Cpu => {
|
||||||
if itx as u64
|
if itx as u64
|
||||||
== app_state
|
== app_state
|
||||||
.app_scroll_positions
|
.app_scroll_positions
|
||||||
.cpu_scroll_state
|
.cpu_scroll_state
|
||||||
.current_scroll_position - start_position
|
.current_scroll_position
|
||||||
|
- start_position
|
||||||
{
|
{
|
||||||
self.colours.currently_selected_text_style
|
self.colours.currently_selected_text_style
|
||||||
} else if app_state.app_config_fields.show_average_cpu && itx == 0 {
|
} else if app_state.app_config_fields.show_average_cpu && itx == 0 {
|
||||||
@ -745,13 +776,13 @@ impl Painter {
|
|||||||
self.colours.highlighted_border_style
|
self.colours.highlighted_border_style
|
||||||
} else {
|
} else {
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Cpu => self.colours.highlighted_border_style,
|
WidgetPosition::Cpu => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Cpu => self.colours.highlighted_border_style,
|
WidgetPosition::Cpu => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -765,7 +796,7 @@ impl Painter {
|
|||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_memory_graph<B: backend::Backend>(
|
fn draw_memory_graph<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
||||||
) {
|
) {
|
||||||
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
|
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
|
||||||
@ -828,7 +859,9 @@ impl Painter {
|
|||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Mem => self.colours.highlighted_border_style,
|
WidgetPosition::Mem | WidgetPosition::BasicMem => {
|
||||||
|
self.colours.highlighted_border_style
|
||||||
|
}
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -838,7 +871,7 @@ impl Painter {
|
|||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_network_graph<B: backend::Backend>(
|
fn draw_network_graph<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
||||||
) {
|
) {
|
||||||
let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
|
let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
|
||||||
@ -878,7 +911,9 @@ impl Painter {
|
|||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Network => self.colours.highlighted_border_style,
|
WidgetPosition::Network | WidgetPosition::BasicNet => {
|
||||||
|
self.colours.highlighted_border_style
|
||||||
|
}
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -908,18 +943,18 @@ impl Painter {
|
|||||||
"Total RX: {:7}",
|
"Total RX: {:7}",
|
||||||
app_state.canvas_data.total_rx_display
|
app_state.canvas_data.total_rx_display
|
||||||
))
|
))
|
||||||
.style(self.colours.rx_total_style),
|
.style(self.colours.total_rx_style),
|
||||||
Dataset::default()
|
Dataset::default()
|
||||||
.name(&format!(
|
.name(&format!(
|
||||||
"Total TX: {:7}",
|
"Total TX: {:7}",
|
||||||
app_state.canvas_data.total_tx_display
|
app_state.canvas_data.total_tx_display
|
||||||
))
|
))
|
||||||
.style(self.colours.tx_total_style),
|
.style(self.colours.total_tx_style),
|
||||||
])
|
])
|
||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_network_labels<B: backend::Backend>(
|
fn draw_network_labels<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||||
) {
|
) {
|
||||||
let rx_display = &app_state.canvas_data.rx_display;
|
let rx_display = &app_state.canvas_data.rx_display;
|
||||||
@ -951,7 +986,7 @@ impl Painter {
|
|||||||
Table::new(NETWORK_HEADERS.iter(), mapped_network)
|
Table::new(NETWORK_HEADERS.iter(), mapped_network)
|
||||||
.block(Block::default().borders(Borders::ALL).border_style(
|
.block(Block::default().borders(Borders::ALL).border_style(
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Network => self.colours.highlighted_border_style,
|
WidgetPosition::Network => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@ -966,8 +1001,8 @@ impl Painter {
|
|||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_temp_table<B: backend::Backend>(
|
fn draw_temp_table<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
) {
|
) {
|
||||||
let temp_sensor_data: &[Vec<String>] = &app_state.canvas_data.temp_sensor_data;
|
let temp_sensor_data: &[Vec<String>] = &app_state.canvas_data.temp_sensor_data;
|
||||||
|
|
||||||
@ -993,12 +1028,13 @@ impl Painter {
|
|||||||
Row::StyledData(
|
Row::StyledData(
|
||||||
temp_row.iter(),
|
temp_row.iter(),
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Temp => {
|
WidgetPosition::Temp => {
|
||||||
if temp_row_counter as u64
|
if temp_row_counter as u64
|
||||||
== app_state
|
== app_state
|
||||||
.app_scroll_positions
|
.app_scroll_positions
|
||||||
.temp_scroll_state
|
.temp_scroll_state
|
||||||
.current_scroll_position - start_position
|
.current_scroll_position
|
||||||
|
- start_position
|
||||||
{
|
{
|
||||||
temp_row_counter = -1;
|
temp_row_counter = -1;
|
||||||
self.colours.currently_selected_text_style
|
self.colours.currently_selected_text_style
|
||||||
@ -1033,26 +1069,50 @@ impl Painter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
result_title
|
result_title
|
||||||
|
} else if app_state.app_config_fields.use_basic_mode {
|
||||||
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
" Temperatures ".to_string()
|
" Temperatures ".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw
|
let temp_block = if draw_border {
|
||||||
Table::new(TEMP_HEADERS.iter(), temperature_rows)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
Block::default()
|
||||||
.title(&title)
|
.title(&title)
|
||||||
.title_style(if app_state.is_expanded {
|
.title_style(if app_state.is_expanded {
|
||||||
self.colours.highlighted_border_style
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Temp => self.colours.highlighted_border_style,
|
||||||
|
_ => self.colours.border_style,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.colours.widget_title_style
|
self.colours.widget_title_style
|
||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Temp => self.colours.highlighted_border_style,
|
WidgetPosition::Temp => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
})
|
||||||
)
|
} else {
|
||||||
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Temp => Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
|
.border_style(self.colours.highlighted_border_style),
|
||||||
|
_ => Block::default().borders(Borders::NONE),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let margined_draw_loc = Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Temp => 0,
|
||||||
|
_ if !draw_border => 1,
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
Table::new(TEMP_HEADERS.iter(), temperature_rows)
|
||||||
|
.block(temp_block)
|
||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
@ -1060,11 +1120,11 @@ impl Painter {
|
|||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
.render(f, draw_loc);
|
.render(f, margined_draw_loc[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_disk_table<B: backend::Backend>(
|
fn draw_disk_table<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
) {
|
) {
|
||||||
let disk_data: &[Vec<String>] = &app_state.canvas_data.disk_data;
|
let disk_data: &[Vec<String>] = &app_state.canvas_data.disk_data;
|
||||||
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
|
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
|
||||||
@ -1089,12 +1149,13 @@ impl Painter {
|
|||||||
Row::StyledData(
|
Row::StyledData(
|
||||||
disk.iter(),
|
disk.iter(),
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Disk => {
|
WidgetPosition::Disk => {
|
||||||
if disk_counter as u64
|
if disk_counter as u64
|
||||||
== app_state
|
== app_state
|
||||||
.app_scroll_positions
|
.app_scroll_positions
|
||||||
.disk_scroll_state
|
.disk_scroll_state
|
||||||
.current_scroll_position - start_position
|
.current_scroll_position
|
||||||
|
- start_position
|
||||||
{
|
{
|
||||||
disk_counter = -1;
|
disk_counter = -1;
|
||||||
self.colours.currently_selected_text_style
|
self.colours.currently_selected_text_style
|
||||||
@ -1128,28 +1189,51 @@ impl Painter {
|
|||||||
" Disk ─{}─ Esc to go back ",
|
" Disk ─{}─ Esc to go back ",
|
||||||
"─".repeat(repeat_num as usize)
|
"─".repeat(repeat_num as usize)
|
||||||
);
|
);
|
||||||
|
|
||||||
result_title
|
result_title
|
||||||
|
} else if app_state.app_config_fields.use_basic_mode {
|
||||||
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
" Disk ".to_string()
|
" Disk ".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw!
|
let disk_block = if draw_border {
|
||||||
Table::new(DISK_HEADERS.iter(), disk_rows)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
Block::default()
|
||||||
.title(&title)
|
.title(&title)
|
||||||
.title_style(if app_state.is_expanded {
|
.title_style(if app_state.is_expanded {
|
||||||
self.colours.highlighted_border_style
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Disk => self.colours.highlighted_border_style,
|
||||||
|
_ => self.colours.border_style,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.colours.widget_title_style
|
self.colours.widget_title_style
|
||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Disk => self.colours.highlighted_border_style,
|
WidgetPosition::Disk => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
})
|
||||||
)
|
} else {
|
||||||
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Disk => Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
|
.border_style(self.colours.highlighted_border_style),
|
||||||
|
_ => Block::default().borders(Borders::NONE),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let margined_draw_loc = Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Disk => 0,
|
||||||
|
_ if !draw_border => 1,
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
// Draw!
|
||||||
|
Table::new(DISK_HEADERS.iter(), disk_rows)
|
||||||
|
.block(disk_block)
|
||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
@ -1157,11 +1241,11 @@ impl Painter {
|
|||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
.render(f, draw_loc);
|
.render(f, margined_draw_loc[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_search_field<B: backend::Backend>(
|
fn draw_search_field<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
) {
|
) {
|
||||||
let width = max(0, draw_loc.width as i64 - 34) as u64; // TODO: [REFACTOR] Hard coding this is terrible.
|
let width = max(0, draw_loc.width as i64 - 34) as u64; // TODO: [REFACTOR] Hard coding this is terrible.
|
||||||
let cursor_position = app_state.get_cursor_position();
|
let cursor_position = app_state.get_cursor_position();
|
||||||
@ -1179,7 +1263,7 @@ impl Painter {
|
|||||||
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
|
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
|
||||||
let mut current_grapheme_posn = 0;
|
let mut current_grapheme_posn = 0;
|
||||||
let query_with_cursor: Vec<Text<'_>> =
|
let query_with_cursor: Vec<Text<'_>> =
|
||||||
if let app::WidgetPosition::ProcessSearch = app_state.current_widget_selected {
|
if let WidgetPosition::ProcessSearch = app_state.current_widget_selected {
|
||||||
let mut res = grapheme_indices
|
let mut res = grapheme_indices
|
||||||
.filter_map(|grapheme| {
|
.filter_map(|grapheme| {
|
||||||
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
|
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
|
||||||
@ -1237,10 +1321,6 @@ impl Painter {
|
|||||||
|
|
||||||
// Text options shamelessly stolen from VS Code.
|
// Text options shamelessly stolen from VS Code.
|
||||||
let mut option_text = vec![];
|
let mut option_text = vec![];
|
||||||
for _ in 0..(draw_loc.height - 3) {
|
|
||||||
option_text.push(Text::raw("\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let case_style = if !app_state.process_search_state.is_ignoring_case {
|
let case_style = if !app_state.process_search_state.is_ignoring_case {
|
||||||
self.colours.currently_selected_text_style
|
self.colours.currently_selected_text_style
|
||||||
} else {
|
} else {
|
||||||
@ -1290,6 +1370,7 @@ impl Painter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let option_row = vec![
|
let option_row = vec![
|
||||||
|
Text::raw("\n\n"),
|
||||||
Text::styled(&case_text, case_style),
|
Text::styled(&case_text, case_style),
|
||||||
Text::raw(" "),
|
Text::raw(" "),
|
||||||
Text::styled(&whole_text, whole_word_style),
|
Text::styled(&whole_text, whole_word_style),
|
||||||
@ -1301,13 +1382,6 @@ impl Painter {
|
|||||||
search_text.extend(query_with_cursor);
|
search_text.extend(query_with_cursor);
|
||||||
search_text.extend(option_text);
|
search_text.extend(option_text);
|
||||||
|
|
||||||
const TITLE_BASE: &str = " Esc to close ";
|
|
||||||
let repeat_num = max(
|
|
||||||
0,
|
|
||||||
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
|
|
||||||
);
|
|
||||||
let title = format!("{} Esc to close ", "─".repeat(repeat_num as usize));
|
|
||||||
|
|
||||||
let current_border_style: Style = if app_state
|
let current_border_style: Style = if app_state
|
||||||
.process_search_state
|
.process_search_state
|
||||||
.search_state
|
.search_state
|
||||||
@ -1316,27 +1390,58 @@ impl Painter {
|
|||||||
Style::default().fg(Color::Rgb(255, 0, 0))
|
Style::default().fg(Color::Rgb(255, 0, 0))
|
||||||
} else {
|
} else {
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::ProcessSearch => self.colours.highlighted_border_style,
|
WidgetPosition::ProcessSearch => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Paragraph::new(search_text.iter())
|
let title = if draw_border {
|
||||||
.block(
|
const TITLE_BASE: &str = " Esc to close ";
|
||||||
|
|
||||||
|
let repeat_num = max(
|
||||||
|
0,
|
||||||
|
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
|
||||||
|
);
|
||||||
|
format!("{} Esc to close ", "─".repeat(repeat_num as usize))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let process_search_block = if draw_border {
|
||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
|
||||||
.title(&title)
|
.title(&title)
|
||||||
.title_style(current_border_style)
|
.title_style(current_border_style)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(current_border_style)
|
||||||
|
} else {
|
||||||
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::ProcessSearch => Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
.border_style(current_border_style),
|
.border_style(current_border_style),
|
||||||
)
|
_ => Block::default().borders(Borders::NONE),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let margined_draw_loc = Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::ProcessSearch => 0,
|
||||||
|
_ if !draw_border => 1,
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
Paragraph::new(search_text.iter())
|
||||||
|
.block(process_search_block)
|
||||||
.style(self.colours.text_style)
|
.style(self.colours.text_style)
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
.wrap(false)
|
.wrap(false)
|
||||||
.render(f, draw_loc);
|
.render(f, margined_draw_loc[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_processes_table<B: backend::Backend>(
|
fn draw_processes_table<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
|
||||||
) {
|
) {
|
||||||
let process_data: &[ConvertedProcessData] = &app_state.canvas_data.finalized_process_data;
|
let process_data: &[ConvertedProcessData] = &app_state.canvas_data.finalized_process_data;
|
||||||
|
|
||||||
@ -1389,12 +1494,13 @@ impl Painter {
|
|||||||
Row::StyledData(
|
Row::StyledData(
|
||||||
stringified_process_vec.into_iter(),
|
stringified_process_vec.into_iter(),
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Process => {
|
WidgetPosition::Process => {
|
||||||
if process_counter as u64
|
if process_counter as u64
|
||||||
== app_state
|
== app_state
|
||||||
.app_scroll_positions
|
.app_scroll_positions
|
||||||
.process_scroll_state
|
.process_scroll_state
|
||||||
.current_scroll_position - start_position
|
.current_scroll_position
|
||||||
|
- start_position
|
||||||
{
|
{
|
||||||
process_counter = -1;
|
process_counter = -1;
|
||||||
self.colours.currently_selected_text_style
|
self.colours.currently_selected_text_style
|
||||||
@ -1447,7 +1553,7 @@ impl Painter {
|
|||||||
get_variable_intrinsic_widths(width as u16, &width_ratios, &process_headers_lens);
|
get_variable_intrinsic_widths(width as u16, &width_ratios, &process_headers_lens);
|
||||||
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
|
||||||
|
|
||||||
let title =
|
let title = if draw_border {
|
||||||
if app_state.is_expanded && !app_state.process_search_state.search_state.is_enabled {
|
if app_state.is_expanded && !app_state.process_search_state.search_state.is_enabled {
|
||||||
const TITLE_BASE: &str = " Processes ── Esc to go back ";
|
const TITLE_BASE: &str = " Processes ── Esc to go back ";
|
||||||
let repeat_num = max(
|
let repeat_num = max(
|
||||||
@ -1462,15 +1568,17 @@ impl Painter {
|
|||||||
result_title
|
result_title
|
||||||
} else {
|
} else {
|
||||||
" Processes ".to_string()
|
" Processes ".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Table::new(process_headers.iter(), process_rows)
|
let process_block = if draw_border {
|
||||||
.block(
|
|
||||||
Block::default()
|
Block::default()
|
||||||
.title(&title)
|
.title(&title)
|
||||||
.title_style(if app_state.is_expanded {
|
.title_style(if app_state.is_expanded {
|
||||||
match app_state.current_widget_selected {
|
match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Process => self.colours.highlighted_border_style,
|
WidgetPosition::Process => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1478,10 +1586,30 @@ impl Painter {
|
|||||||
})
|
})
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(match app_state.current_widget_selected {
|
.border_style(match app_state.current_widget_selected {
|
||||||
app::WidgetPosition::Process => self.colours.highlighted_border_style,
|
WidgetPosition::Process => self.colours.highlighted_border_style,
|
||||||
_ => self.colours.border_style,
|
_ => self.colours.border_style,
|
||||||
}),
|
})
|
||||||
)
|
} else {
|
||||||
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Process => Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
|
.border_style(self.colours.highlighted_border_style),
|
||||||
|
_ => Block::default().borders(Borders::NONE),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let margined_draw_loc = Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Process => 0,
|
||||||
|
_ if !draw_border => 1,
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
Table::new(process_headers.iter(), process_rows)
|
||||||
|
.block(process_block)
|
||||||
.header_style(self.colours.table_header_style)
|
.header_style(self.colours.table_header_style)
|
||||||
.widths(
|
.widths(
|
||||||
&(intrinsic_widths
|
&(intrinsic_widths
|
||||||
@ -1489,6 +1617,273 @@ impl Painter {
|
|||||||
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
|
||||||
.collect::<Vec<_>>()),
|
.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
|
.render(f, margined_draw_loc[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_basic_cpu<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||||
|
) {
|
||||||
|
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
|
||||||
|
|
||||||
|
// This is a bit complicated, but basically, we want to draw SOME number
|
||||||
|
// of columns to draw all CPUs. Ideally, as well, we want to not have
|
||||||
|
// to ever scroll.
|
||||||
|
// **General logic** - count number of elements in cpu_data. Then see how
|
||||||
|
// many rows and columns we have in draw_loc (-2 on both sides for border?).
|
||||||
|
// I think what we can do is try to fit in as many in one column as possible.
|
||||||
|
// If not, then add a new column.
|
||||||
|
// Then, from this, split the row space across ALL columns. From there, generate
|
||||||
|
// the desired lengths.
|
||||||
|
|
||||||
|
if let WidgetPosition::BasicCpu = app_state.current_widget_selected {
|
||||||
|
Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
|
.border_style(self.colours.highlighted_border_style)
|
||||||
.render(f, draw_loc);
|
.render(f, draw_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let num_cpus = cpu_data.len();
|
||||||
|
if draw_loc.height > 0 {
|
||||||
|
let remaining_height = draw_loc.height as usize;
|
||||||
|
const REQUIRED_COLUMNS: usize = 4;
|
||||||
|
|
||||||
|
let chunk_vec =
|
||||||
|
vec![Constraint::Percentage((100 / REQUIRED_COLUMNS) as u16); REQUIRED_COLUMNS];
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.constraints(chunk_vec.as_ref())
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
// +9 due to 3 + 4 + 2 columns for the name & space + percentage + bar bounds
|
||||||
|
let margin_space = 2;
|
||||||
|
let remaining_width = max(
|
||||||
|
0,
|
||||||
|
draw_loc.width as i64
|
||||||
|
- ((9 + margin_space) * REQUIRED_COLUMNS - margin_space) as i64,
|
||||||
|
) as usize;
|
||||||
|
|
||||||
|
let bar_length = remaining_width / REQUIRED_COLUMNS;
|
||||||
|
|
||||||
|
// CPU (and RAM) percent bars are, uh, "heavily" inspired from htop.
|
||||||
|
let cpu_bars = (0..num_cpus)
|
||||||
|
.map(|cpu_index| {
|
||||||
|
let use_percentage =
|
||||||
|
if let Some(cpu_usage) = cpu_data[cpu_index].cpu_data.last() {
|
||||||
|
cpu_usage.1
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let num_bars = calculate_basic_use_bars(use_percentage, bar_length);
|
||||||
|
format!(
|
||||||
|
"{:3}[{}{}{:3.0}%]\n",
|
||||||
|
if app_state.app_config_fields.show_average_cpu {
|
||||||
|
if cpu_index == 0 {
|
||||||
|
"AVG".to_string()
|
||||||
|
} else {
|
||||||
|
(cpu_index - 1).to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cpu_index.to_string()
|
||||||
|
},
|
||||||
|
"|".repeat(num_bars),
|
||||||
|
" ".repeat(bar_length - num_bars),
|
||||||
|
use_percentage.round(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut row_counter = num_cpus;
|
||||||
|
let mut start_index = 0;
|
||||||
|
for (itx, chunk) in chunks.iter().enumerate() {
|
||||||
|
let to_divide = REQUIRED_COLUMNS - itx;
|
||||||
|
let how_many_cpus = min(
|
||||||
|
remaining_height,
|
||||||
|
(row_counter / to_divide) + (if row_counter % to_divide == 0 { 0 } else { 1 }),
|
||||||
|
);
|
||||||
|
row_counter -= how_many_cpus;
|
||||||
|
let end_index = min(start_index + how_many_cpus, num_cpus);
|
||||||
|
let cpu_column: Vec<Text<'_>> = (start_index..end_index)
|
||||||
|
.map(|cpu_index| {
|
||||||
|
Text::Styled(
|
||||||
|
(&cpu_bars[cpu_index]).into(),
|
||||||
|
self.colours.cpu_colour_styles
|
||||||
|
[cpu_index as usize % self.colours.cpu_colour_styles.len()],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
start_index += how_many_cpus;
|
||||||
|
|
||||||
|
let margined_loc = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(1)
|
||||||
|
.split(*chunk);
|
||||||
|
|
||||||
|
Paragraph::new(cpu_column.iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.render(f, margined_loc[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_basic_memory<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||||
|
) {
|
||||||
|
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
|
||||||
|
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
|
||||||
|
|
||||||
|
let margined_loc = Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(1)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
if let WidgetPosition::BasicMem = app_state.current_widget_selected {
|
||||||
|
Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
|
.border_style(self.colours.highlighted_border_style)
|
||||||
|
.render(f, draw_loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// +9 due to 3 + 4 + 2 + 2 columns for the name & space + percentage + bar bounds + margin spacing
|
||||||
|
let bar_length = max(0, draw_loc.width as i64 - 11) as usize;
|
||||||
|
let ram_use_percentage = if let Some(mem) = mem_data.last() {
|
||||||
|
mem.1
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
let swap_use_percentage = if let Some(swap) = swap_data.last() {
|
||||||
|
swap.1
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
let num_bars_ram = calculate_basic_use_bars(ram_use_percentage, bar_length);
|
||||||
|
let num_bars_swap = calculate_basic_use_bars(swap_use_percentage, bar_length);
|
||||||
|
let mem_label = format!(
|
||||||
|
"RAM[{}{}{:3.0}%]\n",
|
||||||
|
"|".repeat(num_bars_ram),
|
||||||
|
" ".repeat(bar_length - num_bars_ram),
|
||||||
|
ram_use_percentage.round(),
|
||||||
|
);
|
||||||
|
let swap_label = format!(
|
||||||
|
"SWP[{}{}{:3.0}%]",
|
||||||
|
"|".repeat(num_bars_swap),
|
||||||
|
" ".repeat(bar_length - num_bars_swap),
|
||||||
|
swap_use_percentage.round(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mem_text: Vec<Text<'_>> = vec![
|
||||||
|
Text::Styled(mem_label.into(), self.colours.ram_style),
|
||||||
|
Text::Styled(swap_label.into(), self.colours.swap_style),
|
||||||
|
];
|
||||||
|
|
||||||
|
Paragraph::new(mem_text.iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.render(f, margined_loc[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_basic_network<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||||
|
) {
|
||||||
|
let divided_loc = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
let net_loc = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(1)
|
||||||
|
.split(divided_loc[0]);
|
||||||
|
|
||||||
|
let total_loc = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(1)
|
||||||
|
.split(divided_loc[1]);
|
||||||
|
|
||||||
|
if let WidgetPosition::BasicNet = app_state.current_widget_selected {
|
||||||
|
Block::default()
|
||||||
|
.borders(*SIDE_BORDERS)
|
||||||
|
.border_style(self.colours.highlighted_border_style)
|
||||||
|
.render(f, draw_loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rx_label = format!("RX: {}\n", &app_state.canvas_data.rx_display);
|
||||||
|
let tx_label = format!("TX: {}", &app_state.canvas_data.tx_display);
|
||||||
|
let total_rx_label = format!("Total RX: {}\n", &app_state.canvas_data.total_rx_display);
|
||||||
|
let total_tx_label = format!("Total TX: {}", &app_state.canvas_data.total_tx_display);
|
||||||
|
|
||||||
|
let net_text = vec![
|
||||||
|
Text::Styled(rx_label.into(), self.colours.rx_style),
|
||||||
|
Text::Styled(tx_label.into(), self.colours.tx_style),
|
||||||
|
];
|
||||||
|
|
||||||
|
let total_net_text = vec![
|
||||||
|
Text::Styled(total_rx_label.into(), self.colours.total_rx_style),
|
||||||
|
Text::Styled(total_tx_label.into(), self.colours.total_tx_style),
|
||||||
|
];
|
||||||
|
|
||||||
|
Paragraph::new(net_text.iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.render(f, net_loc[0]);
|
||||||
|
|
||||||
|
Paragraph::new(total_net_text.iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.render(f, total_loc[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_basic_table_arrows<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||||
|
) {
|
||||||
|
// Effectively a paragraph with a ton of spacing
|
||||||
|
|
||||||
|
// TODO: [MODULARITY] This is hard coded. Gross.
|
||||||
|
let (left_table, right_table) = if app_state.current_widget_selected.is_widget_table() {
|
||||||
|
match app_state.current_widget_selected {
|
||||||
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
||||||
|
(WidgetPosition::Temp, WidgetPosition::Disk)
|
||||||
|
}
|
||||||
|
WidgetPosition::Disk => (WidgetPosition::Process, WidgetPosition::Temp),
|
||||||
|
WidgetPosition::Temp => (WidgetPosition::Disk, WidgetPosition::Process),
|
||||||
|
_ => (WidgetPosition::Disk, WidgetPosition::Temp),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match app_state.previous_basic_table_selected {
|
||||||
|
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
|
||||||
|
(WidgetPosition::Temp, WidgetPosition::Disk)
|
||||||
|
}
|
||||||
|
WidgetPosition::Disk => (WidgetPosition::Process, WidgetPosition::Temp),
|
||||||
|
WidgetPosition::Temp => (WidgetPosition::Disk, WidgetPosition::Process),
|
||||||
|
_ => (WidgetPosition::Disk, WidgetPosition::Temp),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_name = left_table.get_pretty_name();
|
||||||
|
let right_name = right_table.get_pretty_name();
|
||||||
|
|
||||||
|
let num_spaces = max(
|
||||||
|
0,
|
||||||
|
draw_loc.width as i64 - 2 - 4 - (left_name.len() + right_name.len()) as i64,
|
||||||
|
) as usize;
|
||||||
|
|
||||||
|
let arrow_text = vec![
|
||||||
|
Text::Styled(
|
||||||
|
format!("\n◄ {}", right_name).into(),
|
||||||
|
self.colours.text_style,
|
||||||
|
),
|
||||||
|
Text::Raw(" ".repeat(num_spaces).into()),
|
||||||
|
Text::Styled(format!("{} ►", left_name).into(), self.colours.text_style),
|
||||||
|
];
|
||||||
|
|
||||||
|
let margined_draw_loc = Layout::default()
|
||||||
|
.constraints([Constraint::Percentage(100)].as_ref())
|
||||||
|
.horizontal_margin(1)
|
||||||
|
.split(draw_loc);
|
||||||
|
|
||||||
|
Paragraph::new(arrow_text.iter())
|
||||||
|
.block(Block::default())
|
||||||
|
.render(f, margined_draw_loc[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ pub struct CanvasColours {
|
|||||||
pub swap_style: Style,
|
pub swap_style: Style,
|
||||||
pub rx_style: Style,
|
pub rx_style: Style,
|
||||||
pub tx_style: Style,
|
pub tx_style: Style,
|
||||||
pub rx_total_style: Style,
|
pub total_rx_style: Style,
|
||||||
pub tx_total_style: Style,
|
pub total_tx_style: Style,
|
||||||
pub avg_colour_style: Style,
|
pub avg_colour_style: Style,
|
||||||
pub cpu_colour_styles: Vec<Style>,
|
pub cpu_colour_styles: Vec<Style>,
|
||||||
pub border_style: Style,
|
pub border_style: Style,
|
||||||
@ -39,8 +39,8 @@ impl Default for CanvasColours {
|
|||||||
swap_style: Style::default().fg(STANDARD_SECOND_COLOUR),
|
swap_style: Style::default().fg(STANDARD_SECOND_COLOUR),
|
||||||
rx_style: Style::default().fg(STANDARD_FIRST_COLOUR),
|
rx_style: Style::default().fg(STANDARD_FIRST_COLOUR),
|
||||||
tx_style: Style::default().fg(STANDARD_SECOND_COLOUR),
|
tx_style: Style::default().fg(STANDARD_SECOND_COLOUR),
|
||||||
rx_total_style: Style::default().fg(STANDARD_THIRD_COLOUR),
|
total_rx_style: Style::default().fg(STANDARD_THIRD_COLOUR),
|
||||||
tx_total_style: Style::default().fg(STANDARD_FOURTH_COLOUR),
|
total_tx_style: Style::default().fg(STANDARD_FOURTH_COLOUR),
|
||||||
avg_colour_style: Style::default().fg(AVG_COLOUR),
|
avg_colour_style: Style::default().fg(AVG_COLOUR),
|
||||||
cpu_colour_styles: Vec::new(),
|
cpu_colour_styles: Vec::new(),
|
||||||
border_style: Style::default().fg(text_colour),
|
border_style: Style::default().fg(text_colour),
|
||||||
@ -94,12 +94,12 @@ impl CanvasColours {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> {
|
pub fn set_rx_total_colour(&mut self, colour: &str) -> error::Result<()> {
|
||||||
self.rx_total_style = get_style_from_config(colour)?;
|
self.total_rx_style = get_style_from_config(colour)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> {
|
pub fn set_tx_total_colour(&mut self, colour: &str) -> error::Result<()> {
|
||||||
self.tx_total_style = get_style_from_config(colour)?;
|
self.total_tx_style = get_style_from_config(colour)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@ use tui::style::{Color, Style};
|
|||||||
|
|
||||||
use crate::utils::{error, gen_util::*};
|
use crate::utils::{error, gen_util::*};
|
||||||
|
|
||||||
const GOLDEN_RATIO: f32 = 0.618_034; // Approx, good enough for use (also Clippy gets mad if it's too long)
|
const GOLDEN_RATIO: f32 = 0.618_034;
|
||||||
|
// Approx, good enough for use (also Clippy gets mad if it's too long)
|
||||||
pub const STANDARD_FIRST_COLOUR: Color = Color::LightMagenta;
|
pub const STANDARD_FIRST_COLOUR: Color = Color::LightMagenta;
|
||||||
pub const STANDARD_SECOND_COLOUR: Color = Color::LightYellow;
|
pub const STANDARD_SECOND_COLOUR: Color = Color::LightYellow;
|
||||||
pub const STANDARD_THIRD_COLOUR: Color = Color::LightCyan;
|
pub const STANDARD_THIRD_COLOUR: Color = Color::LightCyan;
|
||||||
|
@ -54,7 +54,7 @@ pub fn get_variable_intrinsic_widths(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Simple redistribution tactic - if there's any space left, split it evenly amongst all members
|
// Simple redistribution tactic - if there's any space left, split it evenly amongst all members
|
||||||
if last_index < num_widths {
|
if last_index < num_widths && last_index != 0 {
|
||||||
let for_all_widths = (remaining_width / last_index as i32) as u16;
|
let for_all_widths = (remaining_width / last_index as i32) as u16;
|
||||||
let mut remainder = remaining_width % last_index as i32;
|
let mut remainder = remaining_width % last_index as i32;
|
||||||
|
|
||||||
@ -150,3 +150,12 @@ pub fn get_start_position(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate how many bars are to be
|
||||||
|
/// drawn within basic mode's components.
|
||||||
|
pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize {
|
||||||
|
std::cmp::min(
|
||||||
|
(num_bars_available as f64 * use_percentage / 100.0).round() as usize,
|
||||||
|
num_bars_available,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
pub const STALE_MAX_MILLISECONDS: u128 = 60 * 1000; // How long to store data.
|
pub const STALE_MAX_MILLISECONDS: u128 = 60 * 1000;
|
||||||
|
// How long to store data.
|
||||||
pub const TIME_STARTS_FROM: u64 = 60 * 1000;
|
pub const TIME_STARTS_FROM: u64 = 60 * 1000;
|
||||||
pub const TICK_RATE_IN_MILLISECONDS: u64 = 200; // How fast the screen refreshes
|
pub const TICK_RATE_IN_MILLISECONDS: u64 = 200;
|
||||||
|
// How fast the screen refreshes
|
||||||
pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u128 = 1000;
|
pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS: u128 = 1000;
|
||||||
pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u128 = 1000;
|
pub const MAX_KEY_TIMEOUT_IN_MILLISECONDS: u128 = 1000;
|
||||||
pub const NUM_COLOURS: i32 = 256;
|
pub const NUM_COLOURS: i32 = 256;
|
||||||
|
@ -37,6 +37,7 @@ pub struct ConvertedProcessData {
|
|||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct ConvertedCpuData {
|
pub struct ConvertedCpuData {
|
||||||
pub cpu_name: String,
|
pub cpu_name: String,
|
||||||
|
/// Tuple is time, value
|
||||||
pub cpu_data: Vec<(f64, f64)>,
|
pub cpu_data: Vec<(f64, f64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +196,8 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String
|
|||||||
(current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
|
(current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
|
||||||
/ current_data.memory_harvest.mem_total_in_mb as f64)
|
/ current_data.memory_harvest.mem_total_in_mb as f64)
|
||||||
.round()
|
.round()
|
||||||
) + &format!(
|
)
|
||||||
|
+ &format!(
|
||||||
" {:.1}GB/{:.1}GB",
|
" {:.1}GB/{:.1}GB",
|
||||||
current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
|
current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
|
||||||
(current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0).round()
|
(current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0).round()
|
||||||
@ -211,7 +213,8 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String
|
|||||||
(current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
|
(current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
|
||||||
/ current_data.swap_harvest.mem_total_in_mb as f64)
|
/ current_data.swap_harvest.mem_total_in_mb as f64)
|
||||||
.round()
|
.round()
|
||||||
) + &format!(
|
)
|
||||||
|
+ &format!(
|
||||||
" {:.1}GB/{:.1}GB",
|
" {:.1}GB/{:.1}GB",
|
||||||
current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
|
current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
|
||||||
(current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0).round()
|
(current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0).round()
|
||||||
|
111
src/main.rs
111
src/main.rs
@ -20,14 +20,13 @@ use std::{
|
|||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{
|
event::{
|
||||||
poll, read, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode, KeyEvent,
|
poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent,
|
||||||
KeyModifiers, MouseEvent,
|
KeyModifiers, MouseEvent,
|
||||||
},
|
},
|
||||||
execute,
|
execute,
|
||||||
style::Print,
|
style::Print,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
|
||||||
use tui::{backend::CrosstermBackend, Terminal};
|
use tui::{backend::CrosstermBackend, Terminal};
|
||||||
|
|
||||||
use app::{
|
use app::{
|
||||||
@ -36,19 +35,24 @@ use app::{
|
|||||||
};
|
};
|
||||||
use constants::*;
|
use constants::*;
|
||||||
use data_conversion::*;
|
use data_conversion::*;
|
||||||
|
use options::*;
|
||||||
use utils::error::{self, BottomError};
|
use utils::error::{self, BottomError};
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
|
||||||
mod utils {
|
mod utils {
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod gen_util;
|
pub mod gen_util;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod data_conversion;
|
mod data_conversion;
|
||||||
|
|
||||||
enum Event<I, J> {
|
pub mod options;
|
||||||
|
|
||||||
|
enum BottomEvent<I, J> {
|
||||||
KeyInput(I),
|
KeyInput(I),
|
||||||
MouseInput(J),
|
MouseInput(J),
|
||||||
Update(Box<data_harvester::Data>),
|
Update(Box<data_harvester::Data>),
|
||||||
@ -59,49 +63,6 @@ enum ResetEvent {
|
|||||||
Reset,
|
Reset,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
flags: Option<ConfigFlags>,
|
|
||||||
colors: Option<ConfigColours>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
|
||||||
struct ConfigFlags {
|
|
||||||
avg_cpu: Option<bool>,
|
|
||||||
dot_marker: Option<bool>,
|
|
||||||
temperature_type: Option<String>,
|
|
||||||
rate: Option<u64>,
|
|
||||||
left_legend: Option<bool>,
|
|
||||||
current_usage: Option<bool>,
|
|
||||||
group_processes: Option<bool>,
|
|
||||||
case_sensitive: Option<bool>,
|
|
||||||
whole_word: Option<bool>,
|
|
||||||
regex: Option<bool>,
|
|
||||||
default_widget: Option<String>,
|
|
||||||
show_disabled_data: Option<bool>,
|
|
||||||
//disabled_cpu_cores: Option<Vec<u64>>, // TODO: [FEATURE] Enable disabling cores in config/flags
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
|
||||||
struct ConfigColours {
|
|
||||||
table_header_color: Option<String>,
|
|
||||||
avg_cpu_color: Option<String>,
|
|
||||||
cpu_core_colors: Option<Vec<String>>,
|
|
||||||
ram_color: Option<String>,
|
|
||||||
swap_color: Option<String>,
|
|
||||||
rx_color: Option<String>,
|
|
||||||
tx_color: Option<String>,
|
|
||||||
rx_total_color: Option<String>,
|
|
||||||
tx_total_color: Option<String>,
|
|
||||||
border_color: Option<String>,
|
|
||||||
highlighted_border_color: Option<String>,
|
|
||||||
text_color: Option<String>,
|
|
||||||
selected_text_color: Option<String>,
|
|
||||||
selected_bg_color: Option<String>,
|
|
||||||
widget_title_color: Option<String>,
|
|
||||||
graph_color: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_matches() -> clap::ArgMatches<'static> {
|
fn get_matches() -> clap::ArgMatches<'static> {
|
||||||
clap_app!(app =>
|
clap_app!(app =>
|
||||||
(name: crate_name!())
|
(name: crate_name!())
|
||||||
@ -118,8 +79,8 @@ fn get_matches() -> clap::ArgMatches<'static> {
|
|||||||
(@arg RATE_MILLIS: -r --rate +takes_value "Sets a refresh rate in milliseconds; the minimum is 250ms, defaults to 1000ms. Smaller values may take more resources.")
|
(@arg RATE_MILLIS: -r --rate +takes_value "Sets a refresh rate in milliseconds; the minimum is 250ms, defaults to 1000ms. Smaller values may take more resources.")
|
||||||
(@arg LEFT_LEGEND: -l --left_legend "Puts external chart legends on the left side rather than the default right side.")
|
(@arg LEFT_LEGEND: -l --left_legend "Puts external chart legends on the left side rather than the default right side.")
|
||||||
(@arg USE_CURR_USAGE: -u --current_usage "Within Linux, sets a process' CPU usage to be based on the total current CPU usage, rather than assuming 100% usage.")
|
(@arg USE_CURR_USAGE: -u --current_usage "Within Linux, sets a process' CPU usage to be based on the total current CPU usage, rather than assuming 100% usage.")
|
||||||
(@arg CONFIG_LOCATION: -C --config +takes_value "Sets the location of the config file. Expects a config file in the TOML format. If doesn't exist, creates one.")
|
(@arg CONFIG_LOCATION: -C --config +takes_value "Sets the location of the config file. Expects a config file in the TOML format. If it doesn't exist, one is created.")
|
||||||
//(@arg BASIC_MODE: -b --basic "Sets bottom to basic mode, not showing graphs and only showing basic tables.") // TODO: [FEATURE] Min mode
|
(@arg BASIC_MODE: -b --basic "Hides graphs and uses a more basic look")
|
||||||
(@arg GROUP_PROCESSES: -g --group "Groups processes with the same name together on launch.")
|
(@arg GROUP_PROCESSES: -g --group "Groups processes with the same name together on launch.")
|
||||||
(@arg CASE_SENSITIVE: -S --case_sensitive "Match case when searching by default.")
|
(@arg CASE_SENSITIVE: -S --case_sensitive "Match case when searching by default.")
|
||||||
(@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.")
|
(@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.")
|
||||||
@ -156,6 +117,7 @@ fn main() -> error::Result<()> {
|
|||||||
let use_current_cpu_total = get_use_current_cpu_total_option(&matches, &config);
|
let use_current_cpu_total = get_use_current_cpu_total_option(&matches, &config);
|
||||||
let current_widget_selected = get_default_widget(&matches, &config);
|
let current_widget_selected = get_default_widget(&matches, &config);
|
||||||
let show_disabled_data = get_show_disabled_data_option(&matches, &config);
|
let show_disabled_data = get_show_disabled_data_option(&matches, &config);
|
||||||
|
let use_basic_mode = get_use_basic_mode_option(&matches, &config);
|
||||||
|
|
||||||
// Create "app" struct, which will control most of the program and store settings/state
|
// Create "app" struct, which will control most of the program and store settings/state
|
||||||
let mut app = App::new(
|
let mut app = App::new(
|
||||||
@ -167,6 +129,7 @@ fn main() -> error::Result<()> {
|
|||||||
use_current_cpu_total,
|
use_current_cpu_total,
|
||||||
current_widget_selected,
|
current_widget_selected,
|
||||||
show_disabled_data,
|
show_disabled_data,
|
||||||
|
use_basic_mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
enable_app_grouping(&matches, &config, &mut app);
|
enable_app_grouping(&matches, &config, &mut app);
|
||||||
@ -196,7 +159,7 @@ fn main() -> error::Result<()> {
|
|||||||
thread::sleep(Duration::from_millis(
|
thread::sleep(Duration::from_millis(
|
||||||
constants::STALE_MAX_MILLISECONDS as u64 + 5000,
|
constants::STALE_MAX_MILLISECONDS as u64 + 5000,
|
||||||
));
|
));
|
||||||
tx.send(Event::Clean).unwrap();
|
tx.send(BottomEvent::Clean).unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Event loop
|
// Event loop
|
||||||
@ -221,7 +184,7 @@ fn main() -> error::Result<()> {
|
|||||||
loop {
|
loop {
|
||||||
if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
||||||
match recv {
|
match recv {
|
||||||
Event::KeyInput(event) => {
|
BottomEvent::KeyInput(event) => {
|
||||||
if handle_key_event_or_break(event, &mut app, &rtx) {
|
if handle_key_event_or_break(event, &mut app, &rtx) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -231,8 +194,8 @@ fn main() -> error::Result<()> {
|
|||||||
app.update_process_gui = false;
|
app.update_process_gui = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::MouseInput(event) => handle_mouse_event(event, &mut app),
|
BottomEvent::MouseInput(event) => handle_mouse_event(event, &mut app),
|
||||||
Event::Update(data) => {
|
BottomEvent::Update(data) => {
|
||||||
app.data_collection.eat_data(&data);
|
app.data_collection.eat_data(&data);
|
||||||
|
|
||||||
if !app.is_frozen {
|
if !app.is_frozen {
|
||||||
@ -282,7 +245,7 @@ fn main() -> error::Result<()> {
|
|||||||
update_final_process_list(&mut app);
|
update_final_process_list(&mut app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Clean => {
|
BottomEvent::Clean => {
|
||||||
app.data_collection
|
app.data_collection
|
||||||
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
||||||
}
|
}
|
||||||
@ -397,6 +360,10 @@ fn handle_key_event_or_break(
|
|||||||
KeyCode::Char('u') => app.clear_search(),
|
KeyCode::Char('u') => app.clear_search(),
|
||||||
KeyCode::Char('a') => app.skip_cursor_beginning(),
|
KeyCode::Char('a') => app.skip_cursor_beginning(),
|
||||||
KeyCode::Char('e') => app.skip_cursor_end(),
|
KeyCode::Char('e') => app.skip_cursor_end(),
|
||||||
|
// KeyCode::Char('j') => {}, // Move down
|
||||||
|
// KeyCode::Char('k') => {}, // Move up
|
||||||
|
// KeyCode::Char('h') => {}, // Move right
|
||||||
|
// KeyCode::Char('l') => {}, // Move left
|
||||||
// Can't do now, CTRL+BACKSPACE doesn't work and graphemes
|
// Can't do now, CTRL+BACKSPACE doesn't work and graphemes
|
||||||
// are hard to iter while truncating last (eloquently).
|
// are hard to iter while truncating last (eloquently).
|
||||||
// KeyCode::Backspace => app.skip_word_backspace(),
|
// KeyCode::Backspace => app.skip_word_backspace(),
|
||||||
@ -498,23 +465,15 @@ fn get_temperature_option(
|
|||||||
if let Some(temp_type) = &flags.temperature_type {
|
if let Some(temp_type) = &flags.temperature_type {
|
||||||
// Give lowest priority to config.
|
// Give lowest priority to config.
|
||||||
return match temp_type.as_str() {
|
return match temp_type.as_str() {
|
||||||
"fahrenheit" | "f" => {
|
"fahrenheit" | "f" => Ok(data_harvester::temperature::TemperatureType::Fahrenheit),
|
||||||
Ok(data_harvester::temperature::TemperatureType::Fahrenheit)
|
"kelvin" | "k" => Ok(data_harvester::temperature::TemperatureType::Kelvin),
|
||||||
}
|
"celsius" | "c" => Ok(data_harvester::temperature::TemperatureType::Celsius),
|
||||||
"kelvin" | "k" => {
|
_ => Err(BottomError::ConfigError(
|
||||||
Ok(data_harvester::temperature::TemperatureType::Kelvin)
|
|
||||||
}
|
|
||||||
"celsius" | "c" => {
|
|
||||||
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
Err(BottomError::ConfigError(
|
|
||||||
"Invalid temperature type. Please have the value be of the form \
|
"Invalid temperature type. Please have the value be of the form \
|
||||||
<kelvin|k|celsius|c|fahrenheit|f>"
|
<kelvin|k|celsius|c|fahrenheit|f>"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))
|
)),
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
||||||
@ -900,7 +859,9 @@ fn sort_process_data(to_sort_vec: &mut Vec<ConvertedProcessData>, app: &App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_input_thread(
|
fn create_input_thread(
|
||||||
tx: std::sync::mpsc::Sender<Event<crossterm::event::KeyEvent, crossterm::event::MouseEvent>>,
|
tx: std::sync::mpsc::Sender<
|
||||||
|
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
if poll(Duration::from_millis(20)).is_ok() {
|
if poll(Duration::from_millis(20)).is_ok() {
|
||||||
@ -910,16 +871,16 @@ fn create_input_thread(
|
|||||||
loop {
|
loop {
|
||||||
if poll(Duration::from_millis(20)).is_ok() {
|
if poll(Duration::from_millis(20)).is_ok() {
|
||||||
if let Ok(event) = read() {
|
if let Ok(event) = read() {
|
||||||
if let CEvent::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
if Instant::now().duration_since(keyboard_timer).as_millis() >= 20 {
|
if Instant::now().duration_since(keyboard_timer).as_millis() >= 20 {
|
||||||
if tx.send(Event::KeyInput(key)).is_err() {
|
if tx.send(BottomEvent::KeyInput(key)).is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
keyboard_timer = Instant::now();
|
keyboard_timer = Instant::now();
|
||||||
}
|
}
|
||||||
} else if let CEvent::Mouse(mouse) = event {
|
} else if let Event::Mouse(mouse) = event {
|
||||||
if Instant::now().duration_since(mouse_timer).as_millis() >= 20 {
|
if Instant::now().duration_since(mouse_timer).as_millis() >= 20 {
|
||||||
if tx.send(Event::MouseInput(mouse)).is_err() {
|
if tx.send(BottomEvent::MouseInput(mouse)).is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mouse_timer = Instant::now();
|
mouse_timer = Instant::now();
|
||||||
@ -933,7 +894,9 @@ fn create_input_thread(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_event_thread(
|
fn create_event_thread(
|
||||||
tx: std::sync::mpsc::Sender<Event<crossterm::event::KeyEvent, crossterm::event::MouseEvent>>,
|
tx: std::sync::mpsc::Sender<
|
||||||
|
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
||||||
|
>,
|
||||||
rrx: std::sync::mpsc::Receiver<ResetEvent>, use_current_cpu_total: bool,
|
rrx: std::sync::mpsc::Receiver<ResetEvent>, use_current_cpu_total: bool,
|
||||||
update_rate_in_milliseconds: u64, temp_type: data_harvester::temperature::TemperatureType,
|
update_rate_in_milliseconds: u64, temp_type: data_harvester::temperature::TemperatureType,
|
||||||
) {
|
) {
|
||||||
@ -952,7 +915,7 @@ fn create_event_thread(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
futures::executor::block_on(data_state.update_data());
|
futures::executor::block_on(data_state.update_data());
|
||||||
let event = Event::Update(Box::from(data_state.data));
|
let event = BottomEvent::Update(Box::from(data_state.data));
|
||||||
data_state.data = data_harvester::Data::default();
|
data_state.data = data_harvester::Data::default();
|
||||||
tx.send(event).unwrap();
|
tx.send(event).unwrap();
|
||||||
thread::sleep(Duration::from_millis(update_rate_in_milliseconds));
|
thread::sleep(Duration::from_millis(update_rate_in_milliseconds));
|
||||||
|
267
src/options.rs
Normal file
267
src/options.rs
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{data_harvester, App, WidgetPosition},
|
||||||
|
constants::*,
|
||||||
|
utils::error::{self, BottomError},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub flags: Option<ConfigFlags>,
|
||||||
|
pub colors: Option<ConfigColours>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize)]
|
||||||
|
pub struct ConfigFlags {
|
||||||
|
pub avg_cpu: Option<bool>,
|
||||||
|
pub dot_marker: Option<bool>,
|
||||||
|
pub temperature_type: Option<String>,
|
||||||
|
pub rate: Option<u64>,
|
||||||
|
pub left_legend: Option<bool>,
|
||||||
|
pub current_usage: Option<bool>,
|
||||||
|
pub group_processes: Option<bool>,
|
||||||
|
pub case_sensitive: Option<bool>,
|
||||||
|
pub whole_word: Option<bool>,
|
||||||
|
pub regex: Option<bool>,
|
||||||
|
pub default_widget: Option<String>,
|
||||||
|
pub show_disabled_data: Option<bool>,
|
||||||
|
pub basic: Option<bool>,
|
||||||
|
//disabled_cpu_cores: Option<Vec<u64>>, // TODO: [FEATURE] Enable disabling cores in config/flags
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize)]
|
||||||
|
pub struct ConfigColours {
|
||||||
|
pub table_header_color: Option<String>,
|
||||||
|
pub avg_cpu_color: Option<String>,
|
||||||
|
pub cpu_core_colors: Option<Vec<String>>,
|
||||||
|
pub ram_color: Option<String>,
|
||||||
|
pub swap_color: Option<String>,
|
||||||
|
pub rx_color: Option<String>,
|
||||||
|
pub tx_color: Option<String>,
|
||||||
|
pub rx_total_color: Option<String>,
|
||||||
|
pub tx_total_color: Option<String>,
|
||||||
|
pub border_color: Option<String>,
|
||||||
|
pub highlighted_border_color: Option<String>,
|
||||||
|
pub text_color: Option<String>,
|
||||||
|
pub selected_text_color: Option<String>,
|
||||||
|
pub selected_bg_color: Option<String>,
|
||||||
|
pub widget_title_color: Option<String>,
|
||||||
|
pub graph_color: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_update_rate_in_milliseconds(
|
||||||
|
update_rate: &Option<&str>, config: &Config,
|
||||||
|
) -> error::Result<u128> {
|
||||||
|
let update_rate_in_milliseconds = if let Some(update_rate) = update_rate {
|
||||||
|
update_rate.parse::<u128>()?
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(rate) = flags.rate {
|
||||||
|
rate as u128
|
||||||
|
} else {
|
||||||
|
DEFAULT_REFRESH_RATE_IN_MILLISECONDS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEFAULT_REFRESH_RATE_IN_MILLISECONDS
|
||||||
|
};
|
||||||
|
|
||||||
|
if update_rate_in_milliseconds < 250 {
|
||||||
|
return Err(BottomError::InvalidArg(
|
||||||
|
"Please set your update rate to be greater than 250 milliseconds.".to_string(),
|
||||||
|
));
|
||||||
|
} else if update_rate_in_milliseconds > u128::from(std::u64::MAX) {
|
||||||
|
return Err(BottomError::InvalidArg(
|
||||||
|
"Please set your update rate to be less than unsigned INT_MAX.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(update_rate_in_milliseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_temperature_option(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config,
|
||||||
|
) -> error::Result<data_harvester::temperature::TemperatureType> {
|
||||||
|
if matches.is_present("FAHRENHEIT") {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Fahrenheit);
|
||||||
|
} else if matches.is_present("KELVIN") {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Kelvin);
|
||||||
|
} else if matches.is_present("CELSIUS") {
|
||||||
|
return Ok(data_harvester::temperature::TemperatureType::Celsius);
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(temp_type) = &flags.temperature_type {
|
||||||
|
// Give lowest priority to config.
|
||||||
|
return match temp_type.as_str() {
|
||||||
|
"fahrenheit" | "f" => {
|
||||||
|
Ok(data_harvester::temperature::TemperatureType::Fahrenheit)
|
||||||
|
}
|
||||||
|
"kelvin" | "k" => {
|
||||||
|
Ok(data_harvester::temperature::TemperatureType::Kelvin)
|
||||||
|
}
|
||||||
|
"celsius" | "c" => {
|
||||||
|
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Err(BottomError::ConfigError(
|
||||||
|
"Invalid temperature type. Please have the value be of the form <kelvin|k|celsius|c|fahrenheit|f>".to_string()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_avg_cpu_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("AVG_CPU") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(avg_cpu) = flags.avg_cpu {
|
||||||
|
return avg_cpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_use_dot_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("DOT_MARKER") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(dot_marker) = flags.dot_marker {
|
||||||
|
return dot_marker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_use_left_legend_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("LEFT_LEGEND") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(left_legend) = flags.left_legend {
|
||||||
|
return left_legend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_use_current_cpu_total_option(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config,
|
||||||
|
) -> bool {
|
||||||
|
if matches.is_present("USE_CURR_USAGE") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(current_usage) = flags.current_usage {
|
||||||
|
return current_usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_show_disabled_data_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("SHOW_DISABLED_DATA") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(show_disabled_data) = flags.show_disabled_data {
|
||||||
|
return show_disabled_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_use_basic_mode_option(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||||
|
if matches.is_present("BASIC_MODE") {
|
||||||
|
return true;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(basic) = flags.basic {
|
||||||
|
return basic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) {
|
||||||
|
if matches.is_present("GROUP_PROCESSES") {
|
||||||
|
app.toggle_grouping();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(grouping) = flags.group_processes {
|
||||||
|
if grouping {
|
||||||
|
app.toggle_grouping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_app_case_sensitive(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
|
||||||
|
) {
|
||||||
|
if matches.is_present("CASE_SENSITIVE") {
|
||||||
|
app.process_search_state.search_toggle_ignore_case();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(case_sensitive) = flags.case_sensitive {
|
||||||
|
if case_sensitive {
|
||||||
|
app.process_search_state.search_toggle_ignore_case();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_app_match_whole_word(
|
||||||
|
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
|
||||||
|
) {
|
||||||
|
if matches.is_present("WHOLE_WORD") {
|
||||||
|
app.process_search_state.search_toggle_whole_word();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(whole_word) = flags.whole_word {
|
||||||
|
if whole_word {
|
||||||
|
app.process_search_state.search_toggle_whole_word();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) {
|
||||||
|
if matches.is_present("REGEX_DEFAULT") {
|
||||||
|
app.process_search_state.search_toggle_regex();
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(regex) = flags.regex {
|
||||||
|
if regex {
|
||||||
|
app.process_search_state.search_toggle_regex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition {
|
||||||
|
if matches.is_present("CPU_WIDGET") {
|
||||||
|
return WidgetPosition::Cpu;
|
||||||
|
} else if matches.is_present("MEM_WIDGET") {
|
||||||
|
return WidgetPosition::Mem;
|
||||||
|
} else if matches.is_present("DISK_WIDGET") {
|
||||||
|
return WidgetPosition::Disk;
|
||||||
|
} else if matches.is_present("TEMP_WIDGET") {
|
||||||
|
return WidgetPosition::Temp;
|
||||||
|
} else if matches.is_present("NET_WIDGET") {
|
||||||
|
return WidgetPosition::Network;
|
||||||
|
} else if matches.is_present("PROC_WIDGET") {
|
||||||
|
return WidgetPosition::Process;
|
||||||
|
} else if let Some(flags) = &config.flags {
|
||||||
|
if let Some(default_widget) = &flags.default_widget {
|
||||||
|
return match default_widget.as_str() {
|
||||||
|
"cpu_default" => WidgetPosition::Cpu,
|
||||||
|
"memory_default" => WidgetPosition::Mem,
|
||||||
|
"processes_default" => WidgetPosition::Process,
|
||||||
|
"network_default" => WidgetPosition::Network,
|
||||||
|
"temperature_default" => WidgetPosition::Temp,
|
||||||
|
"disk_default" => WidgetPosition::Disk,
|
||||||
|
_ => WidgetPosition::Process,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetPosition::Process
|
||||||
|
}
|
@ -5,7 +5,7 @@ use predicates::prelude::*;
|
|||||||
|
|
||||||
// These tests are mostly here just to ensure that invalid results will be caught when passing arguments...
|
// These tests are mostly here just to ensure that invalid results will be caught when passing arguments...
|
||||||
|
|
||||||
// TODO: [TEST] Allow for release testing.
|
// TODO: [TEST] Allow for release testing. Do this with paths.
|
||||||
|
|
||||||
//======================RATES======================//
|
//======================RATES======================//
|
||||||
|
|
||||||
@ -71,3 +71,31 @@ fn test_invalid_rate() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conflicting_temps() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
Command::new(get_os_binary_loc())
|
||||||
|
.arg("-c")
|
||||||
|
.arg("-f")
|
||||||
|
.assert()
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::contains(
|
||||||
|
"cannot be used with one or more of the other specified arguments",
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_conflicting_default_widget() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
Command::new(get_os_binary_loc())
|
||||||
|
.arg("--cpu_default")
|
||||||
|
.arg("--disk_default")
|
||||||
|
.assert()
|
||||||
|
.failure()
|
||||||
|
.stderr(predicate::str::contains(
|
||||||
|
"cannot be used with one or more of the other specified arguments",
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user