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