From f90e831239823e536b1dba2a15d703a39b0f6dbb Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Fri, 21 Feb 2025 22:00:12 +0000 Subject: [PATCH] feat: filter panel colors --- example_config/example.config.json | 9 ++- example_config/example.config.jsonc | 15 +++- example_config/example.config.toml | 14 ++++ src/config/color_parser.rs | 34 ++++++++ src/config/config.toml | 16 +++- src/ui/draw_blocks/filter.rs | 121 ++++++++++++++++++++++------ src/ui/mod.rs | 2 +- 7 files changed, 184 insertions(+), 27 deletions(-) diff --git a/example_config/example.config.json b/example_config/example.config.json index bf382a3..68887fb 100644 --- a/example_config/example.config.json +++ b/example_config/example.config.json @@ -8,7 +8,7 @@ "show_std_err": false, "show_timestamp": true, "timezone": "Etc/UTC", - "timestamp_format":"%Y-%m-%dT%H:%M:%S.%8f", + "timestamp_format": "%Y-%m-%dT%H:%M:%S.%8f", "use_cli": false, "colors": { "borders": { @@ -64,6 +64,13 @@ "text_rx": "#FFE9C1", "text_tx": "#CD8C8C" }, + "filter": { + "background": "reset", + "text": "gray", + "selected_filter_background": "gray", + "selected_filter_text": "black", + "highlight": "magenta" + }, "headers_bar": { "background": "magenta", "loading_spinner": "white", diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index 6ceb44d..1a838cd 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -22,7 +22,7 @@ "host": "/var/run/docker.sock", // Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.01234567 // *Should* accept any valid strftime string up to 32 chars - "timestamp_format":"%Y-%m-%dT%H:%M:%S.%8f", + "timestamp_format": "%Y-%m-%dT%H:%M:%S.%8f", // Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC "timezone": "Etc/UTC", // Directory for saving exported logs, defaults to `$HOME`, this is automatically *correctly* calculated for Linux, Mac, and Windows @@ -251,6 +251,19 @@ // Ports & IP listing text "text": "white" }, + // The filter panel + "filter": { + // Background color of panel + "background": "reset", + // color of text + "text": "gray", + // background color of the selected filter by item (Name/Image/Status/All) + "selected_filter_background": "gray", + // text color of the selected filter by item (Name/Image/Status/All) + "selected_filter_text": "black", + // Highlighted text color + "highlight": "magenta" + }, // The logs panel, will only be applied if color_logs is false "logs": { // Background color of panel diff --git a/example_config/example.config.toml b/example_config/example.config.toml index 8115b9c..66162bd 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -163,6 +163,20 @@ running_healthy ="green" running_unhealthy="#FFB224" unknown="red" +# The filter panel +[colors.filter] +# Background color of panel +background = "reset" +# color of text +text="gray" +# background color of the selected filter by item (Name/Image/Status/All) +selected_filter_background="gray" +# text color of the selected filter by item (Name/Image/Status/All) +selected_filter_text="black" +# Highlighted text color +highlight="magenta" + + # The color the of Docker commands available for each container [colors.commands] # Background color of panel diff --git a/src/config/color_parser.rs b/src/config/color_parser.rs index a591cbd..1599637 100644 --- a/src/config/color_parser.rs +++ b/src/config/color_parser.rs @@ -73,6 +73,22 @@ impl From> for AppColors { Self::map_color(ep.text.as_deref(), &mut app_colors.popup_error.text); } + // Filter panel + if let Some(fc) = config_colors.filter { + Self::map_color(fc.background.as_deref(), &mut app_colors.filter.background); + Self::map_color(fc.highlight.as_deref(), &mut app_colors.filter.highlight); + + Self::map_color( + fc.selected_filter_background.as_deref(), + &mut app_colors.filter.selected_filter_background, + ); + Self::map_color( + fc.selected_filter_text.as_deref(), + &mut app_colors.filter.selected_filter_text, + ); + Self::map_color(fc.text.as_deref(), &mut app_colors.filter.text); + } + // Help Popup if let Some(hp) = config_colors.popup_help { Self::map_color( @@ -221,6 +237,7 @@ optional_config_struct!( ConfigCommands, background, pause, restart, stop, delete, resume, start; ConfigContainers, background, icon, text, text_rx, text_tx; ConfigContainerState, background, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown; + ConfigFilter, background, text, selected_filter_background, selected_filter_text, highlight; ConfigHeadersBar, background, loading_spinner, text, text_selected; ConfigLogs, background, text ); @@ -233,6 +250,7 @@ config_struct!( Commands, background, pause, restart, stop, delete, resume, start; Containers, background, icon, text, text_rx, text_tx; ContainerState, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown; + Filter, background, text, selected_filter_background, selected_filter_text, highlight; HeadersBar, background, text_selected, loading_spinner, text; Logs, background, text; PopupDelete, background, text, text_highlight; @@ -250,6 +268,7 @@ pub struct ConfigColors { commands: Option, container_state: Option, containers: Option, + filter: Option, headers_bar: Option, logs: Option, popup_delete: Option, @@ -365,6 +384,19 @@ impl ContainerState { } } +/// Default colours for the filter panel +impl Filter { + const fn new() -> Self { + Self { + background: Color::Reset, + highlight: Color::Magenta, + selected_filter_background: Color::Gray, + selected_filter_text: Color::Black, + text: Color::Gray, + } + } +} + /// Default colours for the logs panel, only applied if color_logs is false impl Logs { const fn new() -> Self { @@ -426,6 +458,7 @@ pub struct AppColors { pub commands: Commands, pub container_state: ContainerState, pub containers: Containers, + pub filter: Filter, pub headers_bar: HeadersBar, pub logs: Logs, pub popup_delete: PopupDelete, @@ -444,6 +477,7 @@ impl AppColors { commands: Commands::new(), container_state: ContainerState::new(), containers: Containers::new(), + filter: Filter::new(), headers_bar: HeadersBar::new(), logs: Logs::new(), popup_delete: PopupDelete::new(), diff --git a/src/config/config.toml b/src/config/config.toml index e5deda8..66162bd 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -30,7 +30,7 @@ host = "/var/run/docker.sock" # Display the container logs timestamp with a given timezone, if timezone is unknown, defaults to UTC timezone = "Etc/UTC" -# Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.01234567 +# Display the timestamp in a custom format, if given option is invalid, it will default to %Y-%m-%dT%H:%M:%S.%8f -> 2025-02-18T12:34:56.012345678Z # *Should* accept any valid strftime string up to 32 chars timestamp_format="%Y-%m-%dT%H:%M:%S.%8f" @@ -163,6 +163,20 @@ running_healthy ="green" running_unhealthy="#FFB224" unknown="red" +# The filter panel +[colors.filter] +# Background color of panel +background = "reset" +# color of text +text="gray" +# background color of the selected filter by item (Name/Image/Status/All) +selected_filter_background="gray" +# text color of the selected filter by item (Name/Image/Status/All) +selected_filter_text="black" +# Highlighted text color +highlight="magenta" + + # The color the of Docker commands available for each container [colors.commands] # Background color of panel diff --git a/src/ui/draw_blocks/filter.rs b/src/ui/draw_blocks/filter.rs index 3cce1a5..54e280e 100644 --- a/src/ui/draw_blocks/filter.rs +++ b/src/ui/draw_blocks/filter.rs @@ -1,16 +1,20 @@ use ratatui::{ layout::Rect, - style::{Color, Modifier, Style}, + style::{Modifier, Style, Stylize}, text::{Line, Span}, Frame, }; -use crate::{app_data::FilterBy, ui::FrameData}; +use crate::{app_data::FilterBy, config::AppColors, ui::FrameData}; /// Create the filter_by by spans, coloured dependant on which one is selected -fn filter_by_spans(fd: &FrameData) -> [Span; 4] { - let selected = Style::default().bg(Color::Gray).fg(Color::Black); - let not_selected = Style::default().bg(Color::Reset).fg(Color::Reset); +fn filter_by_spans(colors: AppColors, fd: &FrameData) -> [Span; 4] { + let selected = Style::default() + .bg(colors.filter.selected_filter_background) + .fg(colors.filter.selected_filter_text); + let not_selected = Style::default() + .bg(colors.filter.background) + .fg(colors.filter.text); let name = [" Name ", " Image ", " Status ", " All "]; @@ -31,9 +35,13 @@ fn filter_by_spans(fd: &FrameData) -> [Span; 4] { } /// Draw the filter bar -pub fn draw(area: Rect, frame: &mut Frame, fd: &FrameData) { - let style_but = Style::default().fg(Color::Black).bg(Color::Magenta); - let style_desc = Style::default().fg(Color::Gray).bg(Color::Reset); +pub fn draw(area: Rect, colors: AppColors, frame: &mut Frame, fd: &FrameData) { + let style_but = Style::default() + .fg(colors.filter.selected_filter_text) + .bg(colors.filter.highlight); + let style_desc = Style::default() + .fg(colors.filter.text) + .bg(colors.filter.background); let mut line = vec![ Span::styled(" Esc ", style_but), @@ -41,22 +49,22 @@ pub fn draw(area: Rect, frame: &mut Frame, fd: &FrameData) { Span::styled(" ← by → ", style_but), Span::from(" "), ]; - line.extend_from_slice(&filter_by_spans(fd)); + line.extend_from_slice(&filter_by_spans(colors, fd)); line.extend_from_slice(&[ Span::styled( " term: ", Style::default() - .fg(Color::Magenta) + .fg(colors.filter.highlight) .add_modifier(Modifier::BOLD), ), Span::styled( fd.filter_term .as_ref() .map_or(String::new(), std::clone::Clone::clone), - Style::default().fg(Color::Gray), + Style::default().fg(colors.filter.text), ), ]); - frame.render_widget(Line::from(line), area); + frame.render_widget(Line::from(line).bg(colors.filter.background), area); } #[cfg(test)] @@ -65,9 +73,12 @@ mod tests { use ratatui::style::{Color, Modifier}; - use crate::ui::{ - draw_blocks::tests::{expected_to_vec, get_result, test_setup}, - FrameData, + use crate::{ + config::AppColors, + ui::{ + draw_blocks::tests::{expected_to_vec, get_result, test_setup}, + FrameData, + }, }; #[test] @@ -85,7 +96,7 @@ mod tests { setup .terminal .draw(|f| { - super::draw(setup.area, f, &setup.fd); + super::draw(setup.area, AppColors::new(), f, &setup.fd); }) .unwrap(); @@ -97,12 +108,13 @@ mod tests { let expected_row = expected_to_vec(&expected, row_index); for (result_cell_index, result_cell) in result_row.iter().enumerate() { assert_eq!(result_cell.symbol(), expected_row[result_cell_index]); + match result_cell_index { 0..=4 | 12..=19 => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - 5..=11 => { + 5..=11 | 27..=46 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Gray); } @@ -131,7 +143,7 @@ mod tests { setup .terminal .draw(|f| { - super::draw(setup.area, f, &fd); + super::draw(setup.area, AppColors::new(), f, &fd); }) .unwrap(); @@ -149,7 +161,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - 5..=11 | 54..=55 => { + 5..=11 | 27..=46 | 54..=55 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Gray); } @@ -170,13 +182,13 @@ mod tests { } } - // Test when filter_by chances + // Test when filter_by changes setup.app_data.lock().filter_by_next(); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::draw(setup.area, f, &fd); + super::draw(setup.area, AppColors::new(), f, &fd); }) .unwrap(); @@ -188,13 +200,12 @@ mod tests { let expected_row = expected_to_vec(&expected, row_index); for (result_cell_index, result_cell) in result_row.iter().enumerate() { assert_eq!(result_cell.symbol(), expected_row[result_cell_index]); - match result_cell_index { 0..=4 | 12..=19 => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - 5..=11 | 54..=55 => { + 5..=11 | 21..=26 | 34..=46 | 54..=55 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Gray); } @@ -215,4 +226,68 @@ mod tests { } } } + + #[test] + /// Make sure custom colors are applied + fn test_draw_blocks_filter_row_custom_colors() { + let (w, h) = (140, 1); + let mut setup = test_setup(w, h, true, true); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::Filter); + + setup.app_data.lock().filter_term_push('c'); + setup.app_data.lock().filter_term_push('d'); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + let mut colors = AppColors::new(); + colors.filter.background = Color::White; + colors.filter.highlight = Color::Blue; + colors.filter.selected_filter_background = Color::Red; + colors.filter.selected_filter_text = Color::Yellow; + colors.filter.text = Color::Magenta; + + setup + .terminal + .draw(|f| { + super::draw(setup.area, colors, f, &fd); + }) + .unwrap(); + + let expected = [ + " Esc clear ← by → Name Image Status All term: cd " + ]; + + for (row_index, result_row) in get_result(&setup, w) { + let expected_row = expected_to_vec(&expected, row_index); + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + assert_eq!(result_cell.symbol(), expected_row[result_cell_index]); + match result_cell_index { + 0..=4 | 12..=19 => { + assert_eq!(result_cell.bg, Color::Blue); + assert_eq!(result_cell.fg, Color::Yellow); + } + 5..=11 | 27..=46 | 54..=55 => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Magenta); + } + 21..=26 => { + assert_eq!(result_cell.bg, Color::Red); + assert_eq!(result_cell.fg, Color::Yellow); + } + 47..=53 => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Blue); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d103c22..318ded1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -380,7 +380,7 @@ fn draw_frame( // Draw filter bar if let Some(rect) = whole_layout.get(2) { - draw_blocks::filter::draw(*rect, f, fd); + draw_blocks::filter::draw(*rect, colors, f, fd); } if let Some(id) = fd.delete_confirm.as_ref() {