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: jobs:
allow_failures: allow_failures:
- rust: nightly - 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 fast_finish: true
branches: branches:
only: only:

View File

@ -31,7 +31,7 @@ fern = "0.6.0"
futures = "0.3.4" futures = "0.3.4"
heim = "0.0.10" heim = "0.0.10"
log = "0.4.8" log = "0.4.8"
regex = "1.3.4" regex = "1.3"
sysinfo = "0.11" sysinfo = "0.11"
toml = "0.5.6" toml = "0.5.6"
tui = {version = "0.8", features = ["crossterm"], default-features = false } 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`). - 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). - 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 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 ## 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. 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 ## Default config locations
bottom will check specific locations by default for a config file. If no file is found, it will be created. 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 total_delta: f64 = total - prev_total;
let idle_delta: f64 = idle - *prev_idle; let idle_delta: f64 = idle - *prev_idle;
//debug!("Vangelis function: CPU PERCENT: {}", (total_delta - idle_delta) / total_delta * 100_f64);
*prev_idle = idle; *prev_idle = idle;
*prev_non_idle = non_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 utime = val[13].parse::<f64>().unwrap_or(0_f64);
let stime = val[14].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... 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)?; 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)); new_pid_stats.insert(pid.to_string(), (after_proc_val, curr_time));
if use_current_cpu_total { 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> { fn open(pid: DWORD) -> Result<Process, String> {
let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) }; let pc = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, 0, pid) };
if pc.is_null() { if pc.is_null() {
return Err("!OpenProcess".to_string()); return Err("OpenProcess".to_string());
} }
Ok(Process(pc)) Ok(Process(pc))
} }

View File

@ -3,8 +3,7 @@ use std::collections::HashMap;
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout},
terminal::Frame,
widgets::Text, widgets::Text,
Terminal, Terminal,
}; };
@ -14,7 +13,11 @@ use dialogs::*;
use widgets::*; use widgets::*;
use crate::{ use crate::{
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition}, app::{
self,
data_harvester::processes::ProcessHarvest,
layout_manager::{BottomLayout, BottomWidgetType},
},
constants::*, constants::*,
data_conversion::{ConvertedCpuData, ConvertedProcessData}, data_conversion::{ConvertedCpuData, ConvertedProcessData},
utils::error, utils::error,
@ -40,7 +43,7 @@ pub struct DisplayableData {
// Not the final value // Not the final value
pub grouped_process_data: Vec<ConvertedProcessData>, pub grouped_process_data: Vec<ConvertedProcessData>,
// What's actually displayed // What's actually displayed
pub finalized_process_data: Vec<ConvertedProcessData>, pub finalized_process_data_map: HashMap<u64, Vec<ConvertedProcessData>>,
pub mem_label: String, pub mem_label: String,
pub swap_label: String, pub swap_label: String,
pub mem_data: Vec<(f64, f64)>, pub mem_data: Vec<(f64, f64)>,
@ -48,31 +51,110 @@ pub struct DisplayableData {
pub cpu_data: Vec<ConvertedCpuData>, pub cpu_data: Vec<ConvertedCpuData>,
} }
#[allow(dead_code)]
#[derive(Default)]
/// Handles the canvas' state. TODO: [OPT] implement this. /// Handles the canvas' state. TODO: [OPT] implement this.
pub struct Painter { pub struct Painter {
pub colours: CanvasColours,
height: u16, height: u16,
width: u16, width: u16,
vertical_dialog_chunk: Vec<Rect>, styled_general_help_text: Vec<Text<'static>>,
middle_dialog_chunk: Vec<Rect>, styled_process_help_text: Vec<Text<'static>>,
vertical_chunks: Vec<Rect>, styled_search_help_text: Vec<Text<'static>>,
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>>,
is_mac_os: bool, 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 { 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. /// Must be run once before drawing, but after setting colours.
/// This is to set some remaining styles and text. /// 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"); self.is_mac_os = cfg!(target_os = "macos");
if GENERAL_HELP_TEXT.len() > 1 { if GENERAL_HELP_TEXT.len() > 1 {
@ -115,24 +197,26 @@ impl Painter {
} }
} }
pub fn draw_specific_table<B: Backend>( // pub fn draw_specific_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool, // &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
widget_selected: WidgetPosition, // widget_selected: WidgetPosition,
) { // ) {
match widget_selected { // match widget_selected {
WidgetPosition::Process | WidgetPosition::ProcessSearch => { // WidgetPosition::Process | WidgetPosition::ProcessSearch => {
self.draw_process_and_search(f, app_state, draw_loc, draw_border) // 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::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), // WidgetPosition::Disk => self.draw_disk_table(f, app_state, draw_loc, draw_border),
_ => {} // _ => {}
} // }
} // }
// TODO: [FEATURE] Auto-resizing dialog sizes. // TODO: [FEATURE] Auto-resizing dialog sizes.
pub fn draw_data<B: Backend>( pub fn draw_data<B: Backend>(
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App, &mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
) -> error::Result<()> { ) -> error::Result<()> {
use BottomWidgetType::*;
let terminal_size = terminal.size()?; let terminal_size = terminal.size()?;
let current_height = terminal_size.height; let current_height = terminal_size.height;
let current_width = terminal_size.width; let current_width = terminal_size.width;
@ -231,55 +315,63 @@ impl Painter {
} }
} else if app_state.is_expanded { } else if app_state.is_expanded {
let rect = Layout::default() let rect = Layout::default()
.margin(1) .margin(0)
.constraints([Constraint::Percentage(100)].as_ref()) .constraints([Constraint::Percentage(100)].as_ref())
.split(f.size()); .split(f.size());
match &app_state.current_widget_selected { match &app_state.current_widget.widget_type {
WidgetPosition::Cpu | WidgetPosition::BasicCpu | WidgetPosition::CpuLegend => { Cpu => self.draw_cpu(
let cpu_chunk = Layout::default() &mut f,
.direction(Direction::Horizontal) app_state,
.margin(0) rect[0],
.constraints( app_state.current_widget.widget_id,
if app_state.app_config_fields.left_legend { ),
[Constraint::Percentage(15), Constraint::Percentage(85)] CpuLegend => self.draw_cpu(
} else { &mut f,
[Constraint::Percentage(85), Constraint::Percentage(15)] app_state,
} rect[0],
.as_ref(), app_state.current_widget.widget_id - 1,
) ),
.split(rect[0]); Mem | BasicMem => self.draw_memory_graph(
&mut f,
let legend_index = if app_state.app_config_fields.left_legend { app_state,
0 rect[0],
} else { app_state.current_widget.widget_id,
1 ),
}; Disk => self.draw_disk_table(
let graph_index = if app_state.app_config_fields.left_legend { &mut f,
1 app_state,
} else { rect[0],
0 true,
}; app_state.current_widget.widget_id,
),
self.draw_cpu_graph(&mut f, app_state, cpu_chunk[graph_index]); Temp => self.draw_temp_table(
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]); &mut f,
} app_state,
WidgetPosition::Mem | WidgetPosition::BasicMem => { rect[0],
self.draw_memory_graph(&mut f, app_state, rect[0]); true,
} app_state.current_widget.widget_id,
WidgetPosition::Disk => { ),
self.draw_disk_table(&mut f, app_state, rect[0], true); Net => self.draw_network_graph(
} &mut f,
WidgetPosition::Temp => { app_state,
self.draw_temp_table(&mut f, app_state, rect[0], true); rect[0],
} app_state.current_widget.widget_id,
WidgetPosition::Network ),
| WidgetPosition::BasicNet Proc => self.draw_process_and_search(
| WidgetPosition::NetworkLegend => { &mut f,
self.draw_network_graph(&mut f, app_state, rect[0]); app_state,
} rect[0],
WidgetPosition::Process | WidgetPosition::ProcessSearch => { true,
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 { } else if app_state.app_config_fields.use_basic_mode {
// Basic mode. This basically removes all graphs but otherwise // Basic mode. This basically removes all graphs but otherwise
@ -309,112 +401,144 @@ impl Painter {
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(vertical_chunks[2]); .split(vertical_chunks[2]);
self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0]); self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0], 1);
self.draw_basic_memory(&mut f, app_state, middle_chunks[0]); self.draw_basic_memory(&mut f, app_state, middle_chunks[0], 2);
self.draw_basic_network(&mut f, app_state, middle_chunks[1]); self.draw_basic_network(&mut f, app_state, middle_chunks[1], 3);
self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3]); self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3]);
if app_state.current_widget_selected.is_widget_table() { if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
self.draw_specific_table( let widget_id = basic_table_widget_state.currently_displayed_widget_id;
&mut f, match basic_table_widget_state.currently_displayed_widget_type {
app_state, Disk => self.draw_disk_table(
vertical_chunks[4], &mut f,
false, app_state,
app_state.current_widget_selected, vertical_chunks[4],
); false,
} else { widget_id,
self.draw_specific_table( ),
&mut f, Proc => self.draw_process_and_search(
app_state, &mut f,
vertical_chunks[4], app_state,
false, vertical_chunks[4],
app_state.previous_basic_table_selected, false,
); widget_id,
),
Temp => self.draw_temp_table(
&mut f,
app_state,
vertical_chunks[4],
false,
widget_id,
),
_ => {}
}
} }
} else { } 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) .direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(37),
Constraint::Percentage(33),
]
.as_ref(),
)
.split(f.size()); .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() // Now... draw!
.direction(Direction::Horizontal) self.layout_constraints.iter().enumerate().for_each(
.margin(0) |(row_itx, col_constraint_vec)| {
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)].as_ref()) col_constraint_vec.iter().enumerate().for_each(
.split(vertical_chunks[1]); |(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() for (widget_itx, widget) in self.widget_layout.rows[row_itx]
.direction(Direction::Vertical) .children[col_itx]
.margin(0) .children[col_row_itx]
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .children
.split(middle_chunks[1]); .iter()
.enumerate()
let bottom_chunks = Layout::default() {
.direction(Direction::Horizontal) match widget.widget_type {
.margin(0) Empty => {}
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) Cpu => self.draw_cpu(
.split(vertical_chunks[2]); &mut f,
app_state,
// Component specific chunks widget_draw_locs[widget_itx],
let cpu_chunk = Layout::default() widget.widget_id,
.direction(Direction::Horizontal) ),
.margin(0) Mem => self.draw_memory_graph(
.constraints( &mut f,
if app_state.app_config_fields.left_legend { app_state,
[Constraint::Percentage(15), Constraint::Percentage(85)] widget_draw_locs[widget_itx],
} else { widget.widget_id,
[Constraint::Percentage(85), Constraint::Percentage(15)] ),
} Net => self.draw_network(
.as_ref(), &mut f,
) app_state,
.split(vertical_chunks[0]); widget_draw_locs[widget_itx],
widget.widget_id,
let network_chunk = Layout::default() ),
.direction(Direction::Vertical) Temp => self.draw_temp_table(
.margin(0) &mut f,
.constraints( app_state,
if (bottom_chunks[0].height as f64 * 0.25) as u16 >= 4 { widget_draw_locs[widget_itx],
[Constraint::Percentage(75), Constraint::Percentage(25)] true,
} else { widget.widget_id,
let required = if bottom_chunks[0].height < 10 { ),
bottom_chunks[0].height / 2 Disk => self.draw_disk_table(
} else { &mut f,
5 app_state,
}; widget_draw_locs[widget_itx],
let remaining = bottom_chunks[0].height - required; true,
[Constraint::Length(remaining), Constraint::Length(required)] widget.widget_id,
} ),
.as_ref(), Proc => self.draw_process_and_search(
) &mut f,
.split(bottom_chunks[0]); app_state,
widget_draw_locs[widget_itx],
// Default chunk index based on left or right legend setting true,
let legend_index = if app_state.app_config_fields.left_legend { widget.widget_id,
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);
} }
})?; })?;

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(to_kill_processes) = app_state.get_to_delete_processes() {
if let Some(first_pid) = to_kill_processes.1.first() { if let Some(first_pid) = to_kill_processes.1.first() {
let dd_text = vec![ 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 { if to_kill_processes.1.len() != 1 {
Text::raw(format!( Text::raw(format!(
"\nKill {} processes with the name {}?", "\nKill {} processes with the name {}?",

View File

@ -1,7 +1,7 @@
use std::cmp::max; use std::cmp::max;
use crate::{ use crate::{
app::{App, WidgetPosition}, app::{layout_manager::BottomWidgetType, App},
canvas::Painter, canvas::Painter,
}; };
@ -23,27 +23,19 @@ impl BasicTableArrows for Painter {
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect,
) { ) {
// Effectively a paragraph with a ton of spacing // Effectively a paragraph with a ton of spacing
let (left_table, right_table) =
// TODO: [MODULARITY] This is hard coded. Gross. if let Some(basic_table_widget_state) = &app_state.basic_table_widget_state {
let (left_table, right_table) = if app_state.current_widget_selected.is_widget_table() { match basic_table_widget_state.currently_displayed_widget_type {
match app_state.current_widget_selected { BottomWidgetType::Proc | BottomWidgetType::ProcSearch => {
WidgetPosition::Process | WidgetPosition::ProcessSearch => { (BottomWidgetType::Temp, BottomWidgetType::Disk)
(WidgetPosition::Temp, WidgetPosition::Disk) }
BottomWidgetType::Disk => (BottomWidgetType::Proc, BottomWidgetType::Temp),
BottomWidgetType::Temp => (BottomWidgetType::Disk, BottomWidgetType::Proc),
_ => (BottomWidgetType::Disk, BottomWidgetType::Temp),
} }
WidgetPosition::Disk => (WidgetPosition::Process, WidgetPosition::Temp), } else {
WidgetPosition::Temp => (WidgetPosition::Disk, WidgetPosition::Process), (BottomWidgetType::Disk, BottomWidgetType::Temp)
_ => (WidgetPosition::Disk, WidgetPosition::Temp), };
}
} else {
match app_state.previous_basic_table_selected {
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
(WidgetPosition::Temp, WidgetPosition::Disk)
}
WidgetPosition::Disk => (WidgetPosition::Process, WidgetPosition::Temp),
WidgetPosition::Temp => (WidgetPosition::Disk, WidgetPosition::Process),
_ => (WidgetPosition::Disk, WidgetPosition::Temp),
}
};
let left_name = left_table.get_pretty_name(); let left_name = left_table.get_pretty_name();
let right_name = right_table.get_pretty_name(); let right_name = right_table.get_pretty_name();

View File

@ -1,7 +1,7 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use crate::{ use crate::{
app::{App, WidgetPosition}, app::App,
canvas::{drawing_utils::*, Painter}, canvas::{drawing_utils::*, Painter},
constants::*, constants::*,
data_conversion::ConvertedCpuData, data_conversion::ConvertedCpuData,
@ -15,12 +15,14 @@ use tui::{
}; };
pub trait CpuBasicWidget { 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 { impl CpuBasicWidget for Painter {
fn draw_basic_cpu<B: Backend>( 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; 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 // Then, from this, split the row space across ALL columns. From there, generate
// the desired lengths. // the desired lengths.
if let WidgetPosition::BasicCpu = app_state.current_widget_selected { if app_state.current_widget.widget_id == widget_id {
Block::default() Block::default()
.borders(*SIDE_BORDERS) .borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style) .border_style(self.colours.highlighted_border_style)

View File

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

View File

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

View File

@ -1,10 +1,6 @@
use std::cmp::max; use std::cmp::max;
use crate::{ use crate::{app::App, canvas::Painter, constants::*};
app::{App, WidgetPosition},
canvas::Painter,
constants::*,
};
use tui::{ use tui::{
backend::Backend, backend::Backend,
@ -15,109 +11,112 @@ use tui::{
pub trait MemGraphWidget { pub trait MemGraphWidget {
fn draw_memory_graph<B: Backend>( 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 { impl MemGraphWidget for Painter {
fn draw_memory_graph<B: Backend>( 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; if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data; 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 = [ let display_time_labels = [
format!("{}s", app_state.mem_state.current_display_time / 1000), format!("{}s", mem_widget_state.current_display_time / 1000),
"0s".to_string(), "0s".to_string(),
]; ];
let x_axis = if app_state.app_config_fields.hide_time let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time || (app_state.app_config_fields.autohide_time
&& app_state.mem_state.autohide_timer.is_none()) && mem_widget_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
{ {
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() 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) .style(self.colours.graph_style)
.labels_style(self.colours.graph_style) .labels_style(self.colours.graph_style)
.labels(&display_time_labels) .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]) // Offset as the zero value isn't drawn otherwise...
} let y_axis: Axis<'_, &str> = Axis::default()
} else {
Axis::default()
.bounds([0.0, app_state.mem_state.current_display_time as f64])
.style(self.colours.graph_style) .style(self.colours.graph_style)
.labels_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 mem_canvas_vec: Vec<Dataset<'_>> = vec![
let y_axis: Axis<'_, &str> = Axis::default() Dataset::default()
.style(self.colours.graph_style) .name(&app_state.canvas_data.mem_label)
.labels_style(self.colours.graph_style) .marker(if app_state.app_config_fields.use_dot {
.bounds([-0.5, 100.5]) Marker::Dot
.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
} else { } else {
self.colours.widget_title_style Marker::Braille
}) })
.borders(Borders::ALL) .style(self.colours.ram_style)
.border_style(match app_state.current_widget_selected { .data(&mem_data),
WidgetPosition::Mem => self.colours.highlighted_border_style, Dataset::default()
_ => self.colours.border_style, .name(&app_state.canvas_data.swap_label)
}), .marker(if app_state.app_config_fields.use_dot {
) Marker::Dot
.x_axis(x_axis) } else {
.y_axis(y_axis) Marker::Braille
.datasets(&mem_canvas_vec) })
.render(f, draw_loc); .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::{ use crate::{app::App, canvas::Painter, constants::*};
app::{App, WidgetPosition},
canvas::Painter,
constants::*,
};
use tui::{ use tui::{
backend::Backend, backend::Backend,
@ -13,13 +9,13 @@ use tui::{
pub trait NetworkBasicWidget { pub trait NetworkBasicWidget {
fn draw_basic_network<B: Backend>( 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 { impl NetworkBasicWidget for Painter {
fn draw_basic_network<B: Backend>( 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() let divided_loc = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
@ -38,7 +34,7 @@ impl NetworkBasicWidget for Painter {
.horizontal_margin(1) .horizontal_margin(1)
.split(divided_loc[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() Block::default()
.borders(*SIDE_BORDERS) .borders(*SIDE_BORDERS)
.border_style(self.colours.highlighted_border_style) .border_style(self.colours.highlighted_border_style)

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
// Default widget ID
pub const DEFAULT_WIDGET_ID: u64 = 56709;
// How long to store data. // How long to store data.
pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of 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] = [ pub const PROCESS_HELP_TEXT: [&str; 8] = [
"Process Keybindings\n\n", "Process Keybindings\n\n",
"dd Kill the highlighted process\n", "dd, Delete Kill the highlighted process\n",
"c Sort by CPU usage\n", "c Sort by CPU usage\n",
"m Sort by memory usage\n", "m Sort by memory usage\n",
"p Sort by PID\n", "p Sort by PID\n",
@ -134,20 +137,16 @@ pub const DEFAULT_CONFIG_CONTENT: &str = r##"
#temperature_type = "fahrenheit" #temperature_type = "fahrenheit"
#temperature_type = "celsius" #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). # The default time interval (in milliseconds).
#default_time_value = 60000 #default_time_value = 60000
# The time delta on each zoom in/out action (in milliseconds). # The time delta on each zoom in/out action (in milliseconds).
#time_delta = 15000 #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 # These are all the components that support custom theming. Currently, it only
# supports taking in a string representing a hex colour. Note that colour support # supports taking in a string representing a hex colour. Note that colour support
# will, at the end of the day, depend on terminal support - for example, the # 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. # Represents the cursor's colour.
#cursor_color="#458588" #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( 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> { ) -> Vec<ConvertedCpuData> {
let mut cpu_data_vector: Vec<ConvertedCpuData> = Vec::new(); let mut cpu_data_vector: Vec<ConvertedCpuData> = Vec::new();
let current_time = if is_frozen { 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 { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
for (itx, cpu) in data.cpu_data.iter().enumerate() { for (itx, cpu) in data.cpu_data.iter().enumerate() {
// Check if the vector exists yet // Check if the vector exists yet
@ -133,15 +132,15 @@ pub fn convert_cpu_data_points(
//Insert joiner points //Insert joiner points
for &(joiner_offset, joiner_val) in &cpu.1 { 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_vector[itx_offset]
.cpu_data .cpu_data
.push((offset_time, joiner_val)); .push((-offset_time, joiner_val));
} }
cpu_data_vector[itx_offset] cpu_data_vector[itx_offset]
.cpu_data .cpu_data
.push((time_from_start, cpu.0)); .push((-time_from_start, cpu.0));
} }
if *time == current_time { if *time == current_time {
@ -153,7 +152,7 @@ pub fn convert_cpu_data_points(
} }
pub fn convert_mem_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> { ) -> Vec<Point> {
let mut result: Vec<Point> = Vec::new(); let mut result: Vec<Point> = Vec::new();
let current_time = if is_frozen { 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 { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points //Insert joiner points
for &(joiner_offset, joiner_val) in &data.mem_data.1 { for &(joiner_offset, joiner_val) in &data.mem_data.1 {
let offset_time = time_from_start - joiner_offset as f64; let offset_time = time_from_start + joiner_offset as f64;
result.push((offset_time, joiner_val)); 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 { if *time == current_time {
break; break;
@ -187,7 +185,7 @@ pub fn convert_mem_data_points(
} }
pub fn convert_swap_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> { ) -> Vec<Point> {
let mut result: Vec<Point> = Vec::new(); let mut result: Vec<Point> = Vec::new();
let current_time = if is_frozen { 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 { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points //Insert joiner points
for &(joiner_offset, joiner_val) in &data.swap_data.1 { for &(joiner_offset, joiner_val) in &data.swap_data.1 {
let offset_time = time_from_start - joiner_offset as f64; let offset_time = time_from_start + joiner_offset as f64;
result.push((offset_time, joiner_val)); 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 { if *time == current_time {
break; break;
@ -257,7 +254,7 @@ pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String
} }
pub fn get_rx_tx_data_points( 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>) { ) -> (Vec<Point>, Vec<Point>) {
let mut rx: Vec<Point> = Vec::new(); let mut rx: Vec<Point> = Vec::new();
let mut tx: 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? // TODO: [REFACTOR] Can we use collect on this, CPU, and MEM?
for (time, data) in &current_data.timed_data_vec { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
(display_time as f64 - current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points //Insert joiner points
for &(joiner_offset, joiner_val) in &data.rx_data.1 { for &(joiner_offset, joiner_val) in &data.rx_data.1 {
let offset_time = time_from_start - joiner_offset as f64; let offset_time = time_from_start + joiner_offset as f64;
rx.push((offset_time, joiner_val)); rx.push((-offset_time, joiner_val));
} }
for &(joiner_offset, joiner_val) in &data.tx_data.1 { for &(joiner_offset, joiner_val) in &data.tx_data.1 {
let offset_time = time_from_start - joiner_offset as f64; let offset_time = time_from_start + joiner_offset as f64;
tx.push((offset_time, joiner_val)); tx.push((-offset_time, joiner_val));
} }
rx.push((time_from_start, data.rx_data.0)); rx.push((-time_from_start, data.rx_data.0));
tx.push((time_from_start, data.tx_data.0)); tx.push((-time_from_start, data.tx_data.0));
if *time == current_time { if *time == current_time {
break; break;
@ -300,9 +296,9 @@ pub fn get_rx_tx_data_points(
} }
pub fn convert_network_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 { ) -> 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 total_rx_converted_result: (f64, String);
let 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 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 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.") (@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 DEFAULT_WIDGET_TYPE: --default_widget_type +takes_value "The default widget type to select by default.")
(@arg CPU_WIDGET: --cpu_default "Selects the CPU widget to be selected 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 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 TURNED_OFF_CPUS: -t ... +takes_value "Hides CPU data points by default") // TODO: [FEATURE] Enable disabling cores in config/flags //(@arg TURNED_OFF_CPUS: -t ... +takes_value "Hides CPU data points by default") // TODO: [FEATURE] Enable disabling cores in config/flags
) )
.get_matches() .get_matches()
@ -105,14 +99,11 @@ fn main() -> error::Result<()> {
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
// Create "app" struct, which will control most of the program and store settings/state // Get widget layout separately
let mut app = build_app(&matches, &config)?; let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?;
// TODO: [REFACTOR] Change this // Create "app" struct, which will control most of the program and store settings/state
enable_app_grouping(&matches, &config, &mut app); let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?;
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);
// Set up up tui and crossterm // Set up up tui and crossterm
let mut stdout_val = stdout(); let mut stdout_val = stdout();
@ -150,13 +141,13 @@ fn main() -> error::Result<()> {
app.app_config_fields.show_average_cpu, 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) { if let Err(config_check) = generate_config_colours(&config, &mut painter) {
cleanup_terminal(&mut terminal)?; cleanup_terminal(&mut terminal)?;
return Err(config_check); return Err(config_check);
} }
painter.colours.generate_remaining_cpu_colours(); painter.colours.generate_remaining_cpu_colours();
painter.initialize(); painter.complete_painter_init();
let mut first_run = true; let mut first_run = true;
loop { loop {
@ -179,11 +170,7 @@ fn main() -> error::Result<()> {
// Convert all data into tui-compliant components // Convert all data into tui-compliant components
// Network // Network
let network_data = convert_network_data_points( let network_data = convert_network_data_points(&app.data_collection, false);
&app.data_collection,
app.net_state.current_display_time,
false,
);
app.canvas_data.network_data_rx = network_data.rx; app.canvas_data.network_data_rx = network_data.rx;
app.canvas_data.network_data_tx = network_data.tx; app.canvas_data.network_data_tx = network_data.tx;
app.canvas_data.rx_display = network_data.rx_display; app.canvas_data.rx_display = network_data.rx_display;
@ -196,17 +183,12 @@ fn main() -> error::Result<()> {
// Temperatures // Temperatures
app.canvas_data.temp_sensor_data = convert_temp_row(&app); app.canvas_data.temp_sensor_data = convert_temp_row(&app);
// Memory // Memory
app.canvas_data.mem_data = convert_mem_data_points( app.canvas_data.mem_data =
&app.data_collection, convert_mem_data_points(&app.data_collection, false);
app.mem_state.current_display_time, app.canvas_data.swap_data =
false, convert_swap_data_points(&app.data_collection, false);
);
app.canvas_data.swap_data = convert_swap_data_points(
&app.data_collection,
app.mem_state.current_display_time,
false,
);
let memory_and_swap_labels = convert_mem_labels(&app.data_collection); let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
app.canvas_data.mem_label = memory_and_swap_labels.0; app.canvas_data.mem_label = memory_and_swap_labels.0;
app.canvas_data.swap_label = memory_and_swap_labels.1; app.canvas_data.swap_label = memory_and_swap_labels.1;
@ -214,23 +196,23 @@ fn main() -> error::Result<()> {
// Pre-fill CPU if needed // Pre-fill CPU if needed
if first_run { if first_run {
let cpu_len = app.data_collection.cpu_harvest.len(); let cpu_len = app.data_collection.cpu_harvest.len();
app.cpu_state.core_show_vec = vec![true; cpu_len]; app.cpu_state.widget_states.values_mut().for_each(|state| {
app.cpu_state.num_cpus_shown = cpu_len as u64; 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; first_run = false;
} }
// CPU // CPU
app.canvas_data.cpu_data = convert_cpu_data_points( app.canvas_data.cpu_data =
&app.data_collection, convert_cpu_data_points(&app.data_collection, false);
app.cpu_state.current_display_time,
false,
);
// Processes // Processes
let (single, grouped) = convert_process_data(&app.data_collection); let (single, grouped) = convert_process_data(&app.data_collection);
app.canvas_data.process_data = single; app.canvas_data.process_data = single;
app.canvas_data.grouped_process_data = grouped; app.canvas_data.grouped_process_data = grouped;
update_final_process_list(&mut app); update_all_process_lists(&mut app);
} }
} }
BottomEvent::Clean => { 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)?; try_drawing(&mut terminal, &mut app, &mut painter)?;
} }
@ -292,19 +266,13 @@ fn handle_key_event_or_break(
KeyCode::Backspace => app.on_backspace(), KeyCode::Backspace => app.on_backspace(),
KeyCode::Delete => app.on_delete(), KeyCode::Delete => app.on_delete(),
KeyCode::F(1) => { KeyCode::F(1) => {
if app.is_in_search_widget() { app.toggle_ignore_case();
app.toggle_ignore_case();
}
} }
KeyCode::F(2) => { KeyCode::F(2) => {
if app.is_in_search_widget() { app.toggle_search_whole_word();
app.toggle_search_whole_word();
}
} }
KeyCode::F(3) => { 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 { if let KeyModifiers::ALT = event.modifiers {
match event.code { match event.code {
KeyCode::Char('c') | KeyCode::Char('C') => { 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') => { 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') => { 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) { fn handle_force_redraws(app: &mut App) {
if app.force_update_processes { // Currently we use an Option... because we might want to future-proof this
update_final_process_list(app); // if we eventually get widget-specific redrawing!
app.force_update_processes = false; 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 { if app.cpu_state.force_update.is_some() {
app.canvas_data.cpu_data = convert_cpu_data_points( app.canvas_data.cpu_data = convert_cpu_data_points(&app.data_collection, app.is_frozen);
&app.data_collection, app.cpu_state.force_update = None;
app.cpu_state.current_display_time,
app.is_frozen,
);
app.cpu_state.force_update = false;
} }
if app.mem_state.force_update { if app.mem_state.force_update.is_some() {
app.canvas_data.mem_data = convert_mem_data_points( app.canvas_data.mem_data = convert_mem_data_points(&app.data_collection, app.is_frozen);
&app.data_collection, app.canvas_data.swap_data = convert_swap_data_points(&app.data_collection, app.is_frozen);
app.mem_state.current_display_time, app.mem_state.force_update = None;
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.net_state.force_update { if app.net_state.force_update.is_some() {
let (rx, tx) = get_rx_tx_data_points( let (rx, tx) = get_rx_tx_data_points(&app.data_collection, app.is_frozen);
&app.data_collection,
app.net_state.current_display_time,
app.is_frozen,
);
app.canvas_data.network_data_rx = rx; app.canvas_data.network_data_rx = rx;
app.canvas_data.network_data_tx = tx; 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) { fn update_all_process_lists(app: &mut App) {
let mut filtered_process_data: Vec<ConvertedProcessData> = if app.is_grouped() { 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 app.canvas_data
.grouped_process_data .grouped_process_data
.iter() .iter()
.filter(|process| { .filter(|process| {
if app if is_invalid_or_blank {
.process_search_state
.search_state
.is_invalid_or_blank_search()
{
return true; 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 { if let Ok(matcher) = matcher_result {
return matcher.is_match(&process.name); return matcher.is_match(&process.name);
} }
@ -613,20 +581,20 @@ fn update_final_process_list(app: &mut App) {
.cloned() .cloned()
.collect::<Vec<_>>() .collect::<Vec<_>>()
} else { } 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 app.canvas_data
.process_data .process_data
.iter() .iter()
.filter_map(|(_pid, process)| { .filter_map(|(_pid, process)| {
let mut result = true; let mut result = true;
if !is_invalid_or_blank {
if !app if let Some(matcher_result) = app.get_current_regex_matcher(widget_id) {
.process_search_state
.search_state
.is_invalid_or_blank_search()
{
if let Some(matcher_result) = app.get_current_regex_matcher() {
if let Ok(matcher) = matcher_result { 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()); result = matcher.is_match(&process.pid.to_string());
} else { } else {
result = matcher.is_match(&process.name); result = matcher.is_match(&process.name);
@ -650,31 +618,84 @@ fn update_final_process_list(app: &mut App) {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
sort_process_data(&mut filtered_process_data, app); // Quick fix for tab updating the table headers
app.canvas_data.finalized_process_data = filtered_process_data; 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)); 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 => { ProcessSorting::CPU => {
to_sort_vec.sort_by(|a, b| { 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 => { ProcessSorting::MEM => {
to_sort_vec.sort_by(|a, b| { 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| { ProcessSorting::NAME => {
utils::gen_util::get_ordering(&a.name, &b.name, app.process_sorting_reverse) // Don't repeat if false...
}), if proc_widget_state.process_sorting_reverse {
ProcessSorting::PID => {
if !app.is_grouped() {
to_sort_vec.sort_by(|a, b| { 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 serde::Deserialize;
use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
use crate::{ 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::*, constants::*,
utils::error::{self, BottomError}, utils::error::{self, BottomError},
}; };
// use layout_manager::*; use layout_options::*;
// mod layout_manager; mod layout_options;
#[derive(Default, Deserialize)] #[derive(Default, Deserialize)]
pub struct Config { pub struct Config {
pub flags: Option<ConfigFlags>, pub flags: Option<ConfigFlags>,
pub colors: Option<ConfigColours>, pub colors: Option<ConfigColours>,
pub row: Option<Vec<Row>>,
} }
#[derive(Default, Deserialize)] #[derive(Default, Deserialize)]
@ -37,6 +42,8 @@ pub struct ConfigFlags {
pub time_delta: Option<u64>, pub time_delta: Option<u64>,
pub autohide_time: Option<bool>, pub autohide_time: Option<bool>,
pub hide_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 //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 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 autohide_time = get_autohide_time(&matches, &config);
let default_time_value = get_default_time_value(&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 use_basic_mode = get_use_basic_mode(&matches, &config);
let current_widget_selected = if use_basic_mode { // For processes
match default_widget { let is_grouped = get_app_grouping(matches, config);
WidgetPosition::Cpu => WidgetPosition::BasicCpu, let is_case_sensitive = get_app_case_sensitive(matches, config);
WidgetPosition::Network => WidgetPosition::BasicNet, let is_match_whole_word = get_app_match_whole_word(matches, config);
WidgetPosition::Mem => WidgetPosition::BasicMem, let is_use_regex = get_app_use_regex(matches, config);
_ => default_widget,
} 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 { } else {
default_widget None
}; };
let previous_basic_table_selected = if default_widget.is_widget_table() { let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?;
default_widget 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 { } else {
WidgetPosition::Process None
}; };
let app_config_fields = AppConfigFields { let app_config_fields = AppConfigFields {
@ -98,22 +213,62 @@ pub fn build_app(matches: &clap::ArgMatches<'static>, config: &Config) -> error:
autohide_time, autohide_time,
}; };
let time_now = if autohide_time {
Some(Instant::now())
} else {
None
};
Ok(App::builder() Ok(App::builder()
.app_config_fields(app_config_fields) .app_config_fields(app_config_fields)
.current_widget_selected(current_widget_selected) .cpu_state(CpuState::init(cpu_state_map))
.previous_basic_table_selected(previous_basic_table_selected) .mem_state(MemState::init(mem_state_map))
.cpu_state(CpuState::init(default_time_value, time_now)) .net_state(NetState::init(net_state_map))
.mem_state(MemState::init(default_time_value, time_now)) .proc_state(ProcState::init(proc_state_map))
.net_state(NetState::init(default_time_value, time_now)) .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()) .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( fn get_update_rate_in_milliseconds(
matches: &clap::ArgMatches<'static>, config: &Config, matches: &clap::ArgMatches<'static>, config: &Config,
) -> error::Result<u64> { ) -> error::Result<u64> {
@ -296,56 +451,56 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er
Ok(time_interval as u64) 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") { if matches.is_present("GROUP_PROCESSES") {
app.toggle_grouping(); return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(grouping) = flags.group_processes { if let Some(grouping) = flags.group_processes {
if grouping { if grouping {
app.toggle_grouping(); return true;
} }
} }
} }
false
} }
pub fn enable_app_case_sensitive( pub fn get_app_case_sensitive(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
) {
if matches.is_present("CASE_SENSITIVE") { if matches.is_present("CASE_SENSITIVE") {
app.process_search_state.search_toggle_ignore_case(); return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(case_sensitive) = flags.case_sensitive { if let Some(case_sensitive) = flags.case_sensitive {
if case_sensitive { if case_sensitive {
app.process_search_state.search_toggle_ignore_case(); return true;
} }
} }
} }
false
} }
pub fn enable_app_match_whole_word( pub fn get_app_match_whole_word(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
matches: &clap::ArgMatches<'static>, config: &Config, app: &mut App,
) {
if matches.is_present("WHOLE_WORD") { if matches.is_present("WHOLE_WORD") {
app.process_search_state.search_toggle_whole_word(); return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(whole_word) = flags.whole_word { if let Some(whole_word) = flags.whole_word {
if 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") { if matches.is_present("REGEX_DEFAULT") {
app.process_search_state.search_toggle_regex(); return true;
} else if let Some(flags) = &config.flags { } else if let Some(flags) = &config.flags {
if let Some(regex) = flags.regex { if let Some(regex) = flags.regex {
if regex { if regex {
app.process_search_state.search_toggle_regex(); return true;
} }
} }
} }
false
} }
fn get_hide_time(matches: &clap::ArgMatches<'static>, config: &Config) -> bool { 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 false
} }
fn get_default_widget(matches: &clap::ArgMatches<'static>, config: &Config) -> WidgetPosition { fn get_default_widget_and_count(
if matches.is_present("CPU_WIDGET") { matches: &clap::ArgMatches<'static>, config: &Config,
return WidgetPosition::Cpu; ) -> error::Result<(Option<BottomWidgetType>, u64)> {
} else if matches.is_present("MEM_WIDGET") { let widget_type = if let Some(widget_type) = matches.value_of("DEFAULT_WIDGET_TYPE") {
return WidgetPosition::Mem; let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
} else if matches.is_present("DISK_WIDGET") { if let BottomWidgetType::Empty = parsed_widget {
return WidgetPosition::Disk; None
} else if matches.is_present("TEMP_WIDGET") { } else {
return WidgetPosition::Temp; Some(parsed_widget)
} 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,
};
} }
} } 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] #[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()) Command::new(get_os_binary_loc())
.arg("--cpu_default") .arg("--default_widget_type")
.arg("--disk_default") .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() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .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(()) Ok(())