From 2900ae2acf8757eeace7359e046b0a391197039c Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Wed, 9 Oct 2019 22:00:10 -0400 Subject: Added help screen. --- README.md | 10 +- src/app.rs | 118 +++++----- src/canvas.rs | 684 +++++++++++++++++++++++++++++-------------------------- src/constants.rs | 2 +- src/main.rs | 9 +- 5 files changed, 438 insertions(+), 385 deletions(-) diff --git a/README.md b/README.md index 76637f9a..417ca136 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,19 @@ Currently, I'm unable to really dev or test on MacOS, so I'm not sure how well t #### General -- `q`, `Esc`, or `Ctrl-C` to quit. +- `q`, `Ctrl-C` to quit. - `Up/k`, `Down/j`, `Left/h`, `Right/l` to navigate between panels. -- `Shift+Up` and `Shift+Down` scrolls through the list if the panel is a table (Temperature, Disks, Processes) +- `Shift+Up` and `Shift+Down` scrolls through the list if the panel is a table (Temperature, Disks, Processes). + +- `Esc` to close a dialog window (help or dd confirmation). + +- `?` to get a help screen explaining the controls. #### Processes Panel -- `dd` to kill the selected process (currently only on Linux) - **I would highly recommend you to be careful using this, lest you accidentally kill the wrong process**. +- `dd` to kill the selected process - **I would highly recommend you to be careful using this, lest you accidentally kill the wrong process**. - `c` to sort by CPU usage. Sorts in descending order by default. Press again to reverse sorting order. diff --git a/src/app.rs b/src/app.rs index 83f0a010..3c8aeb3b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -21,7 +21,6 @@ pub enum ScrollDirection { } pub struct App { - pub should_quit : bool, pub process_sorting_type : processes::ProcessSorting, pub process_sorting_reverse : bool, pub to_be_resorted : bool, @@ -40,13 +39,13 @@ pub struct App { pub previous_process_position : i64, awaiting_second_d : bool, pub use_dot : bool, + pub show_help : bool, } impl App { pub fn new(show_average_cpu : bool, temperature_type : temperature::TemperatureType, update_rate_in_milliseconds : u64, use_dot : bool) -> App { App { process_sorting_type : processes::ProcessSorting::CPU, - should_quit : false, process_sorting_reverse : true, to_be_resorted : false, currently_selected_process_position : 0, @@ -64,71 +63,86 @@ impl App { previous_temp_position : 0, awaiting_second_d : false, use_dot, + show_help : false, } } + pub fn on_enter(&mut self) { + } + + pub fn on_esc(&mut self) { + if self.show_help { + self.show_help = false; + } + } + + // TODO: How should we make it for process panel specific hotkeys? Only if we're on process panel? Or what? pub fn on_key(&mut self, c : char) { - match c { - 'q' => self.should_quit = true, - 'd' => { - if self.awaiting_second_d { - self.awaiting_second_d = false; - self.kill_highlighted_process().unwrap_or(()); // TODO: Should this be handled? - } - else { - self.awaiting_second_d = true; + if !self.show_help { + match c { + 'd' => { + if self.awaiting_second_d { + self.awaiting_second_d = false; + self.kill_highlighted_process().unwrap_or(()); // TODO: Should this be handled? + } + else { + self.awaiting_second_d = true; + } } - } - 'c' => { - // TODO: This should depend on what tile you're on! - match self.process_sorting_type { - processes::ProcessSorting::CPU => self.process_sorting_reverse = !self.process_sorting_reverse, - _ => { - self.process_sorting_type = processes::ProcessSorting::CPU; - self.process_sorting_reverse = true; + 'c' => { + // TODO: This should depend on what tile you're on! + match self.process_sorting_type { + processes::ProcessSorting::CPU => self.process_sorting_reverse = !self.process_sorting_reverse, + _ => { + self.process_sorting_type = processes::ProcessSorting::CPU; + self.process_sorting_reverse = true; + } } + self.to_be_resorted = true; + self.currently_selected_process_position = 0; } - self.to_be_resorted = true; - self.currently_selected_process_position = 0; - } - 'm' => { - match self.process_sorting_type { - processes::ProcessSorting::MEM => self.process_sorting_reverse = !self.process_sorting_reverse, - _ => { - self.process_sorting_type = processes::ProcessSorting::MEM; - self.process_sorting_reverse = true; + 'm' => { + match self.process_sorting_type { + processes::ProcessSorting::MEM => self.process_sorting_reverse = !self.process_sorting_reverse, + _ => { + self.process_sorting_type = processes::ProcessSorting::MEM; + self.process_sorting_reverse = true; + } } + self.to_be_resorted = true; + self.currently_selected_process_position = 0; } - self.to_be_resorted = true; - self.currently_selected_process_position = 0; - } - 'p' => { - match self.process_sorting_type { - processes::ProcessSorting::PID => self.process_sorting_reverse = !self.process_sorting_reverse, - _ => { - self.process_sorting_type = processes::ProcessSorting::PID; - self.process_sorting_reverse = false; + 'p' => { + match self.process_sorting_type { + processes::ProcessSorting::PID => self.process_sorting_reverse = !self.process_sorting_reverse, + _ => { + self.process_sorting_type = processes::ProcessSorting::PID; + self.process_sorting_reverse = false; + } } + self.to_be_resorted = true; + self.currently_selected_process_position = 0; } - self.to_be_resorted = true; - self.currently_selected_process_position = 0; - } - 'n' => { - match self.process_sorting_type { - processes::ProcessSorting::NAME => self.process_sorting_reverse = !self.process_sorting_reverse, - _ => { - self.process_sorting_type = processes::ProcessSorting::NAME; - self.process_sorting_reverse = false; + 'n' => { + match self.process_sorting_type { + processes::ProcessSorting::NAME => self.process_sorting_reverse = !self.process_sorting_reverse, + _ => { + self.process_sorting_type = processes::ProcessSorting::NAME; + self.process_sorting_reverse = false; + } } + self.to_be_resorted = true; + self.currently_selected_process_position = 0; } - self.to_be_resorted = true; - self.currently_selected_process_position = 0; + '?' => { + self.show_help = true; + } + _ => {} } - _ => {} - } - if self.awaiting_second_d && c != 'd' { - self.awaiting_second_d = false; + if self.awaiting_second_d && c != 'd' { + self.awaiting_second_d = false; + } } } diff --git a/src/canvas.rs b/src/canvas.rs index 2612dda5..c9e7b05b 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -1,8 +1,8 @@ use tui_temp_fork::{ backend, - layout::{Constraint, Direction, Layout}, + layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Modifier, Style}, - widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Row, Table, Widget}, + widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Paragraph, Row, Table, Text, Widget}, Terminal, }; @@ -43,360 +43,396 @@ pub fn draw_data(terminal : &mut Terminal, app_state : terminal.autoresize()?; terminal.draw(|mut f| { //debug!("Drawing!"); - let vertical_chunks = Layout::default() + + // Only for the "help" and "are you sure" menus + let vertical_dialog_chunk = Layout::default() .direction(Direction::Vertical) .margin(1) - .constraints([Constraint::Percentage(34), Constraint::Percentage(34), Constraint::Percentage(33)].as_ref()) + .constraints([Constraint::Percentage(32), Constraint::Percentage(40), Constraint::Percentage(28)].as_ref()) .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() + let middle_dialog_chunk = Layout::default() .direction(Direction::Horizontal) .margin(0) - .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) - .split(vertical_chunks[2]); - - // Set up blocks and their components - // CPU usage graph - { - let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); - let y_axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) - .bounds([-0.5, 100.5]) - .labels(&["0%", "100%"]); - - let mut dataset_vector : Vec = Vec::new(); - - for (i, cpu) in canvas_data.cpu_data.iter().enumerate() { - let mut avg_cpu_exist_offset = 0; - if app_state.show_average_cpu { - if i == 0 { - // Skip, we want to render the average cpu last! - continue; - } - else { - avg_cpu_exist_offset = 1; + .constraints([Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)].as_ref()) + .split(vertical_dialog_chunk[1]); + + if app_state.show_help { + let text = [ + Text::raw("\nGeneral Keybinds\n"), + Text::raw("q, Ctrl-C to quit.\n"), + Text::raw("Up/k, Down/j, Left/h, Right/l to navigate between panels.\n"), + Text::raw("Shift+Up and Shift+Down scrolls through the list.\n"), + Text::raw("Esc to close a dialog window (help or dd confirmation).\n"), + Text::raw("? to get this help screen.\n"), + Text::raw("\nProcess Panel Keybinds\n"), + Text::raw("dd to kill the selected process.\n"), + Text::raw("c to sort by CPU usage.\n"), + Text::raw("m to sort by memory usage.\n"), + Text::raw("p to sort by PID.\n"), + Text::raw("n to sort by process name.\n"), + ]; + + Paragraph::new(text.iter()) + .block(Block::default().title("Help").borders(Borders::ALL)) + .style(Style::default().fg(Color::Gray)) + .alignment(Alignment::Left) + .wrap(true) + .render(&mut f, middle_dialog_chunk[1]); + } + else { + let vertical_chunks = Layout::default() + .direction(Direction::Vertical) + .margin(1) + .constraints([Constraint::Percentage(34), Constraint::Percentage(34), Constraint::Percentage(33)].as_ref()) + .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]); + + // Set up blocks and their components + // CPU usage graph + { + let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); + let y_axis = Axis::default() + .style(Style::default().fg(GRAPH_COLOUR)) + .bounds([-0.5, 100.5]) + .labels(&["0%", "100%"]); + + let mut dataset_vector : Vec = Vec::new(); + + for (i, cpu) in canvas_data.cpu_data.iter().enumerate() { + let mut avg_cpu_exist_offset = 0; + if app_state.show_average_cpu { + if i == 0 { + // Skip, we want to render the average cpu last! + continue; + } + else { + avg_cpu_exist_offset = 1; + } } + + dataset_vector.push( + Dataset::default() + .name(&cpu.0) + .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) + .style(Style::default().fg(COLOUR_LIST[i - avg_cpu_exist_offset % COLOUR_LIST.len()])) + .data(&(cpu.1)), + ); } - dataset_vector.push( - Dataset::default() - .name(&cpu.0) - .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) - .style(Style::default().fg(COLOUR_LIST[i - avg_cpu_exist_offset % COLOUR_LIST.len()])) - .data(&(cpu.1)), - ); - } + if !canvas_data.cpu_data.is_empty() && app_state.show_average_cpu { + dataset_vector.push( + Dataset::default() + .name(&canvas_data.cpu_data[0].0) + .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) + .style(Style::default().fg(COLOUR_LIST[canvas_data.cpu_data.len() - 1 % COLOUR_LIST.len()])) + .data(&(canvas_data.cpu_data[0].1)), + ); + } - if !canvas_data.cpu_data.is_empty() && app_state.show_average_cpu { - dataset_vector.push( - Dataset::default() - .name(&canvas_data.cpu_data[0].0) - .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) - .style(Style::default().fg(COLOUR_LIST[canvas_data.cpu_data.len() - 1 % COLOUR_LIST.len()])) - .data(&(canvas_data.cpu_data[0].1)), - ); + Chart::default() + .block( + Block::default() + .title("CPU Usage") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::CPU => highlighted_border_style, + _ => border_style, + }), + ) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&dataset_vector) + .render(&mut f, vertical_chunks[0]); } - Chart::default() - .block( - Block::default() - .title("CPU Usage") - .borders(Borders::ALL) - .border_style(match app_state.current_application_position { - app::ApplicationPosition::CPU => highlighted_border_style, - _ => border_style, - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&dataset_vector) - .render(&mut f, vertical_chunks[0]); - } - - //Memory usage graph - { - let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); - let y_axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) - .bounds([-0.5, 100.5]) // Offset as the zero value isn't drawn otherwise... - .labels(&["0%", "100%"]); - - let mem_name = "RAM:".to_string() - + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)) - + &format!( - " {:.1}GB/{:.1}GB", - canvas_data.mem_values[0].0 as f64 / 1024.0, - canvas_data.mem_values[0].1 as f64 / 1024.0 - ); - let swap_name : String; - - let mut mem_canvas_vec : Vec = vec![Dataset::default() - .name(&mem_name) - .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) - .style(Style::default().fg(Color::LightBlue)) - .data(&canvas_data.mem_data)]; - - if !(&canvas_data.swap_data).is_empty() { - if let Some(last_canvas_result) = (&canvas_data.swap_data).last() { - if last_canvas_result.1 >= 0.0 { - swap_name = "SWP:".to_string() - + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)) - + &format!( - " {:.1}GB/{:.1}GB", - canvas_data.mem_values[1].0 as f64 / 1024.0, - canvas_data.mem_values[1].1 as f64 / 1024.0 + //Memory usage graph + { + let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); + let y_axis = Axis::default() + .style(Style::default().fg(GRAPH_COLOUR)) + .bounds([-0.5, 100.5]) // Offset as the zero value isn't drawn otherwise... + .labels(&["0%", "100%"]); + + let mem_name = "RAM:".to_string() + + &format!("{:3}%", (canvas_data.mem_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)) + + &format!( + " {:.1}GB/{:.1}GB", + canvas_data.mem_values[0].0 as f64 / 1024.0, + canvas_data.mem_values[0].1 as f64 / 1024.0 + ); + let swap_name : String; + + let mut mem_canvas_vec : Vec = vec![Dataset::default() + .name(&mem_name) + .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) + .style(Style::default().fg(Color::LightBlue)) + .data(&canvas_data.mem_data)]; + + if !(&canvas_data.swap_data).is_empty() { + if let Some(last_canvas_result) = (&canvas_data.swap_data).last() { + if last_canvas_result.1 >= 0.0 { + swap_name = "SWP:".to_string() + + &format!("{:3}%", (canvas_data.swap_data.last().unwrap_or(&(0_f64, 0_f64)).1.round() as u64)) + + &format!( + " {:.1}GB/{:.1}GB", + canvas_data.mem_values[1].0 as f64 / 1024.0, + canvas_data.mem_values[1].1 as f64 / 1024.0 + ); + mem_canvas_vec.push( + Dataset::default() + .name(&swap_name) + .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) + .style(Style::default().fg(Color::LightYellow)) + .data(&canvas_data.swap_data), ); - mem_canvas_vec.push( - Dataset::default() - .name(&swap_name) - .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) - .style(Style::default().fg(Color::LightYellow)) - .data(&canvas_data.swap_data), - ); + } } } + + Chart::default() + .block( + Block::default() + .title("Memory Usage") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::MEM => highlighted_border_style, + _ => border_style, + }), + ) + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&mem_canvas_vec) + .render(&mut f, middle_chunks[0]); } - Chart::default() - .block( - Block::default() - .title("Memory Usage") - .borders(Borders::ALL) - .border_style(match app_state.current_application_position { - app::ApplicationPosition::MEM => highlighted_border_style, - _ => border_style, - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&mem_canvas_vec) - .render(&mut f, middle_chunks[0]); - } + // Temperature table + { + let num_rows = i64::from(middle_divided_chunk_2[0].height) - 3; + let start_position = get_start_position( + num_rows, + &(app_state.scroll_direction), + &mut app_state.previous_temp_position, + &mut app_state.currently_selected_temperature_position, + ); - // Temperature table - { - let num_rows = i64::from(middle_divided_chunk_2[0].height) - 3; - let start_position = get_start_position( - num_rows, - &(app_state.scroll_direction), - &mut app_state.previous_temp_position, - &mut app_state.currently_selected_temperature_position, - ); - - let sliced_vec : Vec> = (&canvas_data.temp_sensor_data[start_position as usize..]).to_vec(); - let mut disk_counter = 0; - - let temperature_rows = sliced_vec.iter().map(|disk| { - Row::StyledData( - disk.iter(), - if disk_counter == app_state.currently_selected_temperature_position - start_position { - // TODO: This is what controls the highlighting! - disk_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) - } - else { - if disk_counter >= 0 { - disk_counter += 1; - } - Style::default().fg(TEXT_COLOUR) - }, - ) - }); - - let width = f64::from(middle_divided_chunk_2[0].width); - Table::new(["Sensor", "Temp"].iter(), temperature_rows) - .block( - Block::default() - .title("Temperatures") - .borders(Borders::ALL) - .border_style(match app_state.current_application_position { - app::ApplicationPosition::TEMP => highlighted_border_style, - _ => border_style, - }), - ) - .header_style(Style::default().fg(Color::LightBlue)) - .widths(&[(width * 0.45) as u16, (width * 0.4) as u16]) - .render(&mut f, middle_divided_chunk_2[0]); - } + let sliced_vec : Vec> = (&canvas_data.temp_sensor_data[start_position as usize..]).to_vec(); + let mut disk_counter = 0; - // Disk usage table - { - let num_rows = i64::from(middle_divided_chunk_2[1].height) - 3; - let start_position = get_start_position( - num_rows, - &(app_state.scroll_direction), - &mut app_state.previous_disk_position, - &mut app_state.currently_selected_disk_position, - ); - - let sliced_vec : Vec> = (&canvas_data.disk_data[start_position as usize..]).to_vec(); - let mut disk_counter = 0; - - let disk_rows = sliced_vec.iter().map(|disk| { - Row::StyledData( - disk.iter(), - if disk_counter == app_state.currently_selected_disk_position - start_position { - // TODO: This is what controls the highlighting! - disk_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) - } - else { - if disk_counter >= 0 { - disk_counter += 1; + let temperature_rows = sliced_vec.iter().map(|disk| { + Row::StyledData( + disk.iter(), + if disk_counter == app_state.currently_selected_temperature_position - start_position { + disk_counter = -1; + Style::default().fg(Color::Black).bg(Color::Cyan) } - Style::default().fg(TEXT_COLOUR) - }, - ) - }); - - // TODO: We may have to dynamically remove some of these table elements based on size... - let width = f64::from(middle_divided_chunk_2[1].width); - Table::new(["Disk", "Mount", "Used", "Total", "Free", "R/s", "W/s"].iter(), disk_rows) - .block( - Block::default() - .title("Disk Usage") - .borders(Borders::ALL) - .border_style(match app_state.current_application_position { - app::ApplicationPosition::DISK => highlighted_border_style, - _ => border_style, - }), - ) - .header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD)) - .widths(&[ - (width * 0.18).floor() as u16, - (width * 0.14).floor() as u16, - (width * 0.11).floor() as u16, - (width * 0.11).floor() as u16, - (width * 0.11).floor() as u16, - (width * 0.11).floor() as u16, - (width * 0.11).floor() as u16, - ]) - .render(&mut f, middle_divided_chunk_2[1]); - } + else { + if disk_counter >= 0 { + disk_counter += 1; + } + Style::default().fg(TEXT_COLOUR) + }, + ) + }); - // Network graph - { - let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); - let y_axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) - .bounds([-0.5, 1_000_000.5]) - .labels(&["0GB", "1GB"]); - Chart::default() - .block( - Block::default() - .title("Network") - .borders(Borders::ALL) - .border_style(match app_state.current_application_position { - app::ApplicationPosition::NETWORK => highlighted_border_style, - _ => border_style, - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .datasets(&[ - Dataset::default() - .name(&(canvas_data.rx_display)) - .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) - .style(Style::default().fg(Color::LightBlue)) - .data(&canvas_data.network_data_rx), - Dataset::default() - .name(&(canvas_data.tx_display)) - .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) - .style(Style::default().fg(Color::LightYellow)) - .data(&canvas_data.network_data_tx), - ]) - .render(&mut f, bottom_chunks[0]); - } + let width = f64::from(middle_divided_chunk_2[0].width); + Table::new(["Sensor", "Temp"].iter(), temperature_rows) + .block( + Block::default() + .title("Temperatures") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::TEMP => highlighted_border_style, + _ => border_style, + }), + ) + .header_style(Style::default().fg(Color::LightBlue)) + .widths(&[(width * 0.45) as u16, (width * 0.4) as u16]) + .render(&mut f, middle_divided_chunk_2[0]); + } - // Processes table - { - let width = f64::from(bottom_chunks[1].width); - - // 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 = i64::from(bottom_chunks[1].height) - 3; - - let start_position = get_start_position( - num_rows, - &(app_state.scroll_direction), - &mut app_state.previous_process_position, - &mut app_state.currently_selected_process_position, - ); - - /*debug!( - "START POSN: {}, PREV POSN: {}, CURRENT SELECTED POSN: {}, NUM ROWS: {}", - start_position, app_state.previous_process_position, app_state.currently_selected_process_position, num_rows - );*/ - - let sliced_vec : Vec> = (&canvas_data.process_data[start_position as usize..]).to_vec(); - let mut process_counter = 0; - - let process_rows = sliced_vec.iter().map(|process| { - Row::StyledData( - process.iter(), - if process_counter == app_state.currently_selected_process_position - start_position { - // TODO: This is what controls the highlighting! - process_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) - } - else { - if process_counter >= 0 { - process_counter += 1; + // Disk usage table + { + let num_rows = i64::from(middle_divided_chunk_2[1].height) - 3; + let start_position = get_start_position( + num_rows, + &(app_state.scroll_direction), + &mut app_state.previous_disk_position, + &mut app_state.currently_selected_disk_position, + ); + + let sliced_vec : Vec> = (&canvas_data.disk_data[start_position as usize..]).to_vec(); + let mut disk_counter = 0; + + let disk_rows = sliced_vec.iter().map(|disk| { + Row::StyledData( + disk.iter(), + if disk_counter == app_state.currently_selected_disk_position - start_position { + disk_counter = -1; + Style::default().fg(Color::Black).bg(Color::Cyan) } - Style::default().fg(TEXT_COLOUR) - }, - ) - }); + else { + if disk_counter >= 0 { + disk_counter += 1; + } + Style::default().fg(TEXT_COLOUR) + }, + ) + }); + // TODO: We may have to dynamically remove some of these table elements based on size... + let width = f64::from(middle_divided_chunk_2[1].width); + Table::new(["Disk", "Mount", "Used", "Total", "Free", "R/s", "W/s"].iter(), disk_rows) + .block( + Block::default() + .title("Disk Usage") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::DISK => highlighted_border_style, + _ => border_style, + }), + ) + .header_style(Style::default().fg(Color::LightBlue).modifier(Modifier::BOLD)) + .widths(&[ + (width * 0.18).floor() as u16, + (width * 0.14).floor() as u16, + (width * 0.11).floor() as u16, + (width * 0.11).floor() as u16, + (width * 0.11).floor() as u16, + (width * 0.11).floor() as u16, + (width * 0.11).floor() as u16, + ]) + .render(&mut f, middle_divided_chunk_2[1]); + } + + // Network graph { - use app::data_collection::processes::ProcessSorting; - let mut pid = "PID".to_string(); - let mut name = "Name".to_string(); - let mut cpu = "CPU%".to_string(); - let mut mem = "Mem%".to_string(); - - let direction_val = if app_state.process_sorting_reverse { - " ⯆".to_string() - } - else { - " ⯅".to_string() - }; - - match app_state.process_sorting_type { - ProcessSorting::CPU => cpu += &direction_val, - ProcessSorting::MEM => mem += &direction_val, - ProcessSorting::PID => pid += &direction_val, - ProcessSorting::NAME => name += &direction_val, - }; - - Table::new([pid, name, cpu, mem].iter(), process_rows) + let x_axis : Axis = Axis::default().style(Style::default().fg(GRAPH_COLOUR)).bounds([0.0, 600_000.0]); + let y_axis = Axis::default() + .style(Style::default().fg(GRAPH_COLOUR)) + .bounds([-0.5, 1_000_000.5]) + .labels(&["0GB", "1GB"]); + Chart::default() .block( Block::default() - .title("Processes") + .title("Network") .borders(Borders::ALL) .border_style(match app_state.current_application_position { - app::ApplicationPosition::PROCESS => highlighted_border_style, + app::ApplicationPosition::NETWORK => highlighted_border_style, _ => border_style, }), ) - .header_style(Style::default().fg(Color::LightBlue)) - .widths(&[(width * 0.2) as u16, (width * 0.35) as u16, (width * 0.2) as u16, (width * 0.2) as u16]) - .render(&mut f, bottom_chunks[1]); + .x_axis(x_axis) + .y_axis(y_axis) + .datasets(&[ + Dataset::default() + .name(&(canvas_data.rx_display)) + .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) + .style(Style::default().fg(Color::LightBlue)) + .data(&canvas_data.network_data_rx), + Dataset::default() + .name(&(canvas_data.tx_display)) + .marker(if app_state.use_dot { Marker::Dot } else { Marker::Braille }) + .style(Style::default().fg(Color::LightYellow)) + .data(&canvas_data.network_data_tx), + ]) + .render(&mut f, bottom_chunks[0]); + } + + // Processes table + { + let width = f64::from(bottom_chunks[1].width); + + // 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 = i64::from(bottom_chunks[1].height) - 3; + + let start_position = get_start_position( + num_rows, + &(app_state.scroll_direction), + &mut app_state.previous_process_position, + &mut app_state.currently_selected_process_position, + ); + + /*debug!( + "START POSN: {}, PREV POSN: {}, CURRENT SELECTED POSN: {}, NUM ROWS: {}", + start_position, app_state.previous_process_position, app_state.currently_selected_process_position, num_rows + );*/ + + let sliced_vec : Vec> = (&canvas_data.process_data[start_position as usize..]).to_vec(); + let mut process_counter = 0; + + let process_rows = sliced_vec.iter().map(|process| { + Row::StyledData( + process.iter(), + if process_counter == app_state.currently_selected_process_position - start_position { + process_counter = -1; + Style::default().fg(Color::Black).bg(Color::Cyan) + } + else { + if process_counter >= 0 { + process_counter += 1; + } + Style::default().fg(TEXT_COLOUR) + }, + ) + }); + + { + use app::data_collection::processes::ProcessSorting; + let mut pid = "PID".to_string(); + let mut name = "Name".to_string(); + let mut cpu = "CPU%".to_string(); + let mut mem = "Mem%".to_string(); + + let direction_val = if app_state.process_sorting_reverse { + " ⯆".to_string() + } + else { + " ⯅".to_string() + }; + + match app_state.process_sorting_type { + ProcessSorting::CPU => cpu += &direction_val, + ProcessSorting::MEM => mem += &direction_val, + ProcessSorting::PID => pid += &direction_val, + ProcessSorting::NAME => name += &direction_val, + }; + + Table::new([pid, name, cpu, mem].iter(), process_rows) + .block( + Block::default() + .title("Processes") + .borders(Borders::ALL) + .border_style(match app_state.current_application_position { + app::ApplicationPosition::PROCESS => highlighted_border_style, + _ => border_style, + }), + ) + .header_style(Style::default().fg(Color::LightBlue)) + .widths(&[(width * 0.2) as u16, (width * 0.35) as u16, (width * 0.2) as u16, (width * 0.2) as u16]) + .render(&mut f, bottom_chunks[1]); + } } } })?; diff --git a/src/constants.rs b/src/constants.rs index 94ef2acd..7e6646df 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,5 @@ // TODO: Store like three minutes of data, then change how much is shown based on scaling! pub const STALE_MAX_MILLISECONDS : u64 = 60 * 1000; // We wish to store at most 60 seconds worth of data. This may change in the future, or be configurable. -pub const _TIME_STARTS_FROM : u64 = 60 * 1000; +pub const _TIME_STARTS_FROM : u64 = 60 * 1000; // TODO: Fix this pub const TICK_RATE_IN_MILLISECONDS : u64 = 200; // We use this as it's a good value to work with. pub const DEFAULT_REFRESH_RATE_IN_MILLISECONDS : u128 = 1000; diff --git a/src/main.rs b/src/main.rs index 8d238e71..6a6651d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -183,14 +183,16 @@ fn main() -> error::Result<()> { Event::KeyInput(event) => { // debug!("Keyboard event fired!"); match event { - KeyEvent::Ctrl('c') | KeyEvent::Esc => break, + KeyEvent::Ctrl('c') | KeyEvent::Char('q') => break, KeyEvent::Char('h') | KeyEvent::Left => app.on_left(), KeyEvent::Char('l') | KeyEvent::Right => app.on_right(), KeyEvent::Char('k') | KeyEvent::Up => app.on_up(), KeyEvent::Char('j') | KeyEvent::Down => app.on_down(), KeyEvent::ShiftUp => app.decrement_position_count(), KeyEvent::ShiftDown => app.increment_position_count(), - KeyEvent::Char(c) => app.on_key(c), // TODO: We can remove the 'q' event and just move it to the quit? + KeyEvent::Char(c) => app.on_key(c), + KeyEvent::Enter => app.on_enter(), + KeyEvent::Esc => app.on_esc(), _ => {} } @@ -248,9 +250,6 @@ fn main() -> error::Result<()> { debug!("Update event complete."); } } - if app.should_quit { - break; - } } // Draw! if let Err(err) = canvas::draw_data(&mut terminal, &mut app, &canvas_data) { -- cgit v1.2.3