From 2123becb8158130118301759a264994907062d75 Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sat, 8 Feb 2020 14:28:19 -0500 Subject: [PATCH] Added colour options to config; updated sample config to reflect this; updated README --- README.md | 23 ++- sample_config.toml | 26 ++- src/app.rs | 4 +- src/canvas.rs | 426 ++++++++++++++++++++++++++++++--------------- src/main.rs | 183 ++++++++++++++++--- 5 files changed, 486 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index da0c2cf5..12b02d28 100644 --- a/README.md +++ b/README.md @@ -92,17 +92,32 @@ Run using `btm`. - `-R`, `--regex` will default to using regex. -- `-C`, `--config` takes in a file path leading to a TOML file, where one can set flags to execute by default. +- `-C`, `--config` takes in a file path leading to a TOML file. - - Options are generally the same as the long names as other flags (ie: `case_sensitive = true`). - - For temperature type, use `temperature_type = `. - - See the [sample config](./sample_config.toml) for an example. + One use of a config file is to set flags to execute by default. + + - Options are generally the same as the long names as other flags (ex: `case_sensitive = true`). + - For temperature type, use `temperature_type = ""`. + + Another use is to set colours (by default they're somewhat randomly generated). The following labels are customizable with a hex colour code strings: + + - Table header colours (`table_header_color="#ffffff"`). + - Every CPU core colour as an array (`cpu_core_colors=["#ffffff", "#000000", "#111111"]`). bottom will look at 216 (let's be realistic here) colours at most, and in order. If not enough colours are provided, then the rest will be pseudo-randomly generated. + - RAM and SWAP colours (`ram_color="#ffffff"`, `swap_color="#111111"`). + - RX and TX colours (`rx_color="#ffffff"`, `tx_color="#111111"`). + - General widget border colour (`border_color="#ffffff"`). + - Current widget border colour (`highlighted_border_color="#ffffff"`). + - Text colour (`text_color="#ffffff"`). + - Cursor colour (`cursor_color="#ffffff"`). + - Current selected scroll entry colour (`scroll_entry_text_color="#282828"`, `scroll_entry_bg_color="#458588"`). bottom will check specific locations by default for a config file. - For Unix-based systems: `~/.config/btm/btm.toml`. - For Windows: TBD. + See the [sample config](./sample_config.toml) for an example. + ### Keybindings #### General diff --git a/sample_config.toml b/sample_config.toml index dff4cc8e..c4dc2b74 100644 --- a/sample_config.toml +++ b/sample_config.toml @@ -1,10 +1,26 @@ +[flags] avg_cpu = true dot_marker = true -temperature_type = "kelvin" -rate = 2000 -left_legend = false +temperature_type = "k" +rate = 1000 +left_legend = true current_usage = false -group_processes = true +group_processes = false case_sensitive = true whole_word = true -regex = true \ No newline at end of file +regex = true + +[colors] +# Based on gruvbox: https://github.com/morhetz/gruvbox +table_header_color="#458588" +cpu_core_colors=["#cc241d", "#98971a", "#d79921", "#458588", "#b16286", "#689d6a", "#fb4934", "#b8bb26", "#fabd2f", "#83a598"] +ram_color="#fb4934" +swap_color="#fabd2f" +rx_color="#458588" +tx_color="#689d6a" +border_color="#ebdbb2" +highlighted_border_color="#fe8019" +text_color="#ebdbb2" +cursor_color="#8ec07c" +scroll_entry_text_color="#282828" +scroll_entry_bg_color="#458588" \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 5a584cec..3f189adf 100644 --- a/src/app.rs +++ b/src/app.rs @@ -711,7 +711,7 @@ impl App { WidgetPosition::Process => self.change_process_position(-1), WidgetPosition::Temp => self.change_temp_position(-1), WidgetPosition::Disk => self.change_disk_position(-1), - WidgetPosition::Cpu => self.change_cpu_table_position(-1), // TODO: Temporary, may change if we add scaling + WidgetPosition::Cpu => self.change_cpu_table_position(-1), // TODO: [PO?] Temporary, may change if we add scaling _ => {} } self.scroll_direction = ScrollDirection::UP; @@ -725,7 +725,7 @@ impl App { WidgetPosition::Process => self.change_process_position(1), WidgetPosition::Temp => self.change_temp_position(1), WidgetPosition::Disk => self.change_disk_position(1), - WidgetPosition::Cpu => self.change_cpu_table_position(1), // TODO: Temporary, may change if we add scaling + WidgetPosition::Cpu => self.change_cpu_table_position(1), // TODO: [PO?] Temporary, may change if we add scaling _ => {} } self.scroll_direction = ScrollDirection::DOWN; diff --git a/src/canvas.rs b/src/canvas.rs index f6425a41..6f4445de 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -1,6 +1,6 @@ use crate::{ app::{self, data_harvester::processes::ProcessHarvest}, - constants, + constants::*, data_conversion::{ConvertedCpuData, ConvertedProcessData}, utils::{error, gen_util::*}, }; @@ -15,11 +15,8 @@ use tui::{ Terminal, }; -const TEXT_COLOUR: Color = Color::Gray; -const GRAPH_COLOUR: Color = Color::Gray; -const BORDER_STYLE_COLOUR: Color = Color::Gray; -const HIGHLIGHTED_BORDER_STYLE_COLOUR: Color = Color::LightBlue; -const TABLE_HEADER_COLOUR: Color = Color::LightBlue; +const STANDARD_FIRST_COLOUR: Color = Color::Rgb(150, 106, 253); +const STANDARD_SECOND_COLOUR: Color = Color::LightYellow; const GOLDEN_RATIO: f32 = 0.618_034; // Approx, good enough for use (also Clippy gets mad if it's too long) // Headers @@ -58,10 +55,6 @@ lazy_static! { Text::raw("Use Alt-r to toggle regex.\n"), Text::raw("\nFor startup flags, type in \"btm -h\".") ]; - static ref COLOUR_LIST: Vec = gen_n_colours(constants::NUM_COLOURS); - static ref CANVAS_BORDER_STYLE: Style = Style::default().fg(BORDER_STYLE_COLOUR); - static ref CANVAS_HIGHLIGHTED_BORDER_STYLE: Style = - Style::default().fg(HIGHLIGHTED_BORDER_STYLE_COLOUR); static ref DISK_HEADERS_LENS: Vec = DISK_HEADERS .iter() .map(|entry| max(FORCE_MIN_THRESHOLD, entry.len())) @@ -98,8 +91,8 @@ pub struct DisplayableData { pub network_data_tx: Vec<(f64, f64)>, pub disk_data: Vec>, pub temp_sensor_data: Vec>, - pub process_data: HashMap, // Not final - pub grouped_process_data: Vec, // Not final + pub process_data: HashMap, // Not the final value + pub grouped_process_data: Vec, // Not the final value pub finalized_process_data: Vec, // What's actually displayed pub mem_label: String, pub swap_label: String, @@ -108,8 +101,8 @@ pub struct DisplayableData { pub cpu_data: Vec, } -/// Generates random colours. -/// Strategy found from https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ +/// Generates random colours. Strategy found from +/// https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ fn gen_n_colours(num_to_gen: i32) -> Vec { fn gen_hsv(h: f32) -> f32 { let new_val = h + GOLDEN_RATIO; @@ -146,7 +139,7 @@ fn gen_n_colours(num_to_gen: i32) -> Vec { ]; let mut h: f32 = 0.4; // We don't need random colours... right? - for _i in 0..num_to_gen { + for _i in 0..(num_to_gen - 6) { h = gen_hsv(h); let result = hsv_to_rgb(h, 0.5, 0.95); colour_vec.push(Color::Rgb(result.0, result.1, result.2)); @@ -155,9 +148,31 @@ fn gen_n_colours(num_to_gen: i32) -> Vec { colour_vec } +fn convert_hex_to_color(hex: &str) -> error::Result { + fn convert_hex_to_rgb(hex: &str) -> error::Result<(u8, u8, u8)> { + if hex.len() == 7 && &hex[0..1] == "#" { + let r = u8::from_str_radix(&hex[1..3], 16)?; + let g = u8::from_str_radix(&hex[3..5], 16)?; + let b = u8::from_str_radix(&hex[5..7], 16)?; + + return Ok((r, g, b)); + } + + Err(error::BottomError::GenericError { + message: format!( + "Colour hex {} is not of valid length. It must be a 7 character string of the form \"#112233\".", + hex + ), + }) + } + + let rgb = convert_hex_to_rgb(hex)?; + Ok(Color::Rgb(rgb.0, rgb.1, rgb.2)) +} + #[allow(dead_code)] #[derive(Default)] -/// Handles the canvas' state. +/// Handles the canvas' state. TODO: [OPT] implement this. pub struct Painter { height: f64, width: f64, @@ -169,6 +184,118 @@ pub struct Painter { bottom_chunks: Vec, cpu_chunk: Vec, network_chunk: Vec, + pub colours: CanvasColours, +} + +pub struct CanvasColours { + text_colour: Color, + scroll_text_colour: Color, + scroll_bg_colour: Color, + scroll_entry_style: Style, + border_colour: Color, + highlighted_border_colour: Color, + table_header_colour: Color, + ram_colour: Color, + swap_colour: Color, + rx_colour: Color, + tx_colour: Color, + cpu_colours: Vec, + cursor_colour: Color, + border_style: Style, + highlighted_border_style: Style, + text_style: Style, +} + +impl Default for CanvasColours { + fn default() -> Self { + CanvasColours { + text_colour: Color::Gray, + scroll_text_colour: Color::Black, + scroll_bg_colour: Color::Cyan, + scroll_entry_style: Style::default().fg(Color::Black).bg(Color::Cyan), + border_colour: Color::Gray, + highlighted_border_colour: Color::LightBlue, + table_header_colour: Color::LightBlue, + ram_colour: STANDARD_FIRST_COLOUR, + swap_colour: STANDARD_SECOND_COLOUR, + rx_colour: STANDARD_FIRST_COLOUR, + tx_colour: STANDARD_SECOND_COLOUR, + cpu_colours: Vec::new(), + cursor_colour: Color::Cyan, + border_style: Style::default().fg(Color::Gray), + highlighted_border_style: Style::default().fg(Color::LightBlue), + text_style: Style::default().fg(Color::Gray), + } + } +} + +impl CanvasColours { + pub fn set_text_colour(&mut self, hex: &str) -> error::Result<()> { + self.text_colour = convert_hex_to_color(hex)?; + self.text_style = Style::default().fg(self.text_colour); + Ok(()) + } + pub fn set_border_colour(&mut self, hex: &str) -> error::Result<()> { + self.border_colour = convert_hex_to_color(hex)?; + self.border_style = Style::default().fg(self.border_colour); + Ok(()) + } + pub fn set_highlighted_border_colour(&mut self, hex: &str) -> error::Result<()> { + self.highlighted_border_colour = convert_hex_to_color(hex)?; + self.highlighted_border_style = Style::default().fg(self.highlighted_border_colour); + Ok(()) + } + pub fn set_table_header_colour(&mut self, hex: &str) -> error::Result<()> { + self.table_header_colour = convert_hex_to_color(hex)?; + Ok(()) + } + pub fn set_ram_colour(&mut self, hex: &str) -> error::Result<()> { + self.ram_colour = convert_hex_to_color(hex)?; + Ok(()) + } + pub fn set_swap_colour(&mut self, hex: &str) -> error::Result<()> { + self.swap_colour = convert_hex_to_color(hex)?; + Ok(()) + } + pub fn set_rx_colour(&mut self, hex: &str) -> error::Result<()> { + self.rx_colour = convert_hex_to_color(hex)?; + Ok(()) + } + pub fn set_tx_colour(&mut self, hex: &str) -> error::Result<()> { + self.tx_colour = convert_hex_to_color(hex)?; + Ok(()) + } + pub fn set_cpu_colours(&mut self, hex_colours: &Vec) -> error::Result<()> { + let max_amount = std::cmp::min(hex_colours.len(), NUM_COLOURS as usize); + for i in 0..max_amount { + self.cpu_colours + .push(convert_hex_to_color(&hex_colours[i])?); + } + Ok(()) + } + pub fn generate_remaining_cpu_colours(&mut self) { + let remaining_num_colours = NUM_COLOURS - self.cpu_colours.len() as i32; + self.cpu_colours + .extend(gen_n_colours(remaining_num_colours)); + } + pub fn set_cursor_colour(&mut self, hex: &str) -> error::Result<()> { + self.cursor_colour = convert_hex_to_color(hex)?; + Ok(()) + } + pub fn set_scroll_entry_text_color(&mut self, hex: &str) -> error::Result<()> { + self.scroll_text_colour = convert_hex_to_color(hex)?; + self.scroll_entry_style = Style::default() + .fg(self.scroll_text_colour) + .bg(self.scroll_bg_colour); + Ok(()) + } + pub fn set_scroll_entry_bg_color(&mut self, hex: &str) -> error::Result<()> { + self.scroll_bg_colour = convert_hex_to_color(hex)?; + self.scroll_entry_style = Style::default() + .fg(self.scroll_text_colour) + .bg(self.scroll_bg_colour); + Ok(()) + } } impl Painter { @@ -208,10 +335,12 @@ impl Painter { Paragraph::new(HELP_TEXT.iter()) .block( Block::default() - .title("Help (Press Esc to close)") + .title(" Help (Press Esc to close) ") + .title_style(self.colours.text_style) + .style(self.colours.border_style) .borders(Borders::ALL), ) - .style(Style::default().fg(Color::Gray)) + .style(Style::default().fg(self.colours.text_colour)) .alignment(Alignment::Left) .wrap(true) .render(&mut f, middle_dialog_chunk[1]); @@ -251,10 +380,12 @@ impl Painter { Paragraph::new(dd_text.iter()) .block( Block::default() - .title("Kill Process Error (Press Esc to close)") + .title(" Kill Process Error (Press Esc to close) ") + .title_style(self.colours.text_style) + .style(self.colours.border_style) .borders(Borders::ALL), ) - .style(Style::default().fg(Color::Gray)) + .style(Style::default().fg(self.colours.text_colour)) .alignment(Alignment::Center) .wrap(true) .render(&mut f, middle_dialog_chunk[1]); @@ -279,10 +410,12 @@ impl Painter { Paragraph::new(dd_text.iter()) .block( Block::default() - .title("Kill Process Confirmation (Press Esc to close)") + .title(" Kill Process Confirmation (Press Esc to close) ") + .title_style(self.colours.text_style) + .style(self.colours.border_style) .borders(Borders::ALL), ) - .style(Style::default().fg(Color::Gray)) + .style(Style::default().fg(self.colours.text_colour)) .alignment(Alignment::Center) .wrap(true) .render(&mut f, middle_dialog_chunk[1]); @@ -423,11 +556,10 @@ impl Painter { let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data; // CPU usage graph - let x_axis: Axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) - .bounds([0.0, constants::TIME_STARTS_FROM as f64]); + let x_axis: Axis = Axis::default().bounds([0.0, TIME_STARTS_FROM as f64]); let y_axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) + .style(self.colours.text_style) + .labels_style(self.colours.text_style) .bounds([-0.5, 100.5]) .labels(&["0%", "100%"]); @@ -436,7 +568,7 @@ impl Painter { for (i, cpu) in cpu_data.iter().enumerate() { cpu_entries_vec.push(( - Style::default().fg(COLOUR_LIST[(i) % COLOUR_LIST.len()]), + Style::default().fg(self.colours.cpu_colours[(i) % self.colours.cpu_colours.len()]), cpu.cpu_data .iter() .map(<(f64, f64)>::from) @@ -447,7 +579,7 @@ impl Painter { if app_state.show_average_cpu { if let Some(avg_cpu_entry) = cpu_data.first() { cpu_entries_vec.push(( - Style::default().fg(COLOUR_LIST[0]), + Style::default().fg(self.colours.cpu_colours[0]), avg_cpu_entry .cpu_data .iter() @@ -473,11 +605,12 @@ impl Painter { Chart::default() .block( Block::default() - .title("CPU") + .title(" CPU ") + .title_style(self.colours.text_style) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - app::WidgetPosition::Cpu => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Cpu => self.colours.highlighted_border_style, + _ => self.colours.border_style, }), ) .x_axis(x_axis) @@ -525,15 +658,18 @@ impl Painter { == app_state.currently_selected_cpu_table_position - start_position { cpu_row_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) + self.colours.scroll_entry_style } else { if cpu_row_counter >= 0 { cpu_row_counter += 1; } - Style::default().fg(COLOUR_LIST[itx % COLOUR_LIST.len()]) + Style::default() + .fg(self.colours.cpu_colours + [itx % self.colours.cpu_colours.len()]) } } - _ => Style::default().fg(COLOUR_LIST[itx % COLOUR_LIST.len()]), + _ => Style::default() + .fg(self.colours.cpu_colours[itx % self.colours.cpu_colours.len()]), }, ) }); @@ -549,11 +685,11 @@ impl Painter { Table::new(CPU_LEGEND_HEADER.iter(), cpu_rows) .block(Block::default().borders(Borders::ALL).border_style( match app_state.current_widget_selected { - app::WidgetPosition::Cpu => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Cpu => self.colours.highlighted_border_style, + _ => self.colours.border_style, }, )) - .header_style(Style::default().fg(TABLE_HEADER_COLOUR)) + .header_style(Style::default().fg(self.colours.table_header_colour)) .widths( &(intrinsic_widths .into_iter() @@ -569,13 +705,12 @@ impl Painter { let mem_data: &[(f64, f64)] = &(app_state.canvas_data.mem_data); let swap_data: &[(f64, f64)] = &(app_state.canvas_data.swap_data); - let x_axis: Axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) - .bounds([0.0, constants::TIME_STARTS_FROM as f64]); + let x_axis: Axis = Axis::default().bounds([0.0, TIME_STARTS_FROM as f64]); // Offset as the zero value isn't drawn otherwise... let y_axis: Axis<&str> = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) + .style(self.colours.text_style) + .labels_style(self.colours.text_style) .bounds([-0.5, 100.5]) .labels(&["0%", "100%"]); @@ -586,7 +721,7 @@ impl Painter { } else { Marker::Braille }) - .style(Style::default().fg(COLOUR_LIST[0])) + .style(Style::default().fg(self.colours.ram_colour)) .data(&mem_data)]; if !(&swap_data).is_empty() { @@ -598,7 +733,7 @@ impl Painter { } else { Marker::Braille }) - .style(Style::default().fg(COLOUR_LIST[1])) + .style(Style::default().fg(self.colours.swap_colour)) .data(&swap_data), ); } @@ -606,11 +741,12 @@ impl Painter { Chart::default() .block( Block::default() - .title("Memory") + .title(" Memory ") + .title_style(self.colours.text_style) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - app::WidgetPosition::Mem => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Mem => self.colours.highlighted_border_style, + _ => self.colours.border_style, }), ) .x_axis(x_axis) @@ -625,21 +761,21 @@ impl Painter { let network_data_rx: &[(f64, f64)] = &(app_state.canvas_data.network_data_rx); let network_data_tx: &[(f64, f64)] = &(app_state.canvas_data.network_data_tx); - let x_axis: Axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) - .bounds([0.0, 60_000.0]); + let x_axis: Axis = Axis::default().bounds([0.0, 60_000.0]); let y_axis = Axis::default() - .style(Style::default().fg(GRAPH_COLOUR)) + .style(self.colours.text_style) + .labels_style(self.colours.text_style) .bounds([-0.5, 30_f64]) .labels(&["0B", "1KiB", "1MiB", "1GiB"]); Chart::default() .block( Block::default() - .title("Network") + .title(" Network ") + .title_style(self.colours.text_style) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - app::WidgetPosition::Network => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Network => self.colours.highlighted_border_style, + _ => self.colours.border_style, }), ) .x_axis(x_axis) @@ -652,7 +788,7 @@ impl Painter { } else { Marker::Braille }) - .style(Style::default().fg(COLOUR_LIST[0])) + .style(Style::default().fg(self.colours.rx_colour)) .data(&network_data_rx), Dataset::default() .name("TX") @@ -661,7 +797,7 @@ impl Painter { } else { Marker::Braille }) - .style(Style::default().fg(COLOUR_LIST[1])) + .style(Style::default().fg(self.colours.tx_colour)) .data(&network_data_tx), ]) .render(f, draw_loc); @@ -686,7 +822,9 @@ impl Painter { } else { vec![vec![rx_display, tx_display]] }; - let mapped_network = total_network.iter().map(|val| Row::Data(val.iter())); + let mapped_network = total_network + .iter() + .map(|val| Row::StyledData(val.iter(), self.colours.text_style)); // Calculate widths let width_ratios: Vec; @@ -716,11 +854,12 @@ impl Painter { ) .block(Block::default().borders(Borders::ALL).border_style( match app_state.current_widget_selected { - app::WidgetPosition::Network => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Network => self.colours.highlighted_border_style, + _ => self.colours.border_style, }, )) - .header_style(Style::default().fg(TABLE_HEADER_COLOUR)) + .header_style(Style::default().fg(self.colours.table_header_colour)) + .style(Style::default().fg(self.colours.text_colour)) .widths( &(intrinsic_widths .into_iter() @@ -755,15 +894,15 @@ impl Painter { == app_state.currently_selected_temperature_position - start_position { temp_row_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) + self.colours.scroll_entry_style } else { if temp_row_counter >= 0 { temp_row_counter += 1; } - Style::default().fg(TEXT_COLOUR) + Style::default().fg(self.colours.text_colour) } } - _ => Style::default().fg(TEXT_COLOUR), + _ => Style::default().fg(self.colours.text_colour), }, ) }); @@ -779,14 +918,15 @@ impl Painter { Table::new(TEMP_HEADERS.iter(), temperature_rows) .block( Block::default() - .title("Temperatures") + .title(" Temperatures ") + .title_style(self.colours.text_style) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - app::WidgetPosition::Temp => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Temp => self.colours.highlighted_border_style, + _ => self.colours.border_style, }), ) - .header_style(Style::default().fg(TABLE_HEADER_COLOUR)) + .header_style(Style::default().fg(self.colours.table_header_colour)) .widths( &(intrinsic_widths .into_iter() @@ -820,21 +960,21 @@ impl Painter { == app_state.currently_selected_disk_position - start_position { disk_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) + self.colours.scroll_entry_style } else { if disk_counter >= 0 { disk_counter += 1; } - Style::default().fg(TEXT_COLOUR) + Style::default().fg(self.colours.text_colour) } } - _ => Style::default().fg(TEXT_COLOUR), + _ => Style::default().fg(self.colours.text_colour), }, ) }); // Calculate widths - // TODO: Ellipsis on strings? + // TODO: [PRETTY] Ellipsis on strings? let width = f64::from(draw_loc.width); let width_ratios = [0.2, 0.15, 0.13, 0.13, 0.13, 0.13, 0.13]; let variable_intrinsic_results = @@ -845,14 +985,15 @@ impl Painter { Table::new(DISK_HEADERS.iter(), disk_rows) .block( Block::default() - .title("Disk") + .title(" Disk ") + .title_style(self.colours.text_style) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - app::WidgetPosition::Disk => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Disk => self.colours.highlighted_border_style, + _ => self.colours.border_style, }), ) - .header_style(Style::default().fg(TABLE_HEADER_COLOUR)) + .header_style(Style::default().fg(self.colours.table_header_colour)) .widths( &(intrinsic_widths .into_iter() @@ -875,89 +1016,101 @@ impl Painter { let cursor_position = app_state.get_cursor_position(); - let query_with_cursor: Vec = - if let app::WidgetPosition::ProcessSearch = app_state.current_widget_selected { - if cursor_position >= query.len() { - let mut q = vec![Text::styled( - shrunk_query.to_string(), - Style::default().fg(TEXT_COLOUR), - )]; - - q.push(Text::styled( - " ".to_string(), - Style::default().fg(TEXT_COLOUR).bg(TABLE_HEADER_COLOUR), - )); - - q - } else { - shrunk_query - .chars() - .enumerate() - .map(|(itx, c)| { - if let app::WidgetPosition::ProcessSearch = - app_state.current_widget_selected - { - if itx == cursor_position { - return Text::styled( - c.to_string(), - Style::default().fg(TEXT_COLOUR).bg(TABLE_HEADER_COLOUR), - ); - } - } - Text::styled(c.to_string(), Style::default().fg(TEXT_COLOUR)) - }) - .collect::>() - } - } else { - vec![Text::styled( + let query_with_cursor: Vec = if let app::WidgetPosition::ProcessSearch = + app_state.current_widget_selected + { + if cursor_position >= query.len() { + let mut q = vec![Text::styled( shrunk_query.to_string(), - Style::default().fg(TEXT_COLOUR), - )] - }; + Style::default().fg(self.colours.text_colour), + )]; + + q.push(Text::styled( + " ".to_string(), + Style::default().bg(self.colours.cursor_colour), + )); + + q + } else { + shrunk_query + .chars() + .enumerate() + .map(|(itx, c)| { + if let app::WidgetPosition::ProcessSearch = + app_state.current_widget_selected + { + if itx == cursor_position { + return Text::styled( + c.to_string(), + Style::default() + .fg(self.colours.text_colour) + .bg(self.colours.table_header_colour), + ); + } + } + Text::styled(c.to_string(), Style::default().fg(self.colours.text_colour)) + }) + .collect::>() + } + } else { + vec![Text::styled( + shrunk_query.to_string(), + Style::default().fg(self.colours.text_colour), + )] + }; let mut search_text = vec![if app_state.search_state.is_searching_with_pid() { Text::styled( "Search by PID (Tab for Name): ", - Style::default().fg(TABLE_HEADER_COLOUR), + Style::default().fg(self.colours.table_header_colour), ) } else { Text::styled( "Search by Name (Tab for PID): ", - Style::default().fg(TABLE_HEADER_COLOUR), + Style::default().fg(self.colours.table_header_colour), ) }]; // Text options shamelessly stolen from VS Code. let option_text = vec![ - Text::styled("\n\n", Style::default().fg(TABLE_HEADER_COLOUR)), + Text::styled( + "\n\n", + Style::default().fg(self.colours.table_header_colour), + ), Text::styled( "Match Case (Alt+C)", - Style::default().fg(TABLE_HEADER_COLOUR), + Style::default().fg(self.colours.table_header_colour), ), if !app_state.search_state.is_ignoring_case() { - Text::styled("[*]", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("[*]", Style::default().fg(self.colours.table_header_colour)) } else { - Text::styled("[ ]", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("[ ]", Style::default().fg(self.colours.table_header_colour)) }, - Text::styled(" ", Style::default().fg(TABLE_HEADER_COLOUR)), + Text::styled( + " ", + Style::default().fg(self.colours.table_header_colour), + ), Text::styled( "Match Whole Word (Alt+W)", - Style::default().fg(TABLE_HEADER_COLOUR), + Style::default().fg(self.colours.table_header_colour), ), if app_state.search_state.is_searching_whole_word() { - Text::styled("[*]", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("[*]", Style::default().fg(self.colours.table_header_colour)) } else { - Text::styled("[ ]", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("[ ]", Style::default().fg(self.colours.table_header_colour)) }, - Text::styled(" ", Style::default().fg(TABLE_HEADER_COLOUR)), + Text::styled( + " ", + Style::default().fg(self.colours.table_header_colour), + ), Text::styled( "Use Regex (Alt+R)", - Style::default().fg(TABLE_HEADER_COLOUR), + Style::default().fg(self.colours.table_header_colour), ), if app_state.search_state.is_searching_with_regex() { - Text::styled("[*]", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("[*]", Style::default().fg(self.colours.table_header_colour)) } else { - Text::styled("[ ]", Style::default().fg(TABLE_HEADER_COLOUR)) + Text::styled("[ ]", Style::default().fg(self.colours.table_header_colour)) }, ]; @@ -970,12 +1123,12 @@ impl Painter { Style::default().fg(Color::Red) } else { match app_state.current_widget_selected { - app::WidgetPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::ProcessSearch => self.colours.highlighted_border_style, + _ => self.colours.border_style, } }, )) - .style(Style::default().fg(Color::Gray)) + .style(Style::default().fg(self.colours.text_colour)) .alignment(Alignment::Left) .wrap(false) .render(f, draw_loc); @@ -1033,15 +1186,15 @@ impl Painter { == app_state.currently_selected_process_position - start_position { process_counter = -1; - Style::default().fg(Color::Black).bg(Color::Cyan) + self.colours.scroll_entry_style } else { if process_counter >= 0 { process_counter += 1; } - Style::default().fg(TEXT_COLOUR) + Style::default().fg(self.colours.text_colour) } } - _ => Style::default().fg(TEXT_COLOUR), + _ => Style::default().fg(self.colours.text_colour), }, ) }); @@ -1086,14 +1239,15 @@ impl Painter { Table::new(process_headers.iter(), process_rows) .block( Block::default() - .title("Processes") + .title(" Processes ") + .title_style(self.colours.text_style) .borders(Borders::ALL) .border_style(match app_state.current_widget_selected { - app::WidgetPosition::Process => *CANVAS_HIGHLIGHTED_BORDER_STYLE, - _ => *CANVAS_BORDER_STYLE, + app::WidgetPosition::Process => self.colours.highlighted_border_style, + _ => self.colours.border_style, }), ) - .header_style(Style::default().fg(TABLE_HEADER_COLOUR)) + .header_style(Style::default().fg(self.colours.table_header_colour)) .widths( &(intrinsic_widths .into_iter() diff --git a/src/main.rs b/src/main.rs index 3920b7c6..40631dce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,12 @@ enum ResetEvent { #[derive(Deserialize)] struct Config { + flags: Option, + colors: Option, +} + +#[derive(Deserialize)] +struct ConfigFlags { avg_cpu: Option, dot_marker: Option, temperature_type: Option, @@ -69,6 +75,22 @@ struct Config { regex: Option, } +#[derive(Deserialize)] +struct ConfigColours { + table_header_color: Option, + cpu_core_colors: Option>, + ram_color: Option, + swap_color: Option, + rx_color: Option, + tx_color: Option, + border_color: Option, + highlighted_border_color: Option, + text_color: Option, + cursor_color: Option, + scroll_entry_text_color: Option, + scroll_entry_bg_color: Option, +} + fn main() -> error::Result<()> { //Parse command line options let matches = clap_app!(app => @@ -119,8 +141,12 @@ fn main() -> error::Result<()> { .value_of("RATE_MILLIS") .unwrap_or(&DEFAULT_REFRESH_RATE_IN_MILLISECONDS.to_string()) .parse::()? - } else if let Some(rate) = config_toml.rate { - rate as u128 + } else if let Some(flags) = &config_toml.flags { + if let Some(rate) = flags.rate { + rate as u128 + } else { + constants::DEFAULT_REFRESH_RATE_IN_MILLISECONDS + } } else { constants::DEFAULT_REFRESH_RATE_IN_MILLISECONDS }; @@ -142,43 +168,63 @@ fn main() -> error::Result<()> { data_harvester::temperature::TemperatureType::Kelvin } else if matches.is_present("CELSIUS") { data_harvester::temperature::TemperatureType::Celsius - } else if let Some(temp_type) = config_toml.temperature_type { - // Give lowest priority to config. - match temp_type.as_str() { - "fahrenheit" | "f" => data_harvester::temperature::TemperatureType::Fahrenheit, - "kelvin" | "k" => data_harvester::temperature::TemperatureType::Kelvin, - "celsius" | "c" => data_harvester::temperature::TemperatureType::Celsius, - _ => data_harvester::temperature::TemperatureType::Celsius, + } else if let Some(flags) = &config_toml.flags { + if let Some(temp_type) = &flags.temperature_type { + // Give lowest priority to config. + match temp_type.as_str() { + "fahrenheit" | "f" => data_harvester::temperature::TemperatureType::Fahrenheit, + "kelvin" | "k" => data_harvester::temperature::TemperatureType::Kelvin, + "celsius" | "c" => data_harvester::temperature::TemperatureType::Celsius, + _ => data_harvester::temperature::TemperatureType::Celsius, + } + } else { + data_harvester::temperature::TemperatureType::Celsius } } else { data_harvester::temperature::TemperatureType::Celsius }; let show_average_cpu = if matches.is_present("AVG_CPU") { true - } else if let Some(avg_cpu) = config_toml.avg_cpu { - avg_cpu + } else if let Some(flags) = &config_toml.flags { + if let Some(avg_cpu) = flags.avg_cpu { + avg_cpu + } else { + false + } } else { false }; let use_dot = if matches.is_present("DOT_MARKER") { true - } else if let Some(dot_marker) = config_toml.dot_marker { - dot_marker + } else if let Some(flags) = &config_toml.flags { + if let Some(dot_marker) = flags.dot_marker { + dot_marker + } else { + false + } } else { false }; let left_legend = if matches.is_present("LEFT_LEGEND") { true - } else if let Some(left_legend) = config_toml.left_legend { - left_legend + } else if let Some(flags) = &config_toml.flags { + if let Some(left_legend) = flags.left_legend { + left_legend + } else { + false + } } else { false }; let use_current_cpu_total = if matches.is_present("USE_CURR_USAGE") { true - } else if let Some(current_usage) = config_toml.current_usage { - current_usage + } else if let Some(flags) = &config_toml.flags { + if let Some(current_usage) = flags.current_usage { + current_usage + } else { + false + } } else { false }; @@ -196,34 +242,42 @@ fn main() -> error::Result<()> { // Enable grouping immediately if set. if matches.is_present("GROUP_PROCESSES") { app.toggle_grouping(); - } else if let Some(grouping) = config_toml.group_processes { - if grouping { - app.toggle_grouping(); + } else if let Some(flags) = &config_toml.flags { + if let Some(grouping) = flags.group_processes { + if grouping { + app.toggle_grouping(); + } } } // Set default search method if matches.is_present("CASE_SENSITIVE") { app.search_state.toggle_ignore_case(); - } else if let Some(case_sensitive) = config_toml.case_sensitive { - if case_sensitive { - app.search_state.toggle_ignore_case(); + } else if let Some(flags) = &config_toml.flags { + if let Some(case_sensitive) = flags.case_sensitive { + if case_sensitive { + app.search_state.toggle_ignore_case(); + } } } if matches.is_present("WHOLE_WORD") { app.search_state.toggle_search_whole_word(); - } else if let Some(whole_word) = config_toml.whole_word { - if whole_word { - app.search_state.toggle_search_whole_word(); + } else if let Some(flags) = &config_toml.flags { + if let Some(whole_word) = flags.whole_word { + if whole_word { + app.search_state.toggle_search_whole_word(); + } } } if matches.is_present("REGEX_DEFAULT") { app.search_state.toggle_search_regex(); - } else if let Some(regex) = config_toml.regex { - if regex { - app.search_state.toggle_search_regex(); + } else if let Some(flags) = &config_toml.flags { + if let Some(regex) = flags.regex { + if regex { + app.search_state.toggle_search_regex(); + } } } @@ -312,6 +366,13 @@ fn main() -> error::Result<()> { } let mut painter = canvas::Painter::default(); + if let Err(config_check) = generate_config_colours(&config_toml, &mut painter) { + cleanup_terminal(&mut terminal)?; + return Err(config_check); + } + + painter.colours.generate_remaining_cpu_colours(); + loop { // TODO: [OPT] this should not block... if let Ok(recv) = rx.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) { @@ -485,6 +546,70 @@ fn cleanup_terminal( Ok(()) } +fn generate_config_colours( + config_toml: &Config, painter: &mut canvas::Painter, +) -> error::Result<()> { + if let Some(colours) = &config_toml.colors { + if let Some(border_color) = &colours.border_color { + painter.colours.set_border_colour(border_color)?; + } + + if let Some(highlighted_border_color) = &colours.highlighted_border_color { + painter + .colours + .set_highlighted_border_colour(highlighted_border_color)?; + } + + if let Some(text_color) = &colours.text_color { + painter.colours.set_text_colour(text_color)?; + } + + if let Some(cpu_core_colors) = &(colours.cpu_core_colors) { + painter.colours.set_cpu_colours(cpu_core_colors)?; + } + + if let Some(ram_color) = &colours.ram_color { + painter.colours.set_ram_colour(ram_color)?; + } + + if let Some(swap_color) = &colours.swap_color { + painter.colours.set_swap_colour(swap_color)?; + } + + if let Some(rx_color) = &colours.rx_color { + painter.colours.set_rx_colour(rx_color)?; + } + + if let Some(tx_color) = &colours.tx_color { + painter.colours.set_tx_colour(tx_color)?; + } + + if let Some(cursor_color) = &colours.cursor_color { + painter.colours.set_cursor_colour(cursor_color)?; + } + + if let Some(table_header_color) = &colours.table_header_color { + painter + .colours + .set_table_header_colour(table_header_color)?; + } + + if let Some(scroll_entry_text_color) = &colours.scroll_entry_text_color { + painter + .colours + .set_scroll_entry_text_color(scroll_entry_text_color)?; + } + + if let Some(scroll_entry_bg_color) = &colours.scroll_entry_bg_color { + painter + .colours + .set_scroll_entry_bg_color(scroll_entry_bg_color)?; + } + } + + Ok(()) +} + /// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs fn panic_hook(panic_info: &PanicInfo<'_>) { let mut stdout = stdout();