bottom/src/canvas.rs

426 lines
17 KiB
Rust
Raw Normal View History

use std::cmp::max;
2020-02-02 04:49:44 +00:00
use std::collections::HashMap;
2020-02-29 03:24:24 +00:00
use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
2020-02-29 22:07:47 +00:00
terminal::Frame,
widgets::Text,
2020-02-29 22:07:47 +00:00
Terminal,
2019-09-11 03:37:20 +00:00
};
2019-09-12 02:10:49 +00:00
2020-02-08 23:00:50 +00:00
use canvas_colours::*;
use dialogs::*;
use widgets::*;
2020-02-10 02:34:44 +00:00
2020-02-29 03:24:24 +00:00
use crate::{
2020-02-29 22:07:47 +00:00
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition},
constants::*,
data_conversion::{ConvertedCpuData, ConvertedProcessData},
utils::error,
2020-02-29 03:24:24 +00:00
};
mod canvas_colours;
mod dialogs;
2020-02-10 02:34:44 +00:00
mod drawing_utils;
mod widgets;
2019-09-12 02:10:49 +00:00
#[derive(Default)]
2020-02-02 04:49:44 +00:00
pub struct DisplayableData {
2020-02-29 22:05:01 +00:00
pub rx_display: String,
pub tx_display: String,
pub total_rx_display: String,
pub total_tx_display: String,
pub network_data_rx: Vec<(f64, f64)>,
pub network_data_tx: Vec<(f64, f64)>,
pub disk_data: Vec<Vec<String>>,
pub temp_sensor_data: Vec<Vec<String>>,
2020-03-08 23:47:10 +00:00
// Not the final value
2020-02-29 22:05:01 +00:00
pub process_data: HashMap<u32, ProcessHarvest>,
// Not the final value
pub grouped_process_data: Vec<ConvertedProcessData>,
// What's actually displayed
2020-03-08 23:47:10 +00:00
pub finalized_process_data: Vec<ConvertedProcessData>,
2020-02-29 22:05:01 +00:00
pub mem_label: String,
pub swap_label: String,
pub mem_data: Vec<(f64, f64)>,
pub swap_data: Vec<(f64, f64)>,
pub cpu_data: Vec<ConvertedCpuData>,
2019-09-12 02:10:49 +00:00
}
2020-02-05 04:21:44 +00:00
#[allow(dead_code)]
#[derive(Default)]
/// Handles the canvas' state. TODO: [OPT] implement this.
2020-02-05 04:21:44 +00:00
pub struct Painter {
2020-02-29 22:05:01 +00:00
height: u16,
width: u16,
vertical_dialog_chunk: Vec<Rect>,
middle_dialog_chunk: Vec<Rect>,
vertical_chunks: Vec<Rect>,
middle_chunks: Vec<Rect>,
middle_divided_chunk_2: Vec<Rect>,
bottom_chunks: Vec<Rect>,
cpu_chunk: Vec<Rect>,
network_chunk: Vec<Rect>,
pub colours: CanvasColours,
pub styled_general_help_text: Vec<Text<'static>>,
pub styled_process_help_text: Vec<Text<'static>>,
pub styled_search_help_text: Vec<Text<'static>>,
is_mac_os: bool,
}
impl Painter {
2020-02-29 22:05:01 +00:00
/// Must be run once before drawing, but after setting colours.
/// This is to set some remaining styles and text.
pub fn initialize(&mut self) {
self.is_mac_os = cfg!(target_os = "macos");
2020-03-09 04:52:29 +00:00
if GENERAL_HELP_TEXT.len() > 1 {
self.styled_general_help_text.push(Text::Styled(
GENERAL_HELP_TEXT[0].into(),
self.colours.table_header_style,
));
self.styled_general_help_text.extend(
GENERAL_HELP_TEXT[1..]
.iter()
.map(|&text| Text::Styled(text.into(), self.colours.text_style))
.collect::<Vec<_>>(),
);
}
2020-02-29 22:05:01 +00:00
2020-03-09 04:52:29 +00:00
if PROCESS_HELP_TEXT.len() > 1 {
self.styled_process_help_text.push(Text::Styled(
PROCESS_HELP_TEXT[0].into(),
self.colours.table_header_style,
));
self.styled_process_help_text.extend(
PROCESS_HELP_TEXT[1..]
.iter()
.map(|&text| Text::Styled(text.into(), self.colours.text_style))
.collect::<Vec<_>>(),
);
}
2020-02-29 22:05:01 +00:00
2020-03-09 04:52:29 +00:00
if SEARCH_HELP_TEXT.len() > 1 {
self.styled_search_help_text.push(Text::Styled(
SEARCH_HELP_TEXT[0].into(),
self.colours.table_header_style,
));
self.styled_search_help_text.extend(
SEARCH_HELP_TEXT[1..]
.iter()
.map(|&text| Text::Styled(text.into(), self.colours.text_style))
.collect::<Vec<_>>(),
);
}
2020-02-29 22:05:01 +00:00
}
pub fn draw_specific_table<B: Backend>(
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, draw_border: bool,
widget_selected: WidgetPosition,
) {
match widget_selected {
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
self.draw_process_and_search(f, app_state, draw_loc, draw_border)
}
WidgetPosition::Temp => self.draw_temp_table(f, app_state, draw_loc, draw_border),
WidgetPosition::Disk => self.draw_disk_table(f, app_state, draw_loc, draw_border),
_ => {}
}
}
2020-02-29 22:05:01 +00:00
// TODO: [FEATURE] Auto-resizing dialog sizes.
pub fn draw_data<B: Backend>(
2020-02-29 22:05:01 +00:00
&mut self, terminal: &mut Terminal<B>, app_state: &mut app::App,
) -> error::Result<()> {
let terminal_size = terminal.size()?;
let current_height = terminal_size.height;
let current_width = terminal_size.width;
if self.height == 0 && self.width == 0 {
self.height = current_height;
self.width = current_width;
} else if self.height != current_height || self.width != current_width {
app_state.is_resized = true;
self.height = current_height;
self.width = current_width;
2020-02-29 22:05:01 +00:00
}
terminal.autoresize()?;
terminal.draw(|mut f| {
if app_state.help_dialog_state.is_showing_help {
// TODO: [RESIZE] Scrolling dialog boxes is ideal. This is currently VERY temporary!
// The width is currently not good and can wrap... causing this to not go so well!
let gen_help_len = GENERAL_HELP_TEXT.len() as u16 + 3;
let border_len = (max(0, f.size().height as i64 - gen_help_len as i64)) as u16 / 2;
2020-02-29 22:05:01 +00:00
let vertical_dialog_chunk = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(border_len),
Constraint::Length(gen_help_len),
Constraint::Length(border_len),
2020-02-29 22:05:01 +00:00
]
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(f.size());
let middle_dialog_chunk = Layout::default()
.direction(Direction::Horizontal)
.constraints(
if f.size().width < 100 {
// TODO: [REFACTOR] The point we start changing size at currently hard-coded in.
[
Constraint::Percentage(0),
Constraint::Percentage(100),
Constraint::Percentage(0),
]
} else {
[
Constraint::Percentage(20),
Constraint::Percentage(60),
Constraint::Percentage(20),
]
}
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(vertical_dialog_chunk[1]);
self.draw_help_dialog(&mut f, app_state, middle_dialog_chunk[1]);
2020-02-29 22:05:01 +00:00
} else if app_state.delete_dialog_state.is_showing_dd {
let bordering = (max(0, f.size().height as i64 - 7) as u16) / 2;
2020-02-29 22:05:01 +00:00
let vertical_dialog_chunk = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(bordering),
Constraint::Length(7),
Constraint::Length(bordering),
2020-02-29 22:05:01 +00:00
]
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(f.size());
let middle_dialog_chunk = Layout::default()
.direction(Direction::Horizontal)
.constraints(
if f.size().width < 100 {
// TODO: [REFACTOR] The point we start changing size at currently hard-coded in.
[
Constraint::Percentage(5),
Constraint::Percentage(90),
Constraint::Percentage(5),
]
} else {
[
Constraint::Percentage(30),
Constraint::Percentage(40),
Constraint::Percentage(30),
]
}
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(vertical_dialog_chunk[1]);
if let Some(dd_err) = &app_state.dd_err {
self.draw_dd_error_dialog(&mut f, dd_err, middle_dialog_chunk[1]);
2020-02-29 22:05:01 +00:00
} else {
// This is a bit nasty, but it works well... I guess.
app_state.delete_dialog_state.is_showing_dd =
self.draw_dd_dialog(&mut f, app_state, middle_dialog_chunk[1]);
2020-02-29 22:05:01 +00:00
}
} else if app_state.is_expanded {
let rect = Layout::default()
.margin(1)
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size());
match &app_state.current_widget_selected {
WidgetPosition::Cpu | WidgetPosition::BasicCpu | WidgetPosition::CpuLegend => {
2020-02-29 22:05:01 +00:00
let cpu_chunk = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints(
if app_state.app_config_fields.left_legend {
[Constraint::Percentage(15), Constraint::Percentage(85)]
} else {
[Constraint::Percentage(85), Constraint::Percentage(15)]
}
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(rect[0]);
let legend_index = if app_state.app_config_fields.left_legend {
0
} else {
1
};
let graph_index = if app_state.app_config_fields.left_legend {
1
} else {
0
};
2020-03-09 04:52:29 +00:00
self.draw_cpu_graph(&mut f, app_state, cpu_chunk[graph_index]);
2020-02-29 22:05:01 +00:00
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
}
WidgetPosition::Mem | WidgetPosition::BasicMem => {
2020-03-09 04:52:29 +00:00
self.draw_memory_graph(&mut f, app_state, rect[0]);
2020-02-29 22:05:01 +00:00
}
WidgetPosition::Disk => {
self.draw_disk_table(&mut f, app_state, rect[0], true);
2020-02-29 22:05:01 +00:00
}
WidgetPosition::Temp => {
self.draw_temp_table(&mut f, app_state, rect[0], true);
2020-02-29 22:05:01 +00:00
}
WidgetPosition::Network
| WidgetPosition::BasicNet
| WidgetPosition::NetworkLegend => {
2020-03-09 04:52:29 +00:00
self.draw_network_graph(&mut f, app_state, rect[0]);
2020-02-29 22:05:01 +00:00
}
WidgetPosition::Process | WidgetPosition::ProcessSearch => {
self.draw_process_and_search(&mut f, app_state, rect[0], true);
2020-02-29 22:05:01 +00:00
}
}
} else if app_state.app_config_fields.use_basic_mode {
// Basic mode. This basically removes all graphs but otherwise
// the same info.
let cpu_height = (app_state.canvas_data.cpu_data.len() / 4) as u16
+ (if app_state.canvas_data.cpu_data.len() % 4 == 0 {
0
} else {
1
});
let vertical_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(cpu_height),
Constraint::Length(1),
Constraint::Length(2),
Constraint::Length(2),
Constraint::Min(5),
]
.as_ref(),
)
.split(f.size());
let middle_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(vertical_chunks[2]);
self.draw_basic_cpu(&mut f, app_state, vertical_chunks[0]);
self.draw_basic_memory(&mut f, app_state, middle_chunks[0]);
self.draw_basic_network(&mut f, app_state, middle_chunks[1]);
2020-03-03 05:54:49 +00:00
self.draw_basic_table_arrows(&mut f, app_state, vertical_chunks[3]);
if app_state.current_widget_selected.is_widget_table() {
self.draw_specific_table(
&mut f,
app_state,
vertical_chunks[4],
false,
app_state.current_widget_selected,
);
} else {
self.draw_specific_table(
&mut f,
app_state,
vertical_chunks[4],
false,
app_state.previous_basic_table_selected,
);
}
2020-02-29 22:05:01 +00:00
} else {
let vertical_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Percentage(30),
Constraint::Percentage(37),
Constraint::Percentage(33),
]
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(f.size());
let middle_chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)].as_ref())
.split(vertical_chunks[1]);
let middle_divided_chunk_2 = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(middle_chunks[1]);
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(vertical_chunks[2]);
// Component specific chunks
let cpu_chunk = Layout::default()
.direction(Direction::Horizontal)
.margin(0)
.constraints(
if app_state.app_config_fields.left_legend {
[Constraint::Percentage(15), Constraint::Percentage(85)]
} else {
[Constraint::Percentage(85), Constraint::Percentage(15)]
}
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(vertical_chunks[0]);
let network_chunk = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints(
if (bottom_chunks[0].height as f64 * 0.25) as u16 >= 4 {
[Constraint::Percentage(75), Constraint::Percentage(25)]
} else {
let required = if bottom_chunks[0].height < 10 {
bottom_chunks[0].height / 2
} else {
5
};
let remaining = bottom_chunks[0].height - required;
[Constraint::Length(remaining), Constraint::Length(required)]
}
.as_ref(),
2020-02-29 22:05:01 +00:00
)
.split(bottom_chunks[0]);
// Default chunk index based on left or right legend setting
let legend_index = if app_state.app_config_fields.left_legend {
0
} else {
1
};
let graph_index = if app_state.app_config_fields.left_legend {
1
} else {
0
};
2020-03-09 04:52:29 +00:00
self.draw_cpu_graph(&mut f, app_state, cpu_chunk[graph_index]);
2020-02-29 22:05:01 +00:00
self.draw_cpu_legend(&mut f, app_state, cpu_chunk[legend_index]);
2020-03-09 04:52:29 +00:00
self.draw_memory_graph(&mut f, app_state, middle_chunks[0]);
self.draw_network_graph(&mut f, app_state, network_chunk[0]);
2020-02-29 22:05:01 +00:00
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);
2020-02-29 22:05:01 +00:00
}
})?;
app_state.is_resized = false;
Ok(())
}
}