Add modularity to widget placement and inclusion (#95)

This commit is contained in:
Clement Tsang 2020-04-01 20:31:43 -04:00 committed by GitHub
parent c1a19f960f
commit 0b1d84fdf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 4988 additions and 2570 deletions

View File

@ -9,7 +9,7 @@ os:
jobs:
allow_failures:
- rust: nightly
- env: TARGET=x86_64-pc-windows-gnu # Seems to cause problems. TODO: Add test for it, but keep allow fail.
- env: TARGET=x86_64-pc-windows-gnu # Seems to cause problems.
fast_finish: true
branches:
only:

View File

@ -31,7 +31,7 @@ fern = "0.6.0"
futures = "0.3.4"
heim = "0.0.10"
log = "0.4.8"
regex = "1.3.4"
regex = "1.3"
sysinfo = "0.11"
toml = "0.5.6"
tui = {version = "0.8", features = ["crossterm"], default-features = false }

1
clippy.toml Normal file
View File

@ -0,0 +1 @@
cognitive-complexity-threshold = 35

View File

@ -8,7 +8,6 @@ One use of a config file is to set boot flags to execute without having to state
- These options are generally the same as the long names as other flags (ex: `case_sensitive = true`).
- Note that if a flag and an option conflict, the flag has higher precedence (ex: if the `-c` and `temperature_type = kelvin` both exist, the Celsius temperature type is ultimately chosen).
- For temperature type, use `temperature_type = "kelvin|k|celsius|c|fahrenheit|f"`.
- For default widgets, use `default_widget = "cpu_default|memory_default|disk_default|temperature_default|network_default|process_default"`.
## Colours
@ -36,6 +35,45 @@ Supported named colours are one of the following: `Reset, Black, Red, Green, Yel
Note some colours may not be compatible with the terminal you are using. For example, macOS's default Terminal does not play nice with many colours.
## Layout
As of 0.3.0, bottom supports custom layouts. Layouts are in the TOML specification, and are arranged by row -> column -> row. For example, the default layout:
```toml
[[row]]
ratio=30
[[row.child]]
type="cpu"
[[row]]
ratio=40
[[row.child]]
ratio=4
type="mem"
[[row.child]]
ratio=3
[[row.child.child]]
type="temp"
[[row.child.child]]
type="disk"
[[row]]
ratio=30
[[row.child]]
type="net"
[[row.child]]
type="proc"
default=true
```
Valid types are:
- `cpu`
- `mem`
- `proc`
- `net`
- `temp`
- `disk`
- `empty`
## Default config locations
bottom will check specific locations by default for a config file. If no file is found, it will be created.

3077
src/app.rs

File diff suppressed because it is too large Load Diff

View File

@ -80,8 +80,6 @@ fn cpu_usage_calculation(
let total_delta: f64 = total - prev_total;
let idle_delta: f64 = idle - *prev_idle;
//debug!("Vangelis function: CPU PERCENT: {}", (total_delta - idle_delta) / total_delta * 100_f64);
*prev_idle = idle;
*prev_non_idle = non_idle;
@ -111,8 +109,6 @@ fn get_process_cpu_stats(pid: u32) -> std::io::Result<f64> {
let utime = val[13].parse::<f64>().unwrap_or(0_f64);
let stime = val[14].parse::<f64>().unwrap_or(0_f64);
//debug!("PID: {}, utime: {}, stime: {}", pid, utime, stime);
Ok(utime + stime) // This seems to match top...
}
@ -134,15 +130,6 @@ fn linux_cpu_usage<S: core::hash::BuildHasher>(
};
let after_proc_val = get_process_cpu_stats(pid)?;
/*debug!(
"PID - {} - Before: {}, After: {}, CPU: {}, Percentage: {}",
pid,
before_proc_val,
after_proc_val,
cpu_usage,
(after_proc_val - before_proc_val) / cpu_usage * 100_f64
);*/
new_pid_stats.insert(pid.to_string(), (after_proc_val, curr_time));
if use_current_cpu_total {

940
src/app/layout_manager.rs Normal file
View File

@ -0,0 +1,940 @@
use crate::error::{BottomError, Result};
use std::collections::BTreeMap;
use typed_builder::*;
use crate::constants::DEFAULT_WIDGET_ID;
/// Represents a more usable representation of the layout, derived from the
/// config.
#[derive(Clone, Debug)]
pub struct BottomLayout {
pub rows: Vec<BottomRow>,
pub total_row_height_ratio: u32,
}
type WidgetMappings = (u32, BTreeMap<(u32, u32), u64>);
type ColumnRowMappings = (u32, BTreeMap<(u32, u32), WidgetMappings>);
type ColumnMappings = (u32, BTreeMap<(u32, u32), ColumnRowMappings>);
impl BottomLayout {
#[allow(clippy::cognitive_complexity)]
pub fn get_movement_mappings(&mut self) {
fn is_intersecting(a: (u32, u32), b: (u32, u32)) -> bool {
a.0 >= b.0 && a.1 <= b.1
|| a.1 >= b.1 && a.0 <= b.0
|| a.0 <= b.0 && a.1 >= b.0
|| a.0 >= b.0 && a.0 < b.1 && a.1 >= b.1
}
fn get_distance(target: (u32, u32), candidate: (u32, u32)) -> u32 {
if candidate.0 < target.0 {
candidate.1 - target.0
} else if candidate.1 < target.1 {
candidate.1 - candidate.0
} else {
target.1 - candidate.0
}
}
// Now we need to create the correct mapping for moving from a specific
// widget to another
let mut layout_mapping: BTreeMap<(u32, u32), ColumnMappings> = BTreeMap::new();
let mut total_height = 0;
for row in &self.rows {
let mut row_width = 0;
let mut row_mapping: BTreeMap<(u32, u32), ColumnRowMappings> = BTreeMap::new();
let mut is_valid_row = false;
for col in &row.children {
let mut col_row_height = 0;
let mut col_mapping: BTreeMap<(u32, u32), WidgetMappings> = BTreeMap::new();
let mut is_valid_col = false;
for col_row in &col.children {
let mut widget_width = 0;
let mut col_row_mapping: BTreeMap<(u32, u32), u64> = BTreeMap::new();
let mut is_valid_col_row = false;
for widget in &col_row.children {
match widget.widget_type {
BottomWidgetType::Empty => {}
_ => {
is_valid_col_row = true;
col_row_mapping.insert(
(
widget_width * 100 / col_row.total_widget_ratio,
(widget_width + widget.width_ratio) * 100
/ col_row.total_widget_ratio,
),
widget.widget_id,
);
}
}
widget_width += widget.width_ratio;
}
if is_valid_col_row {
col_mapping.insert(
(
col_row_height * 100 / col.total_col_row_ratio,
(col_row_height + col_row.col_row_height_ratio) * 100
/ col.total_col_row_ratio,
),
(col.total_col_row_ratio, col_row_mapping),
);
is_valid_col = true;
}
col_row_height += col_row.col_row_height_ratio;
}
if is_valid_col {
row_mapping.insert(
(
row_width * 100 / row.total_col_ratio,
(row_width + col.col_width_ratio) * 100 / row.total_col_ratio,
),
(row.total_col_ratio, col_mapping),
);
is_valid_row = true;
}
row_width += col.col_width_ratio;
}
if is_valid_row {
layout_mapping.insert(
(
total_height * 100 / self.total_row_height_ratio,
(total_height + row.row_height_ratio) * 100 / self.total_row_height_ratio,
),
(self.total_row_height_ratio, row_mapping),
);
}
total_height += row.row_height_ratio;
}
// Now pass through a second time; this time we want to build up
// our neighbour profile.
let mut height_cursor = 0;
for row in &mut self.rows {
let mut col_cursor = 0;
let row_height_percentage_start = height_cursor * 100 / self.total_row_height_ratio;
let row_height_percentage_end =
(height_cursor + row.row_height_ratio) * 100 / self.total_row_height_ratio;
for col in &mut row.children {
let mut col_row_cursor = 0;
let col_width_percentage_start = col_cursor * 100 / row.total_col_ratio;
let col_width_percentage_end =
(col_cursor + col.col_width_ratio) * 100 / row.total_col_ratio;
for col_row in &mut col.children {
let mut widget_cursor = 0;
let col_row_height_percentage_start =
col_row_cursor * 100 / col.total_col_row_ratio;
let col_row_height_percentage_end =
(col_row_cursor + col_row.col_row_height_ratio) * 100
/ col.total_col_row_ratio;
let col_row_children_len = col_row.children.len();
for widget in &mut col_row.children {
// Bail if empty.
if let BottomWidgetType::Empty = widget.widget_type {
continue;
}
let widget_width_percentage_start =
widget_cursor * 100 / col_row.total_widget_ratio;
let widget_width_percentage_end =
(widget_cursor + widget.width_ratio) * 100 / col_row.total_widget_ratio;
if let Some(current_row) = layout_mapping
.get(&(row_height_percentage_start, row_height_percentage_end))
{
// First check for within the same col_row for left and right
if let Some(current_col) = current_row
.1
.get(&(col_width_percentage_start, col_width_percentage_end))
{
if let Some(current_col_row) = current_col.1.get(&(
col_row_height_percentage_start,
col_row_height_percentage_end,
)) {
if let Some(to_left_widget) = current_col_row
.1
.range(
..(
widget_width_percentage_start,
widget_width_percentage_start,
),
)
.next_back()
{
widget.left_neighbour = Some(*to_left_widget.1);
}
// Right
if let Some(to_right_neighbour) = current_col_row
.1
.range(
(
widget_width_percentage_end,
widget_width_percentage_end,
)..,
)
.next()
{
widget.right_neighbour = Some(*to_right_neighbour.1);
}
}
}
if widget.left_neighbour.is_none() {
if let Some(to_left_col) = current_row
.1
.range(
..(col_width_percentage_start, col_width_percentage_start),
)
.next_back()
{
// Check left in same row
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
for widget_position in &(to_left_col.1).1 {
let candidate_start = (widget_position.0).0;
let candidate_end = (widget_position.0).1;
if is_intersecting(
(
col_row_height_percentage_start,
col_row_height_percentage_end,
),
(candidate_start, candidate_end),
) {
let candidate_distance = get_distance(
(
col_row_height_percentage_start,
col_row_height_percentage_end,
),
(candidate_start, candidate_end),
);
if current_best_distance < candidate_distance {
if let Some(new_best_widget) =
(widget_position.1).1.iter().next_back()
{
current_best_distance = candidate_distance + 1;
current_best_widget_id = *(new_best_widget.1);
}
}
}
}
if current_best_distance > 0 {
widget.left_neighbour = Some(current_best_widget_id);
}
}
}
if widget.right_neighbour.is_none() {
if let Some(to_right_col) = current_row
.1
.range((col_width_percentage_end, col_width_percentage_end)..)
.next()
{
// Check right in same row
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
for widget_position in &(to_right_col.1).1 {
let candidate_start = (widget_position.0).0;
let candidate_end = (widget_position.0).1;
if is_intersecting(
(
col_row_height_percentage_start,
col_row_height_percentage_end,
),
(candidate_start, candidate_end),
) {
let candidate_distance = get_distance(
(
col_row_height_percentage_start,
col_row_height_percentage_end,
),
(candidate_start, candidate_end),
);
if current_best_distance < candidate_distance {
if let Some(new_best_widget) =
(widget_position.1).1.iter().next()
{
current_best_distance = candidate_distance + 1;
current_best_widget_id = *(new_best_widget.1);
}
}
}
}
if current_best_distance > 0 {
widget.right_neighbour = Some(current_best_widget_id);
}
}
}
// Check up/down within same row;
// else check up/down with other rows
if let Some(current_col) = current_row
.1
.get(&(col_width_percentage_start, col_width_percentage_end))
{
if let Some(to_up) = current_col
.1
.range(
..(
col_row_height_percentage_start,
col_row_height_percentage_start,
),
)
.next_back()
{
// Now check each widget_width and pick the best
for candidate_widget in &(to_up.1).1 {
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
if is_intersecting(
(
widget_width_percentage_start,
widget_width_percentage_end,
),
((candidate_widget.0).0, (candidate_widget.0).1),
) {
let candidate_best_distance = get_distance(
(
widget_width_percentage_start,
widget_width_percentage_end,
),
((candidate_widget.0).0, (candidate_widget.0).1),
);
if current_best_distance < candidate_best_distance {
current_best_distance = candidate_best_distance + 1;
current_best_widget_id = *candidate_widget.1;
}
}
if current_best_distance > 0 {
widget.up_neighbour = Some(current_best_widget_id);
}
}
} else if let Some(next_row_up) = layout_mapping
.range(
..(
row_height_percentage_start,
row_height_percentage_start,
),
)
.next_back()
{
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
let (target_start_width, target_end_width) =
if col_row_children_len > 1 {
(
col_width_percentage_start
+ widget_width_percentage_start
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
col_width_percentage_start
+ widget_width_percentage_end
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
)
} else {
(col_width_percentage_start, col_width_percentage_end)
};
for col_position in &(next_row_up.1).1 {
if let Some(next_col_row) =
(col_position.1).1.iter().next_back()
{
let (candidate_col_start, candidate_col_end) =
((col_position.0).0, (col_position.0).1);
let candidate_difference =
candidate_col_end - candidate_col_start;
for candidate_widget in &(next_col_row.1).1 {
let candidate_start = candidate_col_start
+ (candidate_widget.0).0 * candidate_difference
/ 100;
let candidate_end = candidate_col_start
+ (candidate_widget.0).1 * candidate_difference
/ 100;
if is_intersecting(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
) {
let candidate_distance = get_distance(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
);
if current_best_distance < candidate_distance {
current_best_distance =
candidate_distance + 1;
current_best_widget_id =
*(candidate_widget.1);
}
}
}
}
}
if current_best_distance > 0 {
widget.up_neighbour = Some(current_best_widget_id);
}
}
if let Some(to_down) = current_col
.1
.range(
(
col_row_height_percentage_start + 1,
col_row_height_percentage_start + 1,
)..,
)
.next()
{
for candidate_widget in &(to_down.1).1 {
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
if is_intersecting(
(
widget_width_percentage_start,
widget_width_percentage_end,
),
((candidate_widget.0).0, (candidate_widget.0).1),
) {
let candidate_best_distance = get_distance(
(
widget_width_percentage_start,
widget_width_percentage_end,
),
((candidate_widget.0).0, (candidate_widget.0).1),
);
if current_best_distance < candidate_best_distance {
current_best_distance = candidate_best_distance + 1;
current_best_widget_id = *candidate_widget.1;
}
}
if current_best_distance > 0 {
widget.down_neighbour = Some(current_best_widget_id);
}
}
} else if let Some(next_row_down) = layout_mapping
.range(
(
row_height_percentage_start + 1,
row_height_percentage_start + 1,
)..,
)
.next()
{
let mut current_best_distance = 0;
let mut current_best_widget_id = widget.widget_id;
let (target_start_width, target_end_width) =
if col_row_children_len > 1 {
(
col_width_percentage_start
+ widget_width_percentage_start
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
col_width_percentage_start
+ widget_width_percentage_end
* (col_width_percentage_end
- col_width_percentage_start)
/ 100,
)
} else {
(col_width_percentage_start, col_width_percentage_end)
};
for col_position in &(next_row_down.1).1 {
if let Some(next_col_row) = (col_position.1).1.iter().next()
{
let (candidate_col_start, candidate_col_end) =
((col_position.0).0, (col_position.0).1);
let candidate_difference =
candidate_col_end - candidate_col_start;
for candidate_widget in &(next_col_row.1).1 {
let candidate_start = candidate_col_start
+ (candidate_widget.0).0 * candidate_difference
/ 100;
let candidate_end = candidate_col_start
+ (candidate_widget.0).1 * candidate_difference
/ 100;
if is_intersecting(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
) {
let candidate_distance = get_distance(
(target_start_width, target_end_width),
(candidate_start, candidate_end),
);
if current_best_distance < candidate_distance {
current_best_distance =
candidate_distance + 1;
current_best_widget_id =
*(candidate_widget.1);
}
}
}
}
}
if current_best_distance > 0 {
widget.down_neighbour = Some(current_best_widget_id);
}
}
}
}
widget_cursor += widget.width_ratio;
}
col_row_cursor += col_row.col_row_height_ratio;
}
col_cursor += col.col_width_ratio;
}
height_cursor += row.row_height_ratio;
}
}
pub fn init_basic_default() -> Self {
BottomLayout {
total_row_height_ratio: 3,
rows: vec![
BottomRow::builder()
.canvas_handle_height(true)
.children(vec![BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::BasicCpu)
.widget_id(1)
.down_neighbour(Some(2))
.build()])
.build()])
.build()])
.build(),
BottomRow::builder()
.canvas_handle_height(true)
.children(vec![BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![
BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::BasicMem)
.widget_id(2)
.up_neighbour(Some(1))
.down_neighbour(Some(100))
.right_neighbour(Some(3))
.build(),
BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::BasicNet)
.widget_id(3)
.up_neighbour(Some(1))
.down_neighbour(Some(100))
.left_neighbour(Some(2))
.build(),
])
.build()])
.build()])
.build(),
BottomRow::builder()
.canvas_handle_height(true)
.children(vec![BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::BasicTables)
.widget_id(100)
.up_neighbour(Some(2))
.build()])
.build()])
.build()])
.build(),
BottomRow::builder()
.canvas_handle_height(true)
.children(vec![
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Disk)
.widget_id(4)
.up_neighbour(Some(100))
.left_neighbour(Some(7))
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.build()])
.build()])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.up_neighbour(Some(100))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(DEFAULT_WIDGET_ID + 1)
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(4))
.right_neighbour(Some(7))
.build()])
.build(),
])
.build(),
BottomCol::builder()
.canvas_handle_width(true)
.children(vec![BottomColRow::builder()
.canvas_handle_height(true)
.children(vec![BottomWidget::builder()
.canvas_handle_width(true)
.widget_type(BottomWidgetType::Temp)
.widget_id(7)
.up_neighbour(Some(100))
.left_neighbour(Some(DEFAULT_WIDGET_ID))
.right_neighbour(Some(4))
.build()])
.build()])
.build(),
])
.build(),
],
}
}
pub fn init_default(left_legend: bool) -> Self {
BottomLayout {
total_row_height_ratio: 100,
rows: vec![
BottomRow::builder()
.row_height_ratio(30)
.children(vec![BottomCol::builder()
.children(vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(if left_legend {
vec![
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(11))
.right_neighbour(Some(1))
.canvas_handle_width(true)
.build(),
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(12))
.left_neighbour(Some(2))
.flex_grow(true)
.build(),
]
} else {
vec![
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(1)
.down_neighbour(Some(11))
.right_neighbour(Some(2))
.flex_grow(true)
.build(),
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(2)
.down_neighbour(Some(12))
.left_neighbour(Some(1))
.canvas_handle_width(true)
.build(),
]
})
.build()])
.build()])
.build(),
BottomRow::builder()
.total_col_ratio(7)
.row_height_ratio(40)
.children(vec![
BottomCol::builder()
.col_width_ratio(4)
.children(vec![BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Mem)
.widget_id(11)
.right_neighbour(Some(12))
.up_neighbour(Some(1))
.down_neighbour(Some(21))
.build()])
.build()])
.build(),
BottomCol::builder()
.total_col_row_ratio(2)
.col_width_ratio(3)
.children(vec![
BottomColRow::builder()
.col_row_height_ratio(1)
.total_widget_ratio(2)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Temp)
.widget_id(12)
.left_neighbour(Some(11))
.up_neighbour(Some(1))
.down_neighbour(Some(13))
.build()])
.build(),
BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Disk)
.widget_id(13)
.left_neighbour(Some(11))
.up_neighbour(Some(12))
.down_neighbour(Some(DEFAULT_WIDGET_ID))
.build()])
.build(),
])
.build(),
])
.build(),
BottomRow::builder()
.total_col_ratio(2)
.row_height_ratio(30)
.children(vec![
BottomCol::builder()
.children(vec![BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Net)
.widget_id(21)
.right_neighbour(Some(DEFAULT_WIDGET_ID))
.up_neighbour(Some(11))
.build()])
.build()])
.build(),
BottomCol::builder()
.total_col_row_ratio(2)
.children(vec![
BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Proc)
.widget_id(DEFAULT_WIDGET_ID)
.left_neighbour(Some(21))
.up_neighbour(Some(13))
.down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
.build()])
.flex_grow(true)
.build(),
BottomColRow::builder()
.col_row_height_ratio(1)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(DEFAULT_WIDGET_ID + 1)
.up_neighbour(Some(DEFAULT_WIDGET_ID))
.left_neighbour(Some(21))
.build()])
.canvas_handle_height(true)
.build(),
])
.build(),
])
.build(),
],
}
}
}
/// Represents a single row in the layout.
#[derive(Clone, Debug, TypedBuilder)]
pub struct BottomRow {
pub children: Vec<BottomCol>,
#[builder(default = 1)]
pub total_col_ratio: u32,
#[builder(default = 1)]
pub row_height_ratio: u32,
#[builder(default = false)]
pub canvas_handle_height: bool,
#[builder(default = false)]
pub flex_grow: bool,
}
/// Represents a single column in the layout. We assume that even if the column
/// contains only ONE element, it is still a column (rather than either a col or
/// a widget, as per the config, for simplicity's sake).
#[derive(Clone, Debug, TypedBuilder)]
pub struct BottomCol {
pub children: Vec<BottomColRow>,
#[builder(default = 1)]
pub total_col_row_ratio: u32,
#[builder(default = 1)]
pub col_width_ratio: u32,
#[builder(default = false)]
pub canvas_handle_width: bool,
#[builder(default = false)]
pub flex_grow: bool,
}
#[derive(Clone, Default, Debug, TypedBuilder)]
pub struct BottomColRow {
pub children: Vec<BottomWidget>,
#[builder(default = 1)]
pub total_widget_ratio: u32,
#[builder(default = 1)]
pub col_row_height_ratio: u32,
#[builder(default = false)]
pub canvas_handle_height: bool,
#[builder(default = false)]
pub flex_grow: bool,
}
/// Represents a single widget.
#[derive(Debug, Default, Clone, TypedBuilder)]
pub struct BottomWidget {
pub widget_type: BottomWidgetType,
pub widget_id: u64,
#[builder(default = 1)]
pub width_ratio: u32,
#[builder(default = None)]
pub left_neighbour: Option<u64>,
#[builder(default = None)]
pub right_neighbour: Option<u64>,
#[builder(default = None)]
pub up_neighbour: Option<u64>,
#[builder(default = None)]
pub down_neighbour: Option<u64>,
#[builder(default = false)]
pub canvas_handle_width: bool,
#[builder(default = false)]
pub flex_grow: bool,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum BottomWidgetType {
Empty,
Cpu,
CpuLegend,
Mem,
Net,
Proc,
ProcSearch,
Temp,
Disk,
BasicCpu,
BasicMem,
BasicNet,
BasicTables,
}
impl BottomWidgetType {
pub fn is_widget_table(&self) -> bool {
use BottomWidgetType::*;
match self {
Disk | Proc | Temp | CpuLegend => true,
_ => false,
}
}
pub fn is_widget_graph(&self) -> bool {
use BottomWidgetType::*;
match self {
Cpu | Net | Mem => true,
_ => false,
}
}
pub fn get_pretty_name(&self) -> &str {
use BottomWidgetType::*;
match self {
Cpu => "CPU",
Mem => "Memory",
Net => "Network",
Proc => "Processes",
Temp => "Temperature",
Disk => "Disks",
_ => "",
}
}
}
impl Default for BottomWidgetType {
fn default() -> Self {
BottomWidgetType::Empty
}
}
impl std::str::FromStr for BottomWidgetType {
type Err = BottomError;
fn from_str(s: &str) -> Result<Self> {
let lower_case = s.to_lowercase();
match lower_case.as_str() {
"cpu" => Ok(BottomWidgetType::Cpu),
"mem" => Ok(BottomWidgetType::Mem),
"net" => Ok(BottomWidgetType::Net),
"proc" => Ok(BottomWidgetType::Proc),
"temp" => Ok(BottomWidgetType::Temp),
"disk" => Ok(BottomWidgetType::Disk),
"empty" => Ok(BottomWidgetType::Empty),
_ => Err(BottomError::ConfigError(format!(
"Invalid widget type: {}",
s
))),
}
}
}

View File

@ -21,7 +21,7 @@ impl Process {
fn open(pid: DWORD) -> Result<Process, String> {
let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) };
if pc.is_null() {
return Err("!OpenProcess".to_string());
return Err("OpenProcess".to_string());
}
Ok(Process(pc))
}

View File

@ -3,8 +3,7 @@ use std::collections::HashMap;
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
layout::{Constraint, Direction, Layout},
widgets::Text,
Terminal,
};
@ -14,7 +13,11 @@ use dialogs::*;
use widgets::*;
use crate::{
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition},
app::{
self,
data_harvester::processes::ProcessHarvest,
layout_manager::{BottomLayout, BottomWidgetType},
},
constants::*,
data_conversion::{ConvertedCpuData, ConvertedProcessData},
utils::error,
@ -40,7 +43,7 @@ pub struct DisplayableData {
// Not the final value
pub grouped_process_data: Vec<ConvertedProcessData>,
// What's actually displayed
pub finalized_process_data: Vec<ConvertedProcessData>,
pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>,
pub mem_label: String,
pub swap_label: String,
pub mem_data: Vec<(f64, f64)>,
@ -48,31 +51,110 @@ pub struct DisplayableData {
pub cpu_data: Vec<ConvertedCpuData>,
}
#[allow(dead_code)]
#[derive(Default)]
/// Handles the canvas' state. TODO: [OPT] implement this.
pub struct Painter {
pub colours: CanvasColours,
height: u16,
width: u16,
vertical_dialog_chunk: Vec<Rect>,
middle_dialog_chunk: Vec<Rect>,
vertical_chunks: Vec<Rect>,
middle_chunks: Vec<Rect>,
middle_divided_chunk_2: Vec<Rect>,
bottom_chunks: Vec<Rect>,
cpu_chunk: Vec<Rect>,
network_chunk: Vec<Rect>,
pub colours: CanvasColours,
pub styled_general_help_text: Vec<Text<'static>>,
pub styled_process_help_text: Vec<Text<'static>>,
pub styled_search_help_text: Vec<Text<'static>>,
styled_general_help_text: Vec<Text<'static>>,
styled_process_help_text: Vec<Text<'static>>,
styled_search_help_text: Vec<Text<'static>>,
is_mac_os: bool,
row_constraints: Vec<Constraint>,
col_constraints: Vec<Vec<Constraint>>,
col_row_constraints: Vec<Vec<Vec<Constraint>>>,
layout_constraints: Vec<Vec<Vec<Vec<Constraint>>>>,
widget_layout: BottomLayout,
}
impl Painter {
pub fn init(widget_layout: BottomLayout) -> Self {
// Now for modularity; we have to also initialize the base layouts!
// We want to do this ONCE and reuse; after this we can just construct
// based on the console size.
let mut row_constraints = Vec::new();
let mut col_constraints = Vec::new();
let mut col_row_constraints = Vec::new();
let mut layout_constraints = Vec::new();
widget_layout.rows.iter().for_each(|row| {
if row.canvas_handle_height {
row_constraints.push(Constraint::Length(0));
} else {
row_constraints.push(Constraint::Ratio(
row.row_height_ratio,
widget_layout.total_row_height_ratio,
));
}
let mut new_col_constraints = Vec::new();
let mut new_widget_constraints = Vec::new();
let mut new_col_row_constraints = Vec::new();
row.children.iter().for_each(|col| {
if col.canvas_handle_width {
new_col_constraints.push(Constraint::Length(0));
} else {
new_col_constraints
.push(Constraint::Ratio(col.col_width_ratio, row.total_col_ratio));
}
let mut new_new_col_row_constraints = Vec::new();
let mut new_new_widget_constraints = Vec::new();
col.children.iter().for_each(|col_row| {
if col_row.canvas_handle_height {
new_new_col_row_constraints.push(Constraint::Length(0));
} else if col_row.flex_grow {
new_new_col_row_constraints.push(Constraint::Min(0));
} else {
new_new_col_row_constraints.push(Constraint::Ratio(
col_row.col_row_height_ratio,
col.total_col_row_ratio,
));
}
let mut new_new_new_widget_constraints = Vec::new();
col_row.children.iter().for_each(|widget| {
if widget.canvas_handle_width {
new_new_new_widget_constraints.push(Constraint::Length(0));
} else if widget.flex_grow {
new_new_new_widget_constraints.push(Constraint::Min(0));
} else {
new_new_new_widget_constraints.push(Constraint::Ratio(
widget.width_ratio,
col_row.total_widget_ratio,
));
}
});
new_new_widget_constraints.push(new_new_new_widget_constraints);
});
new_col_row_constraints.push(new_new_col_row_constraints);
new_widget_constraints.push(new_new_widget_constraints);
});
col_row_constraints.push(new_col_row_constraints);
layout_constraints.push(new_widget_constraints);
col_constraints.push(new_col_constraints);
});
Painter {
colours: CanvasColours::default(),
height: 0,
width: 0,
styled_general_help_text: Vec::new(),
styled_process_help_text: Vec::new(),
styled_search_help_text: Vec::new(),
is_mac_os: false,
row_constraints,
col_constraints,
col_row_constraints,
layout_constraints,
widget_layout,
}
}
/// Must be run once before drawing, but after setting colours.
/// This is to set some remaining styles and text.
pub fn initialize(&mut self) {
pub fn complete_painter_init(&mut self) {
self.is_mac_os = cfg!(target_os = "macos");
if GENERAL_HELP_TEXT.len() > 1 {
@ -115,24 +197,26 @@ 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),
_ => {}
}
}
// 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: [FEATURE] Auto-resizing dialog sizes.
pub fn draw_data<B: Backend>(
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
) -> error::Result<()> {
use BottomWidgetType::*;
let terminal_size = terminal.size()?;
let current_height = terminal_size.height;
let current_width = terminal_size.width;
@ -231,55 +315,63 @@ impl Painter {
}
} else if app_state.is_expanded {
let rect = Layout::default()
.margin(1)
.margin(0)
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size());
match &app_state.current_widget_selected {
WidgetPosition::Cpu | WidgetPosition::BasicCpu | WidgetPosition::CpuLegend => {
let cpu_chunk = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints(
if app_state.app_config_fields.left_legend {
[Constraint::Percentage(15), Constraint::Percentage(85)]
} else {
[Constraint::Percentage(85), Constraint::Percentage(15)]
}
.as_ref(),
)
.split(rect[0]);
let legend_index = if app_state.app_config_fields.left_legend {
0
} else {
1
};
let graph_index = if app_state.app_config_fields.left_legend {
1
} else {
0
};
self.draw_cpu_graph(&mut f, app_state, cpu_chunk[graph_index]);
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
}
WidgetPosition::Mem | WidgetPosition::BasicMem => {
self.draw_memory_graph(&mut f, app_state, rect[0]);
}
WidgetPosition::Disk => {
self.draw_disk_table(&mut f, app_state, rect[0], true);
}
WidgetPosition::Temp => {
self.draw_temp_table(&mut f, app_state, rect[0], true);
}
WidgetPosition::Network
| WidgetPosition::BasicNet
| WidgetPosition::NetworkLegend => {
self.draw_network_graph(&mut f, app_state, rect[0]);
}
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
self.draw_process_and_search(&mut f, app_state, rect[0], true);
}
match &app_state.current_widget.widget_type {
Cpu => self.draw_cpu(
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id,
),
CpuLegend => self.draw_cpu(
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id - 1,
),
Mem | BasicMem => self.draw_memory_graph(
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id,
),
Disk => self.draw_disk_table(
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
Temp => self.draw_temp_table(
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
Net => self.draw_network_graph(
&mut f,
app_state,
rect[0],
app_state.current_widget.widget_id,
),
Proc => self.draw_process_and_search(
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
ProcSearch => self.draw_process_and_search(
&mut f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id - 1,
),
_ => {}
}
} else if app_state.app_config_fields.use_basic_mode {
// Basic mode. This basically removes all graphs but otherwise
@ -309,112 +401,144 @@ impl Painter {
.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_cpu(&mut f, app_state, vertical_chunks[0], 1);
self.draw_basic_memory(&mut f, app_state, middle_chunks[0], 2);
self.draw_basic_network(&mut f, app_state, middle_chunks[1], 3);
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,
);
if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
let widget_id = basic_table_widget_state.currently_displayed_widget_id;
match basic_table_widget_state.currently_displayed_widget_type {
Disk => self.draw_disk_table(
&mut f,
app_state,
vertical_chunks[4],
false,
widget_id,
),
Proc => self.draw_process_and_search(
&mut f,
app_state,
vertical_chunks[4],
false,
widget_id,
),
Temp => self.draw_temp_table(
&mut f,
app_state,
vertical_chunks[4],
false,
widget_id,
),
_ => {}
}
}
} else {
let vertical_chunks = Layout::default()
// Draws using the passed in (or default) layout. NOT basic so far.
let row_draw_locs = Layout::default()
.margin(0)
.constraints(self.row_constraints.as_ref())
.direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(37),
Constraint::Percentage(33),
]
.as_ref(),
)
.split(f.size());
let col_draw_locs = self
.col_constraints
.iter()
.enumerate()
.map(|(itx, col_constraint)| {
Layout::default()
.constraints(col_constraint.as_ref())
.direction(Direction::Horizontal)
.split(row_draw_locs[itx])
})
.collect::<Vec<_>>();
let col_row_draw_locs = self
.col_row_constraints
.iter()
.enumerate()
.map(|(col_itx, col_row_constraints)| {
col_row_constraints
.iter()
.enumerate()
.map(|(itx, col_row_constraint)| {
Layout::default()
.constraints(col_row_constraint.as_ref())
.direction(Direction::Vertical)
.split(col_draw_locs[col_itx][itx])
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let middle_chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)].as_ref())
.split(vertical_chunks[1]);
// Now... draw!
self.layout_constraints.iter().enumerate().for_each(
|(row_itx, col_constraint_vec)| {
col_constraint_vec.iter().enumerate().for_each(
|(col_itx, col_row_constraint_vec)| {
col_row_constraint_vec.iter().enumerate().for_each(
|(col_row_itx, widget_constraints)| {
let widget_draw_locs = Layout::default()
.constraints(widget_constraints.as_ref())
.direction(Direction::Horizontal)
.split(
col_row_draw_locs[row_itx][col_itx][col_row_itx],
);
let middle_divided_chunk_2 = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(middle_chunks[1]);
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(vertical_chunks[2]);
// Component specific chunks
let cpu_chunk = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints(
if app_state.app_config_fields.left_legend {
[Constraint::Percentage(15), Constraint::Percentage(85)]
} else {
[Constraint::Percentage(85), Constraint::Percentage(15)]
}
.as_ref(),
)
.split(vertical_chunks[0]);
let network_chunk = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints(
if (bottom_chunks[0].height as f64 * 0.25) as u16 >= 4 {
[Constraint::Percentage(75), Constraint::Percentage(25)]
} else {
let required = if bottom_chunks[0].height < 10 {
bottom_chunks[0].height / 2
} else {
5
};
let remaining = bottom_chunks[0].height - required;
[Constraint::Length(remaining), Constraint::Length(required)]
}
.as_ref(),
)
.split(bottom_chunks[0]);
// Default chunk index based on left or right legend setting
let legend_index = if app_state.app_config_fields.left_legend {
0
} else {
1
};
let graph_index = if app_state.app_config_fields.left_legend {
1
} else {
0
};
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_memory_graph(&mut f, app_state, middle_chunks[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_temp_table(&mut f, app_state, middle_divided_chunk_2[0], true);
self.draw_disk_table(&mut f, app_state, middle_divided_chunk_2[1], true);
self.draw_process_and_search(&mut f, app_state, bottom_chunks[1], true);
for (widget_itx, widget) in self.widget_layout.rows[row_itx]
.children[col_itx]
.children[col_row_itx]
.children
.iter()
.enumerate()
{
match widget.widget_type {
Empty => {}
Cpu => self.draw_cpu(
&mut f,
app_state,
widget_draw_locs[widget_itx],
widget.widget_id,
),
Mem => self.draw_memory_graph(
&mut f,
app_state,
widget_draw_locs[widget_itx],
widget.widget_id,
),
Net => self.draw_network(
&mut f,
app_state,
widget_draw_locs[widget_itx],
widget.widget_id,
),
Temp => self.draw_temp_table(
&mut f,
app_state,
widget_draw_locs[widget_itx],
true,
widget.widget_id,
),
Disk => self.draw_disk_table(
&mut f,
app_state,
widget_draw_locs[widget_itx],
true,
widget.widget_id,
),
Proc => self.draw_process_and_search(
&mut f,
app_state,
widget_draw_locs[widget_itx],
true,
widget.widget_id,
),
_ => {}
}
}
},
);
},
);
},
);
}
})?;

View File

@ -27,7 +27,7 @@ impl KillDialog for Painter {
if let Some(to_kill_processes) = app_state.get_to_delete_processes() {
if let Some(first_pid) = to_kill_processes.1.first() {
let dd_text = vec![
if app_state.is_grouped() {
if app_state.is_grouped(app_state.current_widget.widget_id) {
if to_kill_processes.1.len() != 1 {
Text::raw(format!(
"\nKill {} processes with the name {}?",

View File

@ -1,7 +1,7 @@
use std::cmp::max;
use crate::{
app::{App, WidgetPosition},
app::{layout_manager::BottomWidgetType, App},
canvas::Painter,
};
@ -23,27 +23,19 @@ impl BasicTableArrows for Painter {
&self, f: &mut Frame<'_, B>, app_state: &mut 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)
let (left_table, right_table) =
if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
match basic_table_widget_state.currently_displayed_widget_type {
BottomWidgetType::Proc | BottomWidgetType::ProcSearch => {
(BottomWidgetType::Temp, BottomWidgetType::Disk)
}
BottomWidgetType::Disk => (BottomWidgetType::Proc, BottomWidgetType::Temp),
BottomWidgetType::Temp => (BottomWidgetType::Disk, BottomWidgetType::Proc),
_ => (BottomWidgetType::Disk, BottomWidgetType::Temp),
}
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),
}
};
} else {
(BottomWidgetType::Disk, BottomWidgetType::Temp)
};
let left_name = left_table.get_pretty_name();
let right_name = right_table.get_pretty_name();

View File

@ -1,7 +1,7 @@
use std::cmp::{max, min};
use crate::{
app::{App, WidgetPosition},
app::App,
canvas::{drawing_utils::*, Painter},
constants::*,
data_conversion::ConvertedCpuData,
@ -15,12 +15,14 @@ use tui::{
};
pub trait CpuBasicWidget {
fn draw_basic_cpu<B: Backend>(&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect);
fn draw_basic_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl CpuBasicWidget for Painter {
fn draw_basic_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
@ -34,7 +36,7 @@ impl CpuBasicWidget for Painter {
// 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 {
if app_state.current_widget.widget_id == widget_id {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)

View File

@ -3,7 +3,7 @@ use std::borrow::Cow;
use std::cmp::max;
use crate::{
app::{App, WidgetPosition},
app::App,
canvas::{
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
Painter,
@ -14,7 +14,7 @@ use crate::{
use tui::{
backend::Backend,
layout::{Constraint, Rect},
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget},
};
@ -33,259 +33,307 @@ lazy_static! {
}
pub trait CpuGraphWidget {
fn draw_cpu_graph<B: Backend>(&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect);
fn draw_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
fn draw_cpu_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
fn draw_cpu_legend<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl CpuGraphWidget for Painter {
fn draw_cpu_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
fn draw_cpu<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
if draw_loc.width as f64 * 0.15 <= 6.0 {
// Skip drawing legend
if app_state.current_widget.widget_id == (widget_id + 1) {
if app_state.app_config_fields.left_legend {
app_state.move_widget_selection_right();
} else {
app_state.move_widget_selection_left();
}
}
self.draw_cpu_graph(f, app_state, draw_loc, widget_id);
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
cpu_widget_state.is_legend_hidden = true;
}
} else {
let (graph_index, legend_index, constraints) =
if app_state.app_config_fields.left_legend {
(
1,
0,
[Constraint::Percentage(15), Constraint::Percentage(85)],
)
} else {
(
0,
1,
[Constraint::Percentage(85), Constraint::Percentage(15)],
)
};
let display_time_labels = [
format!("{}s", app_state.cpu_state.current_display_time / 1000),
"0s".to_string(),
];
let partitioned_draw_loc = Layout::default()
.margin(0)
.direction(Direction::Horizontal)
.constraints(constraints.as_ref())
.split(draw_loc);
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& app_state.cpu_state.autohide_timer.is_none())
{
Axis::default().bounds([0.0, app_state.cpu_state.current_display_time as f64])
} else if let Some(time) = app_state.cpu_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
self.draw_cpu_graph(f, app_state, partitioned_draw_loc[graph_index], widget_id);
self.draw_cpu_legend(
f,
app_state,
partitioned_draw_loc[legend_index],
widget_id + 1,
);
}
}
fn draw_cpu_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&widget_id) {
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let display_time_labels = [
format!("{}s", cpu_widget_state.current_display_time / 1000),
"0s".to_string(),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& cpu_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
} else if let Some(time) = cpu_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
} else {
cpu_widget_state.autohide_timer = None;
Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
}
} else {
Axis::default()
.bounds([0.0, app_state.cpu_state.current_display_time as f64])
.bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
} else {
app_state.cpu_state.autohide_timer = None;
Axis::default().bounds([0.0, app_state.cpu_state.current_display_time as f64])
}
} else {
Axis::default()
.bounds([0.0, app_state.cpu_state.current_display_time as f64])
};
// Note this is offset as otherwise the 0 value is not drawn!
let y_axis = Axis::default()
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
};
.bounds([-0.5, 100.5])
.labels(&["0%", "100%"]);
// Note this is offset as otherwise the 0 value is not drawn!
let y_axis = Axis::default()
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.bounds([-0.5, 100.5])
.labels(&["0%", "100%"]);
let dataset_vector: Vec<Dataset<'_>> = cpu_data
.iter()
.enumerate()
.rev()
.filter_map(|(itx, cpu)| {
if app_state.cpu_state.core_show_vec[itx] {
Some(
Dataset::default()
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(
if app_state.app_config_fields.show_average_cpu && itx == 0 {
let use_dot = app_state.app_config_fields.use_dot;
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let dataset_vector: Vec<Dataset<'_>> = cpu_data
.iter()
.enumerate()
.rev()
.filter_map(|(itx, cpu)| {
if cpu_widget_state.core_show_vec[itx] {
Some(
Dataset::default()
.marker(if use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(if show_avg_cpu && itx == 0 {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles
[itx % self.colours.cpu_colour_styles.len()]
},
)
.data(&cpu.cpu_data[..]),
)
} else {
None
}
})
.collect();
let title = if app_state.is_expanded && !app_state.cpu_state.is_showing_tray {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title =
format!(" CPU ─{}─ Esc to go back ", "".repeat(repeat_num as usize));
result_title
} else {
" CPU ".to_string()
};
let border_style = match app_state.current_widget_selected {
WidgetPosition::Cpu => self.colours.highlighted_border_style,
_ => self.colours.border_style,
};
Chart::default()
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_style
})
.data(&cpu.cpu_data[..]),
)
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_style),
)
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&dataset_vector)
.render(f, draw_loc);
None
}
})
.collect();
let title = if app_state.is_expanded && !cpu_widget_state.is_showing_tray {
const TITLE_BASE: &str = " CPU ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title =
format!(" CPU ─{}─ Esc to go back ", "".repeat(repeat_num as usize));
result_title
} else {
" CPU ".to_string()
};
let border_style = if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
Chart::default()
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_style),
)
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&dataset_vector)
.render(f, draw_loc);
}
}
fn draw_cpu_legend<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
if let Some(cpu_widget_state) = app_state.cpu_state.widget_states.get_mut(&(widget_id - 1))
{
cpu_widget_state.is_legend_hidden = false;
let cpu_data: &mut [ConvertedCpuData] = &mut app_state.canvas_data.cpu_data;
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let start_position = get_start_position(
num_rows,
&app_state.app_scroll_positions.scroll_direction,
&mut app_state
.app_scroll_positions
.cpu_scroll_state
.previous_scroll_position,
app_state
.app_scroll_positions
.cpu_scroll_state
.current_scroll_position,
app_state.is_resized,
);
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let start_position = get_start_position(
num_rows,
&cpu_widget_state.scroll_state.scroll_direction,
&mut cpu_widget_state.scroll_state.previous_scroll_position,
cpu_widget_state.scroll_state.current_scroll_position,
app_state.is_resized,
);
let sliced_cpu_data = &cpu_data[start_position as usize..];
let sliced_cpu_data = &cpu_data[start_position as usize..];
let mut offset_scroll_index = (app_state
.app_scroll_positions
.cpu_scroll_state
.current_scroll_position
- start_position) as usize;
let cpu_rows = sliced_cpu_data.iter().enumerate().filter_map(|(itx, cpu)| {
let cpu_string_row: Vec<Cow<'_, str>> = if app_state.cpu_state.is_showing_tray {
vec![
Cow::Borrowed(&cpu.cpu_name),
if app_state.cpu_state.core_show_vec[itx + start_position as usize] {
"[*]".into()
} else {
"[ ]".into()
},
]
} else if app_state.app_config_fields.show_disabled_data
|| app_state.cpu_state.core_show_vec[itx]
{
vec![
Cow::Borrowed(&cpu.cpu_name),
Cow::Borrowed(&cpu.legend_value),
]
} else {
Vec::new()
};
let mut offset_scroll_index =
(cpu_widget_state.scroll_state.current_scroll_position - start_position) as usize;
let show_disabled_data = app_state.app_config_fields.show_disabled_data;
let current_widget_id = app_state.current_widget.widget_id;
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
if cpu_string_row.is_empty() {
offset_scroll_index += 1;
None
} else {
Some(Row::StyledData(
cpu_string_row.into_iter(),
match app_state.current_widget_selected {
WidgetPosition::CpuLegend => {
let cpu_rows = sliced_cpu_data.iter().enumerate().filter_map(|(itx, cpu)| {
let cpu_string_row: Vec<Cow<'_, str>> = if cpu_widget_state.is_showing_tray {
vec![
Cow::Borrowed(&cpu.cpu_name),
if cpu_widget_state.core_show_vec[itx + start_position as usize] {
"[*]".into()
} else {
"[ ]".into()
},
]
} else if show_disabled_data || cpu_widget_state.core_show_vec[itx] {
vec![
Cow::Borrowed(&cpu.cpu_name),
Cow::Borrowed(&cpu.legend_value),
]
} else {
Vec::new()
};
if cpu_string_row.is_empty() {
offset_scroll_index += 1;
None
} else {
Some(Row::StyledData(
cpu_string_row.into_iter(),
if current_widget_id == widget_id {
if itx == offset_scroll_index {
self.colours.currently_selected_text_style
} else if app_state.app_config_fields.show_average_cpu && itx == 0 {
} else if show_avg_cpu && itx == 0 {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles[itx
+ start_position as usize
% self.colours.cpu_colour_styles.len()]
}
}
_ => {
if app_state.app_config_fields.show_average_cpu && itx == 0 {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles[itx
+ start_position as usize
% self.colours.cpu_colour_styles.len()]
}
}
},
))
}
});
} else if show_avg_cpu && itx == 0 {
self.colours.avg_colour_style
} else {
self.colours.cpu_colour_styles[itx
+ start_position as usize % self.colours.cpu_colour_styles.len()]
},
))
}
});
// Calculate widths
let width = f64::from(draw_loc.width);
let width_ratios = vec![0.5, 0.5];
// Calculate widths
let width = f64::from(draw_loc.width);
let width_ratios = vec![0.5, 0.5];
let variable_intrinsic_results = get_variable_intrinsic_widths(
width as u16,
&width_ratios,
if app_state.cpu_state.is_showing_tray {
&CPU_SELECT_LEGEND_HEADER_LENS
} else {
&CPU_LEGEND_HEADER_LENS
},
);
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
let title = if app_state.cpu_state.is_showing_tray {
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 variable_intrinsic_results = get_variable_intrinsic_widths(
width as u16,
&width_ratios,
if cpu_widget_state.is_showing_tray {
&CPU_SELECT_LEGEND_HEADER_LENS
} else {
&CPU_LEGEND_HEADER_LENS
},
);
let result_title = format!("{} Esc to close ", "".repeat(repeat_num as usize));
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
result_title
} else {
"".to_string()
};
let title = if cpu_widget_state.is_showing_tray {
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 result_title = format!("{} Esc to close ", "".repeat(repeat_num as usize));
let title_and_border_style = match app_state.current_widget_selected {
WidgetPosition::CpuLegend => self.colours.highlighted_border_style,
_ => self.colours.border_style,
};
// Draw
Table::new(
if app_state.cpu_state.is_showing_tray {
CPU_SELECT_LEGEND_HEADER
result_title
} else {
CPU_LEGEND_HEADER
}
.iter(),
cpu_rows,
)
.block(
Block::default()
.title(&title)
.title_style(title_and_border_style)
.borders(Borders::ALL)
.border_style(title_and_border_style),
)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, draw_loc);
"".to_string()
};
let title_and_border_style = if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
// Draw
Table::new(
if cpu_widget_state.is_showing_tray {
CPU_SELECT_LEGEND_HEADER
} else {
CPU_LEGEND_HEADER
}
.iter(),
cpu_rows,
)
.block(
Block::default()
.title(&title)
.title_style(title_and_border_style)
.borders(Borders::ALL)
.border_style(title_and_border_style),
)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, draw_loc);
}
}
}

View File

@ -8,7 +8,7 @@ use tui::{
};
use crate::{
app::{self, WidgetPosition},
app::{self},
canvas::{
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
Painter,
@ -28,42 +28,38 @@ lazy_static! {
pub trait DiskTableWidget {
fn draw_disk_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
}
impl DiskTableWidget for Painter {
fn draw_disk_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
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 start_position = get_start_position(
num_rows,
&app_state.app_scroll_positions.scroll_direction,
&mut app_state
.app_scroll_positions
.disk_scroll_state
.previous_scroll_position,
app_state
.app_scroll_positions
.disk_scroll_state
.current_scroll_position,
app_state.is_resized,
);
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
let disk_data: &mut [Vec<String>] = &mut app_state.canvas_data.disk_data;
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let start_position = get_start_position(
num_rows,
&disk_widget_state.scroll_state.scroll_direction,
&mut disk_widget_state.scroll_state.previous_scroll_position,
disk_widget_state.scroll_state.current_scroll_position,
app_state.is_resized,
);
let sliced_vec = &disk_data[start_position as usize..];
let mut disk_counter: i64 = 0;
let sliced_vec = &mut disk_data[start_position as usize..];
let mut disk_counter: i64 = 0;
let disk_rows = sliced_vec.iter().map(|disk| {
Row::StyledData(
disk.iter(),
match app_state.current_widget_selected {
WidgetPosition::Disk => {
let current_widget_id = app_state.current_widget.widget_id;
let disk_rows = sliced_vec.iter().map(|disk| {
Row::StyledData(
disk.iter(),
if current_widget_id == widget_id
&& disk_widget_state.scroll_state.current_scroll_position >= start_position
{
if disk_counter as u64
== app_state
.app_scroll_positions
.disk_scroll_state
.current_scroll_position
== disk_widget_state.scroll_state.current_scroll_position
- start_position
{
disk_counter = -1;
@ -74,82 +70,84 @@ impl DiskTableWidget for Painter {
}
self.colours.text_style
}
}
_ => self.colours.text_style,
},
)
});
} else {
self.colours.text_style
},
)
});
// Calculate widths
// TODO: [PRETTY] Ellipsis on strings?
let width = f64::from(draw_loc.width);
let width_ratios = [0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13];
let variable_intrinsic_results =
get_variable_intrinsic_widths(width as u16, &width_ratios, &DISK_HEADERS_LENS);
let intrinsic_widths = &variable_intrinsic_results.0[0..variable_intrinsic_results.1];
// Calculate widths
// TODO: [PRETTY] Ellipsis on strings?
let width = f64::from(draw_loc.width);
let width_ratios = [0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13];
let variable_intrinsic_results =
get_variable_intrinsic_widths(width as u16, &width_ratios, &DISK_HEADERS_LENS);
let intrinsic_widths = &variable_intrinsic_results.0[0..variable_intrinsic_results.1];
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Disk ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Disk ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else if app_state.app_config_fields.use_basic_mode {
String::new()
} else {
" Disk ".to_string()
};
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Disk ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Disk ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else if app_state.app_config_fields.use_basic_mode {
String::new()
} else {
" Disk ".to_string()
};
let disk_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
match app_state.current_widget_selected {
WidgetPosition::Disk => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
WidgetPosition::Disk => self.colours.highlighted_border_style,
_ => self.colours.border_style,
})
} else {
match app_state.current_widget_selected {
WidgetPosition::Disk => Block::default()
let border_and_title_style = if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let disk_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_and_title_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_and_title_style)
} else if app_state.current_widget.widget_id == widget_id {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style),
_ => Block::default().borders(Borders::NONE),
}
};
.border_style(self.colours.highlighted_border_style)
} else {
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);
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.horizontal_margin(
if app_state.current_widget.widget_id == widget_id || draw_border {
0
} else {
1
},
)
.direction(Direction::Horizontal)
.split(draw_loc);
// Draw!
Table::new(DISK_HEADERS.iter(), disk_rows)
.block(disk_block)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, margined_draw_loc[0]);
// Draw!
Table::new(DISK_HEADERS.iter(), disk_rows)
.block(disk_block)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, margined_draw_loc[0]);
}
}
}

View File

@ -1,7 +1,7 @@
use std::cmp::max;
use crate::{
app::{App, WidgetPosition},
app::App,
canvas::{drawing_utils::*, Painter},
constants::*,
};
@ -15,13 +15,13 @@ use tui::{
pub trait MemBasicWidget {
fn draw_basic_memory<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl MemBasicWidget for Painter {
fn draw_basic_memory<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
@ -31,7 +31,7 @@ impl MemBasicWidget for Painter {
.horizontal_margin(1)
.split(draw_loc);
if let WidgetPosition::BasicMem = app_state.current_widget_selected {
if app_state.current_widget.widget_id == widget_id {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)

View File

@ -1,10 +1,6 @@
use std::cmp::max;
use crate::{
app::{App, WidgetPosition},
canvas::Painter,
constants::*,
};
use crate::{app::App, canvas::Painter, constants::*};
use tui::{
backend::Backend,
@ -15,109 +11,112 @@ use tui::{
pub trait MemGraphWidget {
fn draw_memory_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl MemGraphWidget for Painter {
fn draw_memory_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
let display_time_labels = [
format!("{}s", app_state.mem_state.current_display_time / 1000),
"0s".to_string(),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& app_state.mem_state.autohide_timer.is_none())
{
Axis::default().bounds([0.0, app_state.mem_state.current_display_time as f64])
} else if let Some(time) = app_state.mem_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
let display_time_labels = [
format!("{}s", mem_widget_state.current_display_time / 1000),
"0s".to_string(),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& mem_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0])
} else if let Some(time) = mem_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([-(mem_widget_state.current_display_time as f64), 0.0])
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
} else {
mem_widget_state.autohide_timer = None;
Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0])
}
} else {
Axis::default()
.bounds([0.0, app_state.mem_state.current_display_time as f64])
.bounds([-(mem_widget_state.current_display_time as f64), 0.0])
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
} else {
app_state.mem_state.autohide_timer = None;
Axis::default().bounds([0.0, app_state.mem_state.current_display_time as f64])
}
} else {
Axis::default()
.bounds([0.0, app_state.mem_state.current_display_time as f64])
};
// Offset as the zero value isn't drawn otherwise...
let y_axis: Axis<'_, &str> = Axis::default()
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
};
.bounds([-0.5, 100.5])
.labels(&["0%", "100%"]);
// Offset as the zero value isn't drawn otherwise...
let y_axis: Axis<'_, &str> = Axis::default()
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.bounds([-0.5, 100.5])
.labels(&["0%", "100%"]);
let mem_canvas_vec: Vec<Dataset<'_>> = vec![
Dataset::default()
.name(&app_state.canvas_data.mem_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.ram_style)
.data(&mem_data),
Dataset::default()
.name(&app_state.canvas_data.swap_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.swap_style)
.data(&swap_data),
];
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Memory ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Memory ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else {
" Memory ".to_string()
};
Chart::default()
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
self.colours.highlighted_border_style
let mem_canvas_vec: Vec<Dataset<'_>> = vec![
Dataset::default()
.name(&app_state.canvas_data.mem_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
self.colours.widget_title_style
Marker::Braille
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
WidgetPosition::Mem => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&mem_canvas_vec)
.render(f, draw_loc);
.style(self.colours.ram_style)
.data(&mem_data),
Dataset::default()
.name(&app_state.canvas_data.swap_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.swap_style)
.data(&swap_data),
];
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Memory ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Memory ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else {
" Memory ".to_string()
};
Chart::default()
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
self.colours.highlighted_border_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&mem_canvas_vec)
.render(f, draw_loc);
}
}
}

View File

@ -1,8 +1,4 @@
use crate::{
app::{App, WidgetPosition},
canvas::Painter,
constants::*,
};
use crate::{app::App, canvas::Painter, constants::*};
use tui::{
backend::Backend,
@ -13,13 +9,13 @@ use tui::{
pub trait NetworkBasicWidget {
fn draw_basic_network<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl NetworkBasicWidget for Painter {
fn draw_basic_network<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
@ -38,7 +34,7 @@ impl NetworkBasicWidget for Painter {
.horizontal_margin(1)
.split(divided_loc[1]);
if let WidgetPosition::BasicNet = app_state.current_widget_selected {
if app_state.current_widget.widget_id == widget_id {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)

View File

@ -2,14 +2,14 @@ use lazy_static::lazy_static;
use std::cmp::max;
use crate::{
app::{App, WidgetPosition},
app::App,
canvas::{drawing_utils::get_variable_intrinsic_widths, Painter},
constants::*,
};
use tui::{
backend::Backend,
layout::{Constraint, Rect},
layout::{Constraint, Direction, Layout, Rect},
terminal::Frame,
widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget},
};
@ -24,129 +24,156 @@ lazy_static! {
}
pub trait NetworkGraphWidget {
fn draw_network<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
fn draw_network_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
fn draw_network_labels<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
);
}
impl NetworkGraphWidget for Painter {
fn draw_network_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
fn draw_network<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx;
let network_chunk = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints(
[
Constraint::Length(max(draw_loc.height as i64 - 5, 0) as u16),
Constraint::Length(5),
]
.as_ref(),
)
.split(draw_loc);
let display_time_labels = [
format!("{}s", app_state.net_state.current_display_time / 1000),
"0s".to_string(),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& app_state.net_state.autohide_timer.is_none())
{
Axis::default().bounds([0.0, app_state.net_state.current_display_time as f64])
} else if let Some(time) = app_state.net_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
self.draw_network_graph(f, app_state, network_chunk[0], widget_id);
self.draw_network_labels(f, app_state, network_chunk[1], widget_id);
}
fn draw_network_graph<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) {
let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx;
let display_time_labels = [
format!("{}s", network_widget_state.current_display_time / 1000),
"0s".to_string(),
];
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& network_widget_state.autohide_timer.is_none())
{
Axis::default().bounds([-(network_widget_state.current_display_time as f64), 0.0])
} else if let Some(time) = network_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
.bounds([-(network_widget_state.current_display_time as f64), 0.0])
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
} else {
network_widget_state.autohide_timer = None;
Axis::default()
.bounds([-(network_widget_state.current_display_time as f64), 0.0])
}
} else {
Axis::default()
.bounds([0.0, app_state.net_state.current_display_time as f64])
.bounds([-(network_widget_state.current_display_time as f64), 0.0])
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
} else {
app_state.net_state.autohide_timer = None;
Axis::default().bounds([0.0, app_state.net_state.current_display_time as f64])
}
} else {
Axis::default()
.bounds([0.0, app_state.net_state.current_display_time as f64])
};
// 0 is offset.
let y_axis: Axis<'_, &str> = Axis::default()
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.labels(&display_time_labels)
};
.bounds([-0.5, 30_f64])
.labels(&["0B", "1KiB", "1MiB", "1GiB"]);
// 0 is offset.
let y_axis: Axis<'_, &str> = Axis::default()
.style(self.colours.graph_style)
.labels_style(self.colours.graph_style)
.bounds([-0.5, 30_f64])
.labels(&["0B", "1KiB", "1MiB", "1GiB"]);
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Network ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Network ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Network ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Network ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else {
" Network ".to_string()
};
result_title
} else {
" Network ".to_string()
};
Chart::default()
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
self.colours.highlighted_border_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
WidgetPosition::Network => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&[
Dataset::default()
.name(&format!("RX: {:7}", app_state.canvas_data.rx_display))
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.rx_style)
.data(&network_data_rx),
Dataset::default()
.name(&format!("TX: {:7}", app_state.canvas_data.tx_display))
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.tx_style)
.data(&network_data_tx),
Dataset::default()
.name(&format!(
"Total RX: {:7}",
app_state.canvas_data.total_rx_display
))
.style(self.colours.total_rx_style),
Dataset::default()
.name(&format!(
"Total TX: {:7}",
app_state.canvas_data.total_tx_display
))
.style(self.colours.total_tx_style),
])
.render(f, draw_loc);
Chart::default()
.block(
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
self.colours.highlighted_border_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
}),
)
.x_axis(x_axis)
.y_axis(y_axis)
.datasets(&[
Dataset::default()
.name(&format!("RX: {:7}", app_state.canvas_data.rx_display))
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.rx_style)
.data(&network_data_rx),
Dataset::default()
.name(&format!("TX: {:7}", app_state.canvas_data.tx_display))
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.tx_style)
.data(&network_data_tx),
Dataset::default()
.name(&format!(
"Total RX: {:7}",
app_state.canvas_data.total_rx_display
))
.style(self.colours.total_rx_style),
Dataset::default()
.name(&format!(
"Total TX: {:7}",
app_state.canvas_data.total_tx_display
))
.style(self.colours.total_tx_style),
])
.render(f, draw_loc);
}
}
fn draw_network_labels<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let rx_display = &app_state.canvas_data.rx_display;
let tx_display = &app_state.canvas_data.tx_display;
@ -176,9 +203,10 @@ impl NetworkGraphWidget for Painter {
// Draw
Table::new(NETWORK_HEADERS.iter(), mapped_network)
.block(Block::default().borders(Borders::ALL).border_style(
match app_state.current_widget_selected {
WidgetPosition::Network => self.colours.highlighted_border_style,
_ => self.colours.border_style,
if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
},
))
.header_style(self.colours.table_header_style)

View File

@ -1,7 +1,7 @@
use std::cmp::{max, min};
use crate::{
app::{self, App, WidgetPosition},
app::{self, App},
canvas::{
drawing_utils::{
get_search_start_position, get_start_position, get_variable_intrinsic_widths,
@ -9,7 +9,6 @@ use crate::{
Painter,
},
constants::*,
data_conversion::ConvertedProcessData,
};
use tui::{
@ -25,260 +24,287 @@ use unicode_width::UnicodeWidthStr;
pub trait ProcessTableWidget {
fn draw_process_and_search<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
fn draw_processes_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
fn draw_search_field<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
}
impl ProcessTableWidget for Painter {
fn draw_process_and_search<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
let search_width = if draw_border { 5 } else { 3 };
if let Some(process_widget_state) = app_state.proc_state.widget_states.get(&widget_id) {
let search_width = if draw_border { 5 } else { 3 };
if process_widget_state.is_search_enabled() {
let processes_chunk = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(search_width)].as_ref())
.split(draw_loc);
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);
self.draw_processes_table(f, app_state, processes_chunk[0], draw_border, widget_id);
self.draw_search_field(
f,
app_state,
processes_chunk[1],
draw_border,
widget_id + 1,
);
} else {
self.draw_processes_table(f, app_state, draw_loc, draw_border, widget_id);
}
}
}
fn draw_processes_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
let process_data: &[ConvertedProcessData] = &app_state.canvas_data.finalized_process_data;
if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&widget_id) {
if let Some(process_data) = &app_state
.canvas_data
.finalized_process_data_map
.get(&widget_id)
{
// Admittedly this is kinda a hack... but we need to:
// * Scroll
// * Show/hide elements based on scroll position
//
// As such, we use a process_counter to know when we've
// hit the process we've currently scrolled to.
// We also need to move the list - we can
// do so by hiding some elements!
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let is_on_widget = widget_id == app_state.current_widget.widget_id;
// Admittedly this is kinda a hack... but we need to:
// * Scroll
// * Show/hide elements based on scroll position
//
// As such, we use a process_counter to know when we've
// hit the process we've currently scrolled to.
// We also need to move the list - we can
// do so by hiding some elements!
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let position = get_start_position(
num_rows,
&proc_widget_state.scroll_state.scroll_direction,
&mut proc_widget_state.scroll_state.previous_scroll_position,
proc_widget_state.scroll_state.current_scroll_position,
app_state.is_resized,
);
let position = get_start_position(
num_rows,
&app_state.app_scroll_positions.scroll_direction,
&mut app_state
.app_scroll_positions
.process_scroll_state
.previous_scroll_position,
app_state
.app_scroll_positions
.process_scroll_state
.current_scroll_position,
app_state.is_resized,
);
// Sanity check
let start_position = if position >= process_data.len() as u64 {
std::cmp::max(0, process_data.len() as i64 - 1) as u64
} else {
position
};
let sliced_vec = &process_data[start_position as usize..];
let mut process_counter: i64 = 0;
// Draw!
let process_rows = sliced_vec.iter().map(|process| {
let stringified_process_vec: Vec<String> = vec![
if app_state.is_grouped() {
process.group_pids.len().to_string()
// Sanity check
let start_position = if position >= process_data.len() as u64 {
std::cmp::max(0, process_data.len() as i64 - 1) as u64
} else {
process.pid.to_string()
},
process.name.clone(),
format!("{:.1}%", process.cpu_usage),
format!("{:.1}%", process.mem_usage),
];
Row::StyledData(
stringified_process_vec.into_iter(),
match app_state.current_widget_selected {
WidgetPosition::Process => {
if process_counter as u64
== app_state
.app_scroll_positions
.process_scroll_state
.current_scroll_position
- start_position
{
process_counter = -1;
self.colours.currently_selected_text_style
position
};
let sliced_vec = &process_data[start_position as usize..];
let mut process_counter: i64 = 0;
// Draw!
let process_rows = sliced_vec.iter().map(|process| {
let stringified_process_vec: Vec<String> = vec![
if proc_widget_state.is_grouped {
process.group_pids.len().to_string()
} else {
if process_counter >= 0 {
process_counter += 1;
process.pid.to_string()
},
process.name.clone(),
format!("{:.1}%", process.cpu_usage),
format!("{:.1}%", process.mem_usage),
];
Row::StyledData(
stringified_process_vec.into_iter(),
if is_on_widget {
if process_counter as u64
== proc_widget_state.scroll_state.current_scroll_position
- start_position
{
process_counter = -1;
self.colours.currently_selected_text_style
} else {
if process_counter >= 0 {
process_counter += 1;
}
self.colours.text_style
}
} else {
self.colours.text_style
}
}
_ => self.colours.text_style,
},
)
});
},
)
});
use app::data_harvester::processes::ProcessSorting;
let mut pid_or_name = if app_state.is_grouped() {
"Count"
} else {
"PID(p)"
}
.to_string();
let mut name = "Name(n)".to_string();
let mut cpu = "CPU%(c)".to_string();
let mut mem = "Mem%(m)".to_string();
use app::data_harvester::processes::ProcessSorting;
let mut pid_or_name = if proc_widget_state.is_grouped {
"Count"
} else {
"PID(p)"
}
.to_string();
let mut name = "Name(n)".to_string();
let mut cpu = "CPU%(c)".to_string();
let mut mem = "Mem%(m)".to_string();
let direction_val = if app_state.process_sorting_reverse {
"".to_string()
} else {
"".to_string()
};
let direction_val = if proc_widget_state.process_sorting_reverse {
"".to_string()
} else {
"".to_string()
};
match app_state.process_sorting_type {
ProcessSorting::CPU => cpu += &direction_val,
ProcessSorting::MEM => mem += &direction_val,
ProcessSorting::PID => pid_or_name += &direction_val,
ProcessSorting::NAME => name += &direction_val,
};
match proc_widget_state.process_sorting_type {
ProcessSorting::CPU => cpu += &direction_val,
ProcessSorting::MEM => mem += &direction_val,
ProcessSorting::PID => pid_or_name += &direction_val,
ProcessSorting::NAME => name += &direction_val,
};
let process_headers = [pid_or_name, name, cpu, mem];
let process_headers_lens: Vec<usize> = process_headers
.iter()
.map(|entry| entry.len())
.collect::<Vec<_>>();
let process_headers = [pid_or_name, name, cpu, mem];
let process_headers_lens: Vec<usize> = process_headers
.iter()
.map(|entry| entry.len())
.collect::<Vec<_>>();
// Calculate widths
let width = f64::from(draw_loc.width);
let width_ratios = [0.2, 0.4, 0.2, 0.2];
let variable_intrinsic_results =
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 title = if draw_border {
if app_state.is_expanded && !app_state.process_search_state.search_state.is_enabled {
const TITLE_BASE: &str = " Processes ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Processes ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
// Calculate widths
let width = f64::from(draw_loc.width);
let width_ratios = [0.2, 0.4, 0.2, 0.2];
let variable_intrinsic_results = get_variable_intrinsic_widths(
width as u16,
&width_ratios,
&process_headers_lens,
);
let intrinsic_widths =
&(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
result_title
} else {
" Processes ".to_string()
}
} else {
String::default()
};
let title = if draw_border {
if app_state.is_expanded
&& !proc_widget_state
.process_search_state
.search_state
.is_enabled
{
const TITLE_BASE: &str = " Processes ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Processes ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
let process_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
match app_state.current_widget_selected {
WidgetPosition::Process => self.colours.highlighted_border_style,
_ => self.colours.border_style,
result_title
} else {
" Processes ".to_string()
}
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
WidgetPosition::Process => self.colours.highlighted_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),
String::default()
};
let border_and_title_style = if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let process_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
border_and_title_style
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(border_and_title_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc);
Table::new(process_headers.iter(), process_rows)
.block(process_block)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, margined_draw_loc[0]);
}
};
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)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, margined_draw_loc[0]);
}
}
fn draw_search_field<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
let pid_search_text = "Search by PID (Tab for Name): ";
let name_search_text = "Search by Name (Tab for PID): ";
let grouped_search_text = "Search by Name: ";
let num_columns = draw_loc.width as usize;
if let Some(proc_widget_state) =
app_state.proc_state.widget_states.get_mut(&(widget_id - 1))
{
let pid_search_text = "Search by PID (Tab for Name): ";
let name_search_text = "Search by Name (Tab for PID): ";
let grouped_search_text = "Search by Name: ";
let num_columns = draw_loc.width as usize;
let chosen_text = if app_state.is_grouped() {
grouped_search_text
} else if app_state.process_search_state.is_searching_with_pid {
pid_search_text
} else {
name_search_text
};
let is_on_widget = widget_id == app_state.current_widget.widget_id;
let search_title: &str = if chosen_text.len() == min(num_columns / 2, chosen_text.len()) {
chosen_text
} else if chosen_text.is_empty() {
""
} else {
"> "
};
let chosen_text = if proc_widget_state.is_grouped {
grouped_search_text
} else if proc_widget_state.process_search_state.is_searching_with_pid {
pid_search_text
} else {
name_search_text
};
let num_chars_for_text = search_title.len();
let small_mode = chosen_text.len() != min(num_columns / 2, chosen_text.len());
let search_title: &str = if !small_mode {
chosen_text
} else if chosen_text.is_empty() {
""
} else if proc_widget_state.process_search_state.is_searching_with_pid {
"p> "
} else {
"n> "
};
let mut search_text = vec![Text::styled(search_title, self.colours.table_header_style)];
let num_chars_for_text = search_title.len();
let cursor_position = app_state.get_cursor_position();
let current_cursor_position = app_state.get_char_cursor_position();
let mut search_text = vec![Text::styled(search_title, self.colours.table_header_style)];
let start_position: usize = get_search_start_position(
num_columns - num_chars_for_text - 5,
&app_state.process_search_state.search_state.cursor_direction,
&mut app_state.process_search_state.search_state.cursor_bar,
current_cursor_position,
app_state.is_resized,
);
let cursor_position = proc_widget_state.get_cursor_position();
let current_cursor_position = proc_widget_state.get_char_cursor_position();
let query = app_state.get_current_search_query().as_str();
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
let mut current_grapheme_posn = 0;
let query_with_cursor: Vec<Text<'_>> =
if let WidgetPosition::ProcessSearch = app_state.current_widget_selected {
let start_position: usize = get_search_start_position(
num_columns - num_chars_for_text - 5,
&proc_widget_state
.process_search_state
.search_state
.cursor_direction,
&mut proc_widget_state
.process_search_state
.search_state
.cursor_bar,
current_cursor_position,
app_state.is_resized,
);
let query = proc_widget_state.get_current_search_query().as_str();
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
let mut current_grapheme_posn = 0;
let query_with_cursor: Vec<Text<'_>> = if is_on_widget {
let mut res = grapheme_indices
.filter_map(|grapheme| {
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
@ -320,124 +346,117 @@ impl ProcessTableWidget for Painter {
.collect::<Vec<_>>()
};
// Text options shamelessly stolen from VS Code.
let mut option_text = vec![];
let case_style = if !app_state.process_search_state.is_ignoring_case {
self.colours.currently_selected_text_style
} else {
self.colours.text_style
};
let whole_word_style = if app_state.process_search_state.is_searching_whole_word {
self.colours.currently_selected_text_style
} else {
self.colours.text_style
};
let regex_style = if app_state.process_search_state.is_searching_with_regex {
self.colours.currently_selected_text_style
} else {
self.colours.text_style
};
let case_text = format!(
"Match Case ({})[{}]",
if self.is_mac_os { "F1" } else { "Alt+C" },
if !app_state.process_search_state.is_ignoring_case {
"*"
// Text options shamelessly stolen from VS Code.
let case_style = if !proc_widget_state.process_search_state.is_ignoring_case {
self.colours.currently_selected_text_style
} else {
" "
}
);
self.colours.text_style
};
let whole_text = format!(
"Match Whole Word ({})[{}]",
if self.is_mac_os { "F2" } else { "Alt+W" },
if app_state.process_search_state.is_searching_whole_word {
"*"
let whole_word_style = if proc_widget_state
.process_search_state
.is_searching_whole_word
{
self.colours.currently_selected_text_style
} else {
" "
}
);
self.colours.text_style
};
let regex_text = format!(
"Use Regex ({})[{}]",
if self.is_mac_os { "F3" } else { "Alt+R" },
if app_state.process_search_state.is_searching_with_regex {
"*"
let regex_style = if proc_widget_state
.process_search_state
.is_searching_with_regex
{
self.colours.currently_selected_text_style
} else {
" "
}
);
self.colours.text_style
};
let option_row = vec![
Text::raw("\n\n"),
Text::styled(&case_text, case_style),
Text::raw(" "),
Text::styled(&whole_text, whole_word_style),
Text::raw(" "),
Text::styled(&regex_text, regex_style),
];
option_text.extend(option_row);
search_text.extend(query_with_cursor);
search_text.extend(option_text);
let current_border_style = if app_state
.process_search_state
.search_state
.is_invalid_search
{
*INVALID_REGEX_STYLE
} else {
match app_state.current_widget_selected {
WidgetPosition::ProcessSearch => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}
};
let title = if draw_border {
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 mut option_text = vec![];
let case_text = format!(
"{}({})",
if small_mode { "Case" } else { "Match Case " },
if self.is_mac_os { "F1" } else { "Alt+C" },
);
format!("{} Esc to close ", "".repeat(repeat_num as usize))
} else {
String::new()
};
let process_search_block = if draw_border {
Block::default()
.title(&title)
.title_style(current_border_style)
.borders(Borders::ALL)
.border_style(current_border_style)
} else {
match app_state.current_widget_selected {
WidgetPosition::ProcessSearch => Block::default()
let whole_text = format!(
"{}({})",
if small_mode {
"Whole"
} else {
"Match Whole Word "
},
if self.is_mac_os { "F2" } else { "Alt+W" },
);
let regex_text = format!(
"{}({})",
if small_mode { "Regex" } else { "Use Regex " },
if self.is_mac_os { "F3" } else { "Alt+R" },
);
let option_row = vec![
Text::raw("\n\n"),
Text::styled(&case_text, case_style),
Text::raw(if small_mode { " " } else { " " }),
Text::styled(&whole_text, whole_word_style),
Text::raw(if small_mode { " " } else { " " }),
Text::styled(&regex_text, regex_style),
];
option_text.extend(option_row);
search_text.extend(query_with_cursor);
search_text.extend(option_text);
let current_border_style = if proc_widget_state
.process_search_state
.search_state
.is_invalid_search
{
*INVALID_REGEX_STYLE
} else if is_on_widget {
self.colours.highlighted_border_style
} else {
self.colours.border_style
};
let title = if draw_border {
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()
.title(&title)
.title_style(current_border_style)
.borders(Borders::ALL)
.border_style(current_border_style)
} else if is_on_widget {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(current_border_style),
_ => Block::default().borders(Borders::NONE),
}
};
.border_style(current_border_style)
} else {
Block::default().borders(Borders::NONE)
};
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.horizontal_margin(match app_state.current_widget_selected {
WidgetPosition::ProcessSearch => 0,
_ if !draw_border => 1,
_ => 0,
})
.direction(Direction::Horizontal)
.split(draw_loc);
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.horizontal_margin(if is_on_widget || draw_border { 0 } else { 1 })
.direction(Direction::Horizontal)
.split(draw_loc);
Paragraph::new(search_text.iter())
.block(process_search_block)
.style(self.colours.text_style)
.alignment(Alignment::Left)
.wrap(false)
.render(f, margined_draw_loc[0]);
Paragraph::new(search_text.iter())
.block(process_search_block)
.style(self.colours.text_style)
.alignment(Alignment::Left)
.wrap(false)
.render(f, margined_draw_loc[0]);
}
}
}

View File

@ -9,7 +9,7 @@ use tui::{
};
use crate::{
app::{self, WidgetPosition},
app,
canvas::{
drawing_utils::{get_start_position, get_variable_intrinsic_widths},
Painter,
@ -28,43 +28,37 @@ lazy_static! {
pub trait TempTableWidget {
fn draw_temp_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
);
}
impl TempTableWidget for Painter {
fn draw_temp_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
widget_id: u64,
) {
let temp_sensor_data: &[Vec<String>] = &app_state.canvas_data.temp_sensor_data;
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
let temp_sensor_data: &mut [Vec<String>] = &mut app_state.canvas_data.temp_sensor_data;
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let start_position = get_start_position(
num_rows,
&app_state.app_scroll_positions.scroll_direction,
&mut app_state
.app_scroll_positions
.temp_scroll_state
.previous_scroll_position,
app_state
.app_scroll_positions
.temp_scroll_state
.current_scroll_position,
app_state.is_resized,
);
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
let start_position = get_start_position(
num_rows,
&temp_widget_state.scroll_state.scroll_direction,
&mut temp_widget_state.scroll_state.previous_scroll_position,
temp_widget_state.scroll_state.current_scroll_position,
app_state.is_resized,
);
let sliced_vec = &temp_sensor_data[start_position as usize..];
let mut temp_row_counter: i64 = 0;
let sliced_vec = &temp_sensor_data[start_position as usize..];
let mut temp_row_counter: i64 = 0;
let current_widget_id = app_state.current_widget.widget_id;
let temperature_rows = sliced_vec.iter().map(|temp_row| {
Row::StyledData(
temp_row.iter(),
match app_state.current_widget_selected {
WidgetPosition::Temp => {
let temperature_rows = sliced_vec.iter().map(|temp_row| {
Row::StyledData(
temp_row.iter(),
if current_widget_id == widget_id {
if temp_row_counter as u64
== app_state
.app_scroll_positions
.temp_scroll_state
.current_scroll_position
== temp_widget_state.scroll_state.current_scroll_position
- start_position
{
temp_row_counter = -1;
@ -75,82 +69,86 @@ impl TempTableWidget for Painter {
}
self.colours.text_style
}
}
_ => self.colours.text_style,
},
)
});
} else {
self.colours.text_style
},
)
});
// Calculate widths
let width = f64::from(draw_loc.width);
let width_ratios = [0.5, 0.5];
let variable_intrinsic_results =
get_variable_intrinsic_widths(width as u16, &width_ratios, &TEMP_HEADERS_LENS);
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
// Calculate widths
let width = f64::from(draw_loc.width);
let width_ratios = [0.5, 0.5];
let variable_intrinsic_results =
get_variable_intrinsic_widths(width as u16, &width_ratios, &TEMP_HEADERS_LENS);
let intrinsic_widths = &(variable_intrinsic_results.0)[0..variable_intrinsic_results.1];
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Temperatures ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Temperatures ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Temperatures ── Esc to go back ";
let repeat_num = max(
0,
draw_loc.width as i32 - TITLE_BASE.chars().count() as i32 - 2,
);
let result_title = format!(
" Temperatures ─{}─ Esc to go back ",
"".repeat(repeat_num as usize)
);
result_title
} else if app_state.app_config_fields.use_basic_mode {
String::new()
} else {
" Temperatures ".to_string()
};
result_title
} else if app_state.app_config_fields.use_basic_mode {
String::new()
} else {
" Temperatures ".to_string()
};
let temp_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
match app_state.current_widget_selected {
WidgetPosition::Temp => self.colours.highlighted_border_style,
_ => self.colours.border_style,
}
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(match app_state.current_widget_selected {
WidgetPosition::Temp => self.colours.highlighted_border_style,
_ => self.colours.border_style,
})
} else {
match app_state.current_widget_selected {
WidgetPosition::Temp => Block::default()
let temp_block = if draw_border {
Block::default()
.title(&title)
.title_style(if app_state.is_expanded {
if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
}
} else {
self.colours.widget_title_style
})
.borders(Borders::ALL)
.border_style(if app_state.current_widget.widget_id == widget_id {
self.colours.highlighted_border_style
} else {
self.colours.border_style
})
} else if app_state.current_widget.widget_id == widget_id {
Block::default()
.borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style),
_ => Block::default().borders(Borders::NONE),
}
};
.border_style(self.colours.highlighted_border_style)
} else {
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);
let margined_draw_loc = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.horizontal_margin(
if app_state.current_widget.widget_id == widget_id || draw_border {
0
} else {
1
},
)
.direction(Direction::Horizontal)
.split(draw_loc);
// Draw
Table::new(TEMP_HEADERS.iter(), temperature_rows)
.block(temp_block)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, margined_draw_loc[0]);
// Draw
Table::new(TEMP_HEADERS.iter(), temperature_rows)
.block(temp_block)
.header_style(self.colours.table_header_style)
.widths(
&(intrinsic_widths
.iter()
.map(|calculated_width| Constraint::Length(*calculated_width as u16))
.collect::<Vec<_>>()),
)
.render(f, margined_draw_loc[0]);
}
}
}

View File

@ -1,5 +1,8 @@
use lazy_static::lazy_static;
// Default widget ID
pub const DEFAULT_WIDGET_ID: u64 = 56709;
// How long to store data.
pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data.
@ -56,7 +59,7 @@ pub const GENERAL_HELP_TEXT: [&str; 18] = [
pub const PROCESS_HELP_TEXT: [&str; 8] = [
"Process Keybindings\n\n",
"dd Kill the highlighted process\n",
"dd, Delete Kill the highlighted process\n",
"c Sort by CPU usage\n",
"m Sort by memory usage\n",
"p Sort by PID\n",
@ -134,20 +137,16 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##"
#temperature_type = "fahrenheit"
#temperature_type = "celsius"
# Defaults to processes. Default widget is one of:
#default_widget = "cpu_default"
#default_widget = "memory_default"
#default_widget = "disk_default"
#default_widget = "temperature_default"
#default_widget = "network_default"
#default_widget = "process_default"
# The default time interval (in milliseconds).
#default_time_value = 60000
# The time delta on each zoom in/out action (in milliseconds).
#time_delta = 15000
# Override layout default widget
#default_widget_type = "proc"
#default_widget_count = 1
# 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
# will, at the end of the day, depend on terminal support - for example, the
@ -200,4 +199,28 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##"
# Represents the cursor's colour.
#cursor_color="#458588"
# The default widget layout:
#[[row]]
# ratio=30
# [[row.child]]
# type="cpu"
#[[row]]
# ratio=40
# [[row.child]]
# ratio=4
# type="mem"
# [[row.child]]
# ratio=3
# [[row.child.child]]
# type="temp"
# [[row.child.child]]
# type="disk"
#[[row]]
# ratio=30
# [[row.child]]
# type="net"
# [[row.child]]
# type="proc"
# default=true
"##;

View File

@ -103,7 +103,7 @@ pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<S
}
pub fn convert_cpu_data_points(
current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool,
current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> Vec<ConvertedCpuData> {
let mut cpu_data_vector: Vec<ConvertedCpuData> = Vec::new();
let current_time = if is_frozen {
@ -117,8 +117,7 @@ pub fn convert_cpu_data_points(
};
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 =
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
for (itx, cpu) in data.cpu_data.iter().enumerate() {
// Check if the vector exists yet
@ -133,15 +132,15 @@ pub fn convert_cpu_data_points(
//Insert joiner points
for &(joiner_offset, joiner_val) in &cpu.1 {
let offset_time = time_from_start - joiner_offset as f64;
let offset_time = time_from_start + joiner_offset as f64;
cpu_data_vector[itx_offset]
.cpu_data
.push((offset_time, joiner_val));
.push((-offset_time, joiner_val));
}
cpu_data_vector[itx_offset]
.cpu_data
.push((time_from_start, cpu.0));
.push((-time_from_start, cpu.0));
}
if *time == current_time {
@ -153,7 +152,7 @@ pub fn convert_cpu_data_points(
}
pub fn convert_mem_data_points(
current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool,
current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> Vec<Point> {
let mut result: Vec<Point> = Vec::new();
let current_time = if is_frozen {
@ -167,16 +166,15 @@ pub fn convert_mem_data_points(
};
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 =
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.mem_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
result.push((offset_time, joiner_val));
let offset_time = time_from_start + joiner_offset as f64;
result.push((-offset_time, joiner_val));
}
result.push((time_from_start, data.mem_data.0));
result.push((-time_from_start, data.mem_data.0));
if *time == current_time {
break;
@ -187,7 +185,7 @@ pub fn convert_mem_data_points(
}
pub fn convert_swap_data_points(
current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool,
current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> Vec<Point> {
let mut result: Vec<Point> = Vec::new();
let current_time = if is_frozen {
@ -201,16 +199,15 @@ pub fn convert_swap_data_points(
};
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 =
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.swap_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
result.push((offset_time, joiner_val));
let offset_time = time_from_start + joiner_offset as f64;
result.push((-offset_time, joiner_val));
}
result.push((time_from_start, data.swap_data.0));
result.push((-time_from_start, data.swap_data.0));
if *time == current_time {
break;
@ -257,7 +254,7 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String
}
pub fn get_rx_tx_data_points(
current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool,
current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> (Vec<Point>, Vec<Point>) {
let mut rx: Vec<Point> = Vec::new();
let mut tx: Vec<Point> = Vec::new();
@ -274,22 +271,21 @@ pub fn get_rx_tx_data_points(
// TODO: [REFACTOR] Can we use collect on this, CPU, and MEM?
for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 =
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.rx_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
rx.push((offset_time, joiner_val));
let offset_time = time_from_start + joiner_offset as f64;
rx.push((-offset_time, joiner_val));
}
for &(joiner_offset, joiner_val) in &data.tx_data.1 {
let offset_time = time_from_start - joiner_offset as f64;
tx.push((offset_time, joiner_val));
let offset_time = time_from_start + joiner_offset as f64;
tx.push((-offset_time, joiner_val));
}
rx.push((time_from_start, data.rx_data.0));
tx.push((time_from_start, data.tx_data.0));
rx.push((-time_from_start, data.rx_data.0));
tx.push((-time_from_start, data.tx_data.0));
if *time == current_time {
break;
@ -300,9 +296,9 @@ pub fn get_rx_tx_data_points(
}
pub fn convert_network_data_points(
current_data: &data_farmer::DataCollection, display_time: u64, is_frozen: bool,
current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> ConvertedNetworkData {
let (rx, tx) = get_rx_tx_data_points(current_data, display_time, is_frozen);
let (rx, tx) = get_rx_tx_data_points(current_data, is_frozen);
let total_rx_converted_result: (f64, String);
let rx_converted_result: (f64, String);

View File

@ -86,14 +86,8 @@ fn get_matches() -> clap::ArgMatches<'static> {
(@arg TIME_DELTA: -d --time_delta +takes_value "The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s.")
(@arg HIDE_TIME: --hide_time "Completely hide the time scaling")
(@arg AUTOHIDE_TIME: --autohide_time "Automatically hide the time scaling in graphs after being shown for a brief moment when zoomed in/out. If time is disabled via --hide_time then this will have no effect.")
(@group DEFAULT_WIDGET =>
(@arg CPU_WIDGET: --cpu_default "Selects the CPU widget to be selected by default.")
(@arg MEM_WIDGET: --memory_default "Selects the memory widget to be selected by default.")
(@arg DISK_WIDGET: --disk_default "Selects the disk widget to be selected by default.")
(@arg TEMP_WIDGET: --temperature_default "Selects the temp widget to be selected by default.")
(@arg NET_WIDGET: --network_default "Selects the network widget to be selected by default.")
(@arg PROC_WIDGET: --process_default "Selects the process widget to be selected by default. This is the default if nothing is set.")
)
(@arg DEFAULT_WIDGET_TYPE: --default_widget_type +takes_value "The default widget type to select by default.")
(@arg DEFAULT_WIDGET_COUNT: --default_widget_count +takes_value "Which number of the selected widget type to select, from left to right, top to bottom. Defaults to 1.")
//(@arg TURNED_OFF_CPUS: -t ... +takes_value "Hides CPU data points by default") // TODO: [FEATURE] Enable disabling cores in config/flags
)
.get_matches()
@ -105,14 +99,11 @@ fn main() -> error::Result<()> {
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
// Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app(&matches, &config)?;
// Get widget layout separately
let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?;
// TODO: [REFACTOR] Change this
enable_app_grouping(&matches, &config, &mut app);
enable_app_case_sensitive(&matches, &config, &mut app);
enable_app_match_whole_word(&matches, &config, &mut app);
enable_app_use_regex(&matches, &config, &mut app);
// Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?;
// Set up up tui and crossterm
let mut stdout_val = stdout();
@ -150,13 +141,13 @@ fn main() -> error::Result<()> {
app.app_config_fields.show_average_cpu,
);
let mut painter = canvas::Painter::default();
let mut painter = canvas::Painter::init(widget_layout);
if let Err(config_check) = generate_config_colours(&config, &mut painter) {
cleanup_terminal(&mut terminal)?;
return Err(config_check);
}
painter.colours.generate_remaining_cpu_colours();
painter.initialize();
painter.complete_painter_init();
let mut first_run = true;
loop {
@ -179,11 +170,7 @@ fn main() -> error::Result<()> {
// Convert all data into tui-compliant components
// Network
let network_data = convert_network_data_points(
&app.data_collection,
app.net_state.current_display_time,
false,
);
let network_data = convert_network_data_points(&app.data_collection, false);
app.canvas_data.network_data_rx = network_data.rx;
app.canvas_data.network_data_tx = network_data.tx;
app.canvas_data.rx_display = network_data.rx_display;
@ -196,17 +183,12 @@ fn main() -> error::Result<()> {
// Temperatures
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
// Memory
app.canvas_data.mem_data = convert_mem_data_points(
&app.data_collection,
app.mem_state.current_display_time,
false,
);
app.canvas_data.swap_data = convert_swap_data_points(
&app.data_collection,
app.mem_state.current_display_time,
false,
);
app.canvas_data.mem_data =
convert_mem_data_points(&app.data_collection, false);
app.canvas_data.swap_data =
convert_swap_data_points(&app.data_collection, false);
let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
app.canvas_data.mem_label = memory_and_swap_labels.0;
app.canvas_data.swap_label = memory_and_swap_labels.1;
@ -214,23 +196,23 @@ fn main() -> error::Result<()> {
// Pre-fill CPU if needed
if first_run {
let cpu_len = app.data_collection.cpu_harvest.len();
app.cpu_state.core_show_vec = vec![true; cpu_len];
app.cpu_state.num_cpus_shown = cpu_len as u64;
app.cpu_state.widget_states.values_mut().for_each(|state| {
state.core_show_vec = vec![true; cpu_len];
state.num_cpus_shown = cpu_len;
});
app.cpu_state.num_cpus_total = cpu_len;
first_run = false;
}
// CPU
app.canvas_data.cpu_data = convert_cpu_data_points(
&app.data_collection,
app.cpu_state.current_display_time,
false,
);
app.canvas_data.cpu_data =
convert_cpu_data_points(&app.data_collection, false);
// Processes
let (single, grouped) = convert_process_data(&app.data_collection);
app.canvas_data.process_data = single;
app.canvas_data.grouped_process_data = grouped;
update_final_process_list(&mut app);
update_all_process_lists(&mut app);
}
}
BottomEvent::Clean => {
@ -240,14 +222,6 @@ fn main() -> error::Result<()> {
}
}
// Quick fix for tab updating the table headers
if let data_harvester::processes::ProcessSorting::PID = &app.process_sorting_type {
if app.is_grouped() {
app.process_sorting_type = data_harvester::processes::ProcessSorting::CPU; // Go back to default, negate PID for group
app.process_sorting_reverse = true;
}
}
try_drawing(&mut terminal, &mut app, &mut painter)?;
}
@ -292,19 +266,13 @@ fn handle_key_event_or_break(
KeyCode::Backspace => app.on_backspace(),
KeyCode::Delete => app.on_delete(),
KeyCode::F(1) => {
if app.is_in_search_widget() {
app.toggle_ignore_case();
}
app.toggle_ignore_case();
}
KeyCode::F(2) => {
if app.is_in_search_widget() {
app.toggle_search_whole_word();
}
app.toggle_search_whole_word();
}
KeyCode::F(3) => {
if app.is_in_search_widget() {
app.toggle_search_regex();
}
app.toggle_search_regex();
}
_ => {}
}
@ -313,19 +281,13 @@ fn handle_key_event_or_break(
if let KeyModifiers::ALT = event.modifiers {
match event.code {
KeyCode::Char('c') | KeyCode::Char('C') => {
if app.is_in_search_widget() {
app.toggle_ignore_case();
}
app.toggle_ignore_case();
}
KeyCode::Char('w') | KeyCode::Char('W') => {
if app.is_in_search_widget() {
app.toggle_search_whole_word();
}
app.toggle_search_whole_word();
}
KeyCode::Char('r') | KeyCode::Char('R') => {
if app.is_in_search_widget() {
app.toggle_search_regex();
}
app.toggle_search_regex();
}
_ => {}
}
@ -550,59 +512,65 @@ fn panic_hook(panic_info: &PanicInfo<'_>) {
}
fn handle_force_redraws(app: &mut App) {
if app.force_update_processes {
update_final_process_list(app);
app.force_update_processes = false;
// Currently we use an Option... because we might want to future-proof this
// if we eventually get widget-specific redrawing!
if app.proc_state.force_update_all {
update_all_process_lists(app);
app.proc_state.force_update_all = false;
} else if let Some(widget_id) = app.proc_state.force_update {
update_final_process_list(app, widget_id);
app.proc_state.force_update = None;
}
if app.cpu_state.force_update {
app.canvas_data.cpu_data = convert_cpu_data_points(
&app.data_collection,
app.cpu_state.current_display_time,
app.is_frozen,
);
app.cpu_state.force_update = false;
if app.cpu_state.force_update.is_some() {
app.canvas_data.cpu_data = convert_cpu_data_points(&app.data_collection, app.is_frozen);
app.cpu_state.force_update = None;
}
if app.mem_state.force_update {
app.canvas_data.mem_data = convert_mem_data_points(
&app.data_collection,
app.mem_state.current_display_time,
app.is_frozen,
);
app.canvas_data.swap_data = convert_swap_data_points(
&app.data_collection,
app.mem_state.current_display_time,
app.is_frozen,
);
app.mem_state.force_update = false;
if app.mem_state.force_update.is_some() {
app.canvas_data.mem_data = convert_mem_data_points(&app.data_collection, app.is_frozen);
app.canvas_data.swap_data = convert_swap_data_points(&app.data_collection, app.is_frozen);
app.mem_state.force_update = None;
}
if app.net_state.force_update {
let (rx, tx) = get_rx_tx_data_points(
&app.data_collection,
app.net_state.current_display_time,
app.is_frozen,
);
if app.net_state.force_update.is_some() {
let (rx, tx) = get_rx_tx_data_points(&app.data_collection, app.is_frozen);
app.canvas_data.network_data_rx = rx;
app.canvas_data.network_data_tx = tx;
app.net_state.force_update = false;
app.net_state.force_update = None;
}
}
fn update_final_process_list(app: &mut App) {
let mut filtered_process_data: Vec<ConvertedProcessData> = if app.is_grouped() {
fn update_all_process_lists(app: &mut App) {
let widget_ids = app
.proc_state
.widget_states
.keys()
.cloned()
.collect::<Vec<_>>();
widget_ids.into_iter().for_each(|widget_id| {
update_final_process_list(app, widget_id);
});
}
fn update_final_process_list(app: &mut App, widget_id: u64) {
let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) {
Some(process_state) => process_state
.process_search_state
.search_state
.is_invalid_or_blank_search(),
None => false,
};
let filtered_process_data: Vec<ConvertedProcessData> = if app.is_grouped(widget_id) {
app.canvas_data
.grouped_process_data
.iter()
.filter(|process| {
if app
.process_search_state
.search_state
.is_invalid_or_blank_search()
{
if is_invalid_or_blank {
return true;
} else if let Some(matcher_result) = app.get_current_regex_matcher() {
} else if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) {
if let Ok(matcher) = matcher_result {
return matcher.is_match(&process.name);
}
@ -613,20 +581,20 @@ fn update_final_process_list(app: &mut App) {
.cloned()
.collect::<Vec<_>>()
} else {
let is_searching_with_pid = match app.proc_state.widget_states.get(&widget_id) {
Some(process_state) => process_state.process_search_state.is_searching_with_pid,
None => false,
};
app.canvas_data
.process_data
.iter()
.filter_map(|(_pid, process)| {
let mut result = true;
if !app
.process_search_state
.search_state
.is_invalid_or_blank_search()
{
if let Some(matcher_result) = app.get_current_regex_matcher() {
if !is_invalid_or_blank {
if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) {
if let Ok(matcher) = matcher_result {
if app.process_search_state.is_searching_with_pid {
if is_searching_with_pid {
result = matcher.is_match(&process.pid.to_string());
} else {
result = matcher.is_match(&process.name);
@ -650,31 +618,84 @@ fn update_final_process_list(app: &mut App) {
.collect::<Vec<_>>()
};
sort_process_data(&mut filtered_process_data, app);
app.canvas_data.finalized_process_data = filtered_process_data;
// Quick fix for tab updating the table headers
if let Some(proc_widget_state) = app.proc_state.widget_states.get_mut(&widget_id) {
if let data_harvester::processes::ProcessSorting::PID =
proc_widget_state.process_sorting_type
{
if proc_widget_state.is_grouped {
proc_widget_state.process_sorting_type =
data_harvester::processes::ProcessSorting::CPU; // Go back to default, negate PID for group
proc_widget_state.process_sorting_reverse = true;
}
}
let mut resulting_processes = filtered_process_data;
sort_process_data(&mut resulting_processes, proc_widget_state);
if proc_widget_state.scroll_state.current_scroll_position
>= resulting_processes.len() as u64
{
proc_widget_state.scroll_state.current_scroll_position =
if resulting_processes.len() > 1 {
resulting_processes.len() as u64 - 1
} else {
0
};
proc_widget_state.scroll_state.previous_scroll_position = 0;
proc_widget_state.scroll_state.scroll_direction = app::ScrollDirection::DOWN;
}
app.canvas_data
.finalized_process_data_map
.insert(widget_id, resulting_processes);
}
}
fn sort_process_data(to_sort_vec: &mut Vec<ConvertedProcessData>, app: &App) {
fn sort_process_data(
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
) {
to_sort_vec.sort_by(|a, b| utils::gen_util::get_ordering(&a.name, &b.name, false));
match app.process_sorting_type {
match proc_widget_state.process_sorting_type {
ProcessSorting::CPU => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(a.cpu_usage, b.cpu_usage, app.process_sorting_reverse)
utils::gen_util::get_ordering(
a.cpu_usage,
b.cpu_usage,
proc_widget_state.process_sorting_reverse,
)
});
}
ProcessSorting::MEM => {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(a.mem_usage, b.mem_usage, app.process_sorting_reverse)
utils::gen_util::get_ordering(
a.mem_usage,
b.mem_usage,
proc_widget_state.process_sorting_reverse,
)
});
}
ProcessSorting::NAME => to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(&a.name, &b.name, app.process_sorting_reverse)
}),
ProcessSorting::PID => {
if !app.is_grouped() {
ProcessSorting::NAME => {
// Don't repeat if false...
if proc_widget_state.process_sorting_reverse {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(a.pid, b.pid, app.process_sorting_reverse)
utils::gen_util::get_ordering(
&a.name,
&b.name,
proc_widget_state.process_sorting_reverse,
)
})
}
}
ProcessSorting::PID => {
if !proc_widget_state.is_grouped {
to_sort_vec.sort_by(|a, b| {
utils::gen_util::get_ordering(
a.pid,
b.pid,
proc_widget_state.process_sorting_reverse,
)
});
}
}

View File

@ -1,21 +1,26 @@
use serde::Deserialize;
use std::collections::HashMap;
use std::time::Instant;
use crate::{
app::{data_harvester, App, AppConfigFields, CpuState, MemState, NetState, WidgetPosition},
app::{
data_harvester, layout_manager::*, App, AppConfigFields, BasicTableWidgetState, CpuState,
CpuWidgetState, DiskState, DiskWidgetState, MemState, MemWidgetState, NetState,
NetWidgetState, ProcState, ProcWidgetState, TempState, TempWidgetState,
},
constants::*,
utils::error::{self, BottomError},
};
// use layout_manager::*;
use layout_options::*;
// mod layout_manager;
mod layout_options;
#[derive(Default, Deserialize)]
pub struct Config {
pub flags: Option<ConfigFlags>,
pub colors: Option<ConfigColours>,
pub row: Option<Vec<Row>>,
}
#[derive(Default, Deserialize)]
@ -37,6 +42,8 @@ pub struct ConfigFlags {
pub time_delta: Option<u64>,
pub autohide_time: Option<bool>,
pub hide_time: Option<bool>,
pub default_widget_type: Option<String>,
pub default_widget_count: Option<u64>,
//disabled_cpu_cores: Option<Vec<u64>>, // TODO: [FEATURE] Enable disabling cores in config/flags
}
@ -60,27 +67,135 @@ pub struct ConfigColours {
pub graph_color: Option<String>,
}
pub fn build_app(matches: &clap::ArgMatches<'static>, config: &Config) -> error::Result<App> {
pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
default_widget_id: u64,
) -> error::Result<App> {
let autohide_time = get_autohide_time(&matches, &config);
let default_time_value = get_default_time_value(&matches, &config)?;
let default_widget = get_default_widget(&matches, &config);
let use_basic_mode = get_use_basic_mode(&matches, &config);
let current_widget_selected = if use_basic_mode {
match default_widget {
WidgetPosition::Cpu => WidgetPosition::BasicCpu,
WidgetPosition::Network => WidgetPosition::BasicNet,
WidgetPosition::Mem => WidgetPosition::BasicMem,
_ => default_widget,
}
// For processes
let is_grouped = get_app_grouping(matches, config);
let is_case_sensitive = get_app_case_sensitive(matches, config);
let is_match_whole_word = get_app_match_whole_word(matches, config);
let is_use_regex = get_app_use_regex(matches, config);
let mut widget_map = HashMap::new();
let mut cpu_state_map: HashMap<u64, CpuWidgetState> = HashMap::new();
let mut mem_state_map: HashMap<u64, MemWidgetState> = HashMap::new();
let mut net_state_map: HashMap<u64, NetWidgetState> = HashMap::new();
let mut proc_state_map: HashMap<u64, ProcWidgetState> = HashMap::new();
let mut temp_state_map: HashMap<u64, TempWidgetState> = HashMap::new();
let mut disk_state_map: HashMap<u64, DiskWidgetState> = HashMap::new();
let autohide_timer = if autohide_time {
Some(Instant::now())
} else {
default_widget
None
};
let previous_basic_table_selected = if default_widget.is_widget_table() {
default_widget
let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?;
let mut initial_widget_id: u64 = default_widget_id;
let mut initial_widget_type = BottomWidgetType::Proc;
let is_custom_layout = config.row.is_some();
for row in &widget_layout.rows {
for col in &row.children {
for col_row in &col.children {
for widget in &col_row.children {
widget_map.insert(widget.widget_id, widget.clone());
if let Some(default_widget_type) = &default_widget_type_option {
if !is_custom_layout || use_basic_mode {
match widget.widget_type {
BottomWidgetType::BasicCpu => {
if let BottomWidgetType::Cpu = *default_widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = BottomWidgetType::Cpu;
}
}
BottomWidgetType::BasicMem => {
if let BottomWidgetType::Mem = *default_widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = BottomWidgetType::Cpu;
}
}
BottomWidgetType::BasicNet => {
if let BottomWidgetType::Net = *default_widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = BottomWidgetType::Cpu;
}
}
_ => {
if *default_widget_type == widget.widget_type {
initial_widget_id = widget.widget_id;
initial_widget_type = widget.widget_type.clone();
}
}
}
}
}
match widget.widget_type {
BottomWidgetType::Cpu => {
cpu_state_map.insert(
widget.widget_id,
CpuWidgetState::init(default_time_value, autohide_timer),
);
}
BottomWidgetType::Mem => {
mem_state_map.insert(
widget.widget_id,
MemWidgetState::init(default_time_value, autohide_timer),
);
}
BottomWidgetType::Net => {
net_state_map.insert(
widget.widget_id,
NetWidgetState::init(default_time_value, autohide_timer),
);
}
BottomWidgetType::Proc => {
proc_state_map.insert(
widget.widget_id,
ProcWidgetState::init(
is_case_sensitive,
is_match_whole_word,
is_use_regex,
is_grouped,
),
);
}
BottomWidgetType::Disk => {
disk_state_map.insert(widget.widget_id, DiskWidgetState::init());
}
BottomWidgetType::Temp => {
temp_state_map.insert(widget.widget_id, TempWidgetState::init());
}
_ => {}
}
}
}
}
}
// FIXME: [MODULARITY] Don't collect if not added!
let basic_table_widget_state = if use_basic_mode {
Some(match initial_widget_type {
BottomWidgetType::Proc | BottomWidgetType::Disk | BottomWidgetType::Temp => {
BasicTableWidgetState {
currently_displayed_widget_type: initial_widget_type,
currently_displayed_widget_id: initial_widget_id,
widget_id: 100,
}
}
_ => BasicTableWidgetState {
currently_displayed_widget_type: BottomWidgetType::Proc,
currently_displayed_widget_id: DEFAULT_WIDGET_ID,
widget_id: 100,
},
})
} else {
WidgetPosition::Process
None
};
let app_config_fields = AppConfigFields {
@ -98,22 +213,62 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &Config) -> error:
autohide_time,
};
let time_now = if autohide_time {
Some(Instant::now())
} else {
None
};
Ok(App::builder()
.app_config_fields(app_config_fields)
.current_widget_selected(current_widget_selected)
.previous_basic_table_selected(previous_basic_table_selected)
.cpu_state(CpuState::init(default_time_value, time_now))
.mem_state(MemState::init(default_time_value, time_now))
.net_state(NetState::init(default_time_value, time_now))
.cpu_state(CpuState::init(cpu_state_map))
.mem_state(MemState::init(mem_state_map))
.net_state(NetState::init(net_state_map))
.proc_state(ProcState::init(proc_state_map))
.disk_state(DiskState::init(disk_state_map))
.temp_state(TempState::init(temp_state_map))
.basic_table_widget_state(basic_table_widget_state)
.current_widget(widget_map.get(&initial_widget_id).unwrap().clone()) // I think the unwrap is fine here
.widget_map(widget_map)
.build())
}
pub fn get_widget_layout(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<(BottomLayout, u64)> {
let left_legend = get_use_left_legend(matches, config);
let (default_widget_type, mut default_widget_count) =
get_default_widget_and_count(matches, config)?;
let mut default_widget_id = 1;
let bottom_layout = if get_use_basic_mode(matches, config) {
default_widget_id = DEFAULT_WIDGET_ID;
BottomLayout::init_basic_default()
} else if let Some(rows) = &config.row {
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
let mut total_height_ratio = 0;
let mut ret_bottom_layout = BottomLayout {
rows: rows
.iter()
.map(|row| {
row.convert_row_to_bottom_row(
&mut iter_id,
&mut total_height_ratio,
&mut default_widget_id,
&default_widget_type,
&mut default_widget_count,
left_legend,
)
})
.collect::<error::Result<Vec<_>>>()?,
total_row_height_ratio: total_height_ratio,
};
ret_bottom_layout.get_movement_mappings();
ret_bottom_layout
} else {
default_widget_id = DEFAULT_WIDGET_ID;
BottomLayout::init_default(left_legend)
};
Ok((bottom_layout, default_widget_id))
}
fn get_update_rate_in_milliseconds(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<u64> {
@ -296,56 +451,56 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er
Ok(time_interval as u64)
}
pub fn enable_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) {
pub fn get_app_grouping(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("GROUP_PROCESSES") {
app.toggle_grouping();
return true;
} else if let Some(flags) = &config.flags {
if let Some(grouping) = flags.group_processes {
if grouping {
app.toggle_grouping();
return true;
}
}
}
false
}
pub fn enable_app_case_sensitive(
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
) {
pub fn get_app_case_sensitive(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("CASE_SENSITIVE") {
app.process_search_state.search_toggle_ignore_case();
return true;
} 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();
return true;
}
}
}
false
}
pub fn enable_app_match_whole_word(
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
) {
pub fn get_app_match_whole_word(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("WHOLE_WORD") {
app.process_search_state.search_toggle_whole_word();
return true;
} 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();
return true;
}
}
}
false
}
pub fn enable_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App) {
pub fn get_app_use_regex(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
if matches.is_present("REGEX_DEFAULT") {
app.process_search_state.search_toggle_regex();
return true;
} else if let Some(flags) = &config.flags {
if let Some(regex) = flags.regex {
if regex {
app.process_search_state.search_toggle_regex();
return true;
}
}
}
false
}
fn get_hide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
@ -375,32 +530,52 @@ fn get_autohide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bo
false
}
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,
};
fn get_default_widget_and_count(
matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<(Option<BottomWidgetType>, u64)> {
let widget_type = if let Some(widget_type) = matches.value_of("DEFAULT_WIDGET_TYPE") {
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
if let BottomWidgetType::Empty = parsed_widget {
None
} else {
Some(parsed_widget)
}
}
} else if let Some(flags) = &config.flags {
if let Some(widget_type) = &flags.default_widget_type {
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
if let BottomWidgetType::Empty = parsed_widget {
None
} else {
Some(parsed_widget)
}
} else {
None
}
} else {
None
};
WidgetPosition::Process
if widget_type.is_some() {
let widget_count = if let Some(widget_count) = matches.value_of("DEFAULT_WIDGET_COUNT") {
widget_count.parse::<u128>()?
} else if let Some(flags) = &config.flags {
if let Some(widget_count) = flags.default_widget_count {
widget_count as u128
} else {
1 as u128
}
} else {
1 as u128
};
if widget_count > std::u64::MAX as u128 {
Err(BottomError::InvalidArg(
"Please set your widget count to be at most unsigned INT_MAX.".to_string(),
))
} else {
Ok((widget_type, widget_count as u64))
}
} else {
Ok((None, 1))
}
}

View File

@ -1,3 +0,0 @@
use serde::Deserialize;
use toml::Value;

View File

@ -0,0 +1,307 @@
use crate::app::layout_manager::*;
use crate::error::Result;
use serde::Deserialize;
/// Represents a row. This has a length of some sort (optional) and a vector
/// of children.
#[derive(Deserialize, Debug)]
#[serde(rename = "row")]
pub struct Row {
pub ratio: Option<u32>,
pub child: Option<Vec<RowChildren>>,
}
impl Row {
pub fn convert_row_to_bottom_row(
&self, iter_id: &mut u64, total_height_ratio: &mut u32, default_widget_id: &mut u64,
default_widget_type: &Option<BottomWidgetType>, default_widget_count: &mut u64,
left_legend: bool,
) -> Result<BottomRow> {
// In the future we want to also add percentages.
// But for MVP, we aren't going to bother.
let row_ratio = self.ratio.unwrap_or(1);
let mut children = Vec::new();
*total_height_ratio += row_ratio;
let mut total_col_ratio = 0;
if let Some(row_children) = &self.child {
for row_child in row_children {
match row_child {
RowChildren::Widget(widget) => {
*iter_id += 1;
let width_ratio = widget.ratio.unwrap_or(1);
total_col_ratio += width_ratio;
let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
if let Some(default_widget_type_val) = default_widget_type {
if *default_widget_type_val == widget_type && *default_widget_count > 0
{
*default_widget_count -= 1;
if *default_widget_count == 0 {
*default_widget_id = *iter_id;
}
}
} else {
// Check default flag
if let Some(default_widget_flag) = widget.default {
if default_widget_flag {
*default_widget_id = *iter_id;
}
}
}
children.push(match widget_type {
BottomWidgetType::Cpu => {
let iter_old_id = *iter_id;
*iter_id += 1;
BottomCol::builder()
.col_width_ratio(width_ratio)
.children(if left_legend {
vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(vec![
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(*iter_id)
.canvas_handle_width(true)
.build(),
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(iter_old_id)
.flex_grow(true)
.build(),
])
.build()]
} else {
vec![BottomColRow::builder()
.total_widget_ratio(20)
.children(vec![
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(iter_old_id)
.flex_grow(true)
.build(),
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(*iter_id)
.canvas_handle_width(true)
.build(),
])
.build()]
})
.build()
}
BottomWidgetType::Proc => {
let iter_old_id = *iter_id;
*iter_id += 1;
BottomCol::builder()
.total_col_row_ratio(2)
.col_width_ratio(width_ratio)
.children(vec![
BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Proc)
.widget_id(iter_old_id)
.build()])
.flex_grow(true)
.build(),
BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(*iter_id)
.build()])
.canvas_handle_height(true)
.build(),
])
.build()
}
_ => BottomCol::builder()
.col_width_ratio(width_ratio)
.children(vec![BottomColRow::builder()
.children(vec![BottomWidget::builder()
.widget_type(widget_type)
.widget_id(*iter_id)
.build()])
.build()])
.build(),
});
}
RowChildren::Col { ratio, child } => {
let col_width_ratio = ratio.unwrap_or(1);
total_col_ratio += col_width_ratio;
let mut total_col_row_ratio = 0;
let mut contains_proc = false;
let mut col_row_children = Vec::new();
for widget in child {
let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
*iter_id += 1;
let col_row_height_ratio = widget.ratio.unwrap_or(1);
total_col_row_ratio += col_row_height_ratio;
if let Some(default_widget_type_val) = default_widget_type {
if *default_widget_type_val == widget_type
&& *default_widget_count > 0
{
*default_widget_count -= 1;
if *default_widget_count == 0 {
*default_widget_id = *iter_id;
}
}
} else {
// Check default flag
if let Some(default_widget_flag) = widget.default {
if default_widget_flag {
*default_widget_id = *iter_id;
}
}
}
match widget_type {
BottomWidgetType::Cpu => {
let iter_old_id = *iter_id;
*iter_id += 1;
if left_legend {
col_row_children.push(
BottomColRow::builder()
.col_row_height_ratio(col_row_height_ratio)
.total_widget_ratio(20)
.children(vec![
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(*iter_id)
.canvas_handle_width(true)
.build(),
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(iter_old_id)
.flex_grow(true)
.build(),
])
.build(),
);
} else {
col_row_children.push(
BottomColRow::builder()
.col_row_height_ratio(col_row_height_ratio)
.total_widget_ratio(20)
.children(vec![
BottomWidget::builder()
.width_ratio(17)
.widget_type(BottomWidgetType::Cpu)
.widget_id(iter_old_id)
.flex_grow(true)
.build(),
BottomWidget::builder()
.width_ratio(3)
.widget_type(BottomWidgetType::CpuLegend)
.widget_id(*iter_id)
.canvas_handle_width(true)
.build(),
])
.build(),
);
}
}
BottomWidgetType::Proc => {
contains_proc = true;
let iter_old_id = *iter_id;
*iter_id += 1;
col_row_children.push(
BottomColRow::builder()
.col_row_height_ratio(col_row_height_ratio)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::Proc)
.widget_id(iter_old_id)
.build()])
.flex_grow(true)
.build(),
);
col_row_children.push(
BottomColRow::builder()
.col_row_height_ratio(col_row_height_ratio)
.children(vec![BottomWidget::builder()
.widget_type(BottomWidgetType::ProcSearch)
.widget_id(*iter_id)
.build()])
.canvas_handle_height(true)
.build(),
);
}
_ => col_row_children.push(
BottomColRow::builder()
.col_row_height_ratio(col_row_height_ratio)
.children(vec![BottomWidget::builder()
.widget_type(widget_type)
.widget_id(*iter_id)
.build()])
.build(),
),
}
}
if contains_proc {
// Must adjust ratios to work with proc
total_col_row_ratio *= 2;
for child in &mut col_row_children {
// Multiply all non-proc or proc-search ratios by 2
if !child.children.is_empty() {
match child.children[0].widget_type {
BottomWidgetType::Proc | BottomWidgetType::ProcSearch => {}
_ => child.col_row_height_ratio *= 2,
}
}
}
}
children.push(
BottomCol::builder()
.total_col_row_ratio(total_col_row_ratio)
.col_width_ratio(col_width_ratio)
.children(col_row_children)
.build(),
);
}
}
}
}
Ok(BottomRow::builder()
.total_col_ratio(total_col_ratio)
.row_height_ratio(row_ratio)
.children(children)
.build())
}
}
/// Represents a child of a Row - either a Col (column) or a FinalWidget.
///
/// A Col can also have an optional length and children. We only allow columns
/// to have FinalWidgets as children, lest we get some amount of mutual
/// recursion between Row and Col.
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum RowChildren {
Widget(FinalWidget),
Col {
ratio: Option<u32>,
child: Vec<FinalWidget>,
},
}
/// Represents a widget.
#[derive(Deserialize, Debug)]
pub struct FinalWidget {
pub ratio: Option<u32>,
#[serde(rename = "type")]
pub widget_type: String,
pub default: Option<bool>,
}

View File

@ -141,14 +141,28 @@ fn test_conflicting_temps() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_conflicting_default_widget() -> Result<(), Box<dyn std::error::Error>> {
fn test_invalid_default_widget_1() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("--cpu_default")
.arg("--disk_default")
.arg("--default_widget_type")
.arg("fake_widget")
.assert()
.failure()
.stderr(predicate::str::contains("Invalid widget type"));
Ok(())
}
#[test]
fn test_invalid_default_widget_2() -> Result<(), Box<dyn std::error::Error>> {
Command::new(get_os_binary_loc())
.arg("--default_widget_type")
.arg("cpu")
.arg("--default_widget_count")
.arg("18446744073709551616")
.assert()
.failure()
.stderr(predicate::str::contains(
"cannot be used with one or more of the other specified arguments",
"Please set your widget count to be at most unsigned INT_MAX",
));
Ok(())