diff --git a/src/ui/draw_blocks/headers.rs b/src/ui/draw_blocks/headers.rs index 700b3df..9ded21e 100644 --- a/src/ui/draw_blocks/headers.rs +++ b/src/ui/draw_blocks/headers.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{rc::Rc, sync::Arc}; use parking_lot::Mutex; use ratatui::{ @@ -15,83 +15,59 @@ use crate::{ ui::{FrameData, GuiState, Status, gui_state::Region}, }; -// Draw heading bar at top of program, always visible -/// TODO Should separate into loading icon/headers/help functions -#[allow(clippy::too_many_lines)] -pub fn draw( - area: Rect, +/// Generate a header paragrah with it's width +fn gen_header<'a>( colors: AppColors, - frame: &mut Frame, fd: &FrameData, - gui_state: &Arc>, - keymap: &Keymap, -) { - let gen_style = |bg: Option, fg: Color| { - bg.map_or_else( - || Style::default().fg(fg), - |bg| Style::default().bg(bg).fg(fg), - ) - }; + header: Header, + width: usize, +) -> (Paragraph<'a>, u16) { + let block = gen_header_block(colors, fd, header); - frame.render_widget( - Block::default().style(gen_style(Some(colors.headers_bar.background), Color::Reset)), - area, + let text = format!( + "{x: suffix = " ▲", - SortedOrder::Desc => suffix = " ▼", - } - color = colors.headers_bar.text_selected; - }; +// Generate a block for the header, if the header is currently being used to sort a column, then highlight it white +fn gen_header_block<'a>(colors: AppColors, fd: &FrameData, header: Header) -> (Color, &'a str) { + let mut color = colors.headers_bar.text; + let mut suffix = ""; + if let Some((a, b)) = &fd.sorted_by { + if &header == a { + match b { + SortedOrder::Asc => suffix = " ▲", + SortedOrder::Desc => suffix = " ▼", + } + color = colors.headers_bar.text_selected; }; - - (color, suffix) }; - // Generate block for the headers, state and status has a specific layout, others all equal - // width is dependant on it that column is selected to sort - or not - // TODO - yes this is a mess, needs documenting correctly - let gen_header = |header: &Header, width: usize, colors: AppColors| { - let block = header_block(header, colors); + (color, suffix) +} - let text = format!( - "{x:, fg: Color) -> Style { + bg.map_or_else( + || Style::default().fg(fg), + |bg| Style::default().bg(bg).fg(fg), + ) +} +/// Generate the text to display on the show help section, as can change with a custom keymap +fn gen_help_text(fd: &FrameData, keymap: &Keymap) -> String { let suffix = if fd.status.contains(&Status::Help) { "exit" } else { "show" }; - let info_text = if keymap.toggle_help == Keymap::new().toggle_help { + if keymap.toggle_help == Keymap::new().toggle_help { format!("( h ) {suffix} help{MARGIN}") } else if let Some(secondary) = keymap.toggle_help.1 { format!( @@ -100,44 +76,78 @@ pub fn draw( ) } else { format!(" ( {} ) {suffix} help{MARGIN}", keymap.toggle_help.0) - }; - let info_width = info_text.chars().count(); + } +} - let column_width = usize::from(area.width).saturating_sub(info_width); - let column_width = if column_width > 0 { column_width } else { 1 }; - let splits = if fd.has_containers { - vec![ - Constraint::Max(4), - Constraint::Max(column_width.try_into().unwrap_or_default()), - Constraint::Max(info_width.try_into().unwrap_or_default()), - ] +/// Draw the show/hide help section +fn draw_help( + colors: AppColors, + f: &mut Frame, + fd: &FrameData, + help_text: String, + gui_state: &Arc>, + split_bar: &Rc<[Rect]>, +) { + let help_text_color = if fd.status.contains(&Status::Help) { + colors.headers_bar.text } else { - CONSTRAINT_100.to_vec() + colors.headers_bar.text_selected }; - let split_bar = Layout::default() - .direction(Direction::Horizontal) - .constraints(splits) - .split(area); + let help_paragraph = Paragraph::new(help_text) + .style(gen_style(None, help_text_color)) + .alignment(Alignment::Right); - // Draw loading icon, or not, and a prefix with a single space + // If no containers, don't display the headers, could maybe do this first? + let help_index = if fd.has_containers { 2 } else { 0 }; + gui_state + .lock() + .update_region_map(Region::HelpPanel, split_bar[help_index]); + f.render_widget(help_paragraph, split_bar[help_index]); +} + +// Draw loading icon, or not, and a prefix with a single space +fn draw_loading_spinner(colors: AppColors, f: &mut Frame, fd: &FrameData, rect: Rect) { let loading_paragraph = Paragraph::new(format!("{:>2}", fd.loading_icon)) .style(gen_style(None, colors.headers_bar.loading_spinner)) .alignment(Alignment::Left); - frame.render_widget(loading_paragraph, split_bar[0]); + f.render_widget(loading_paragraph, rect); +} + +/// Draw the sortable column headers (name/state/status etc) +fn draw_columns( + colors: AppColors, + f: &mut Frame, + fd: &FrameData, + gui_state: &Arc>, + split_bar: &Rc<[Rect]>, +) { if fd.has_containers { let header_section_width = split_bar[1].width; let mut counter = 0; + // Meta data to iterate over to create blocks with correct widths + let header_meta = [ + (Header::Name, fd.columns.name.1), + (Header::State, fd.columns.state.1), + (Header::Status, fd.columns.status.1), + (Header::Cpu, fd.columns.cpu.1), + (Header::Memory, fd.columns.mem.1 + fd.columns.mem.2 + 3), + (Header::Id, fd.columns.id.1), + (Header::Image, fd.columns.image.1), + (Header::Rx, fd.columns.net_rx.1), + (Header::Tx, fd.columns.net_tx.1), + ]; + // Only show a header if the header cumulative header width is less than the header section width let header_data = header_meta - .iter() - .filter_map(|i| { - let header_block = gen_header(&i.0, i.1.into(), colors); + .into_iter() + .filter_map(|(header, width)| { + let header_block = gen_header(colors, fd, header, usize::from(width)); counter += header_block.1; if counter <= header_section_width { - Some((header_block.0, i.0, Constraint::Max(header_block.1))) + Some((header_block.0, header, Constraint::Max(header_block.1))) } else { None } @@ -155,27 +165,55 @@ pub fn draw( gui_state .lock() .update_region_map(Region::Header(header), rect); - frame.render_widget(paragraph, rect); + f.render_widget(paragraph, rect); } } +} - // show/hide help - let help_text_color = if fd.status.contains(&Status::Help) { - colors.headers_bar.text - } else { - colors.headers_bar.text_selected +// Draw heading bar at top of program, always visible +pub fn draw( + area: Rect, + colors: AppColors, + f: &mut Frame, + fd: &FrameData, + gui_state: &Arc>, + keymap: &Keymap, +) { + let gen_style = |bg: Option, fg: Color| { + bg.map_or_else( + || Style::default().fg(fg), + |bg| Style::default().bg(bg).fg(fg), + ) }; - let help_paragraph = Paragraph::new(info_text) - .style(gen_style(None, help_text_color)) - .alignment(Alignment::Right); + f.render_widget( + Block::default().style(gen_style(Some(colors.headers_bar.background), Color::Reset)), + area, + ); - // If no containers, don't display the headers, could maybe do this first? - let help_index = if fd.has_containers { 2 } else { 0 }; - gui_state - .lock() - .update_region_map(Region::HelpPanel, split_bar[help_index]); - frame.render_widget(help_paragraph, split_bar[help_index]); + let help_text = gen_help_text(fd, keymap); + let help_width = help_text.chars().count(); + + let column_width = usize::from(area.width).saturating_sub(help_width); + let column_width = if column_width > 0 { column_width } else { 1 }; + let splits = if fd.has_containers { + vec![ + Constraint::Max(4), + Constraint::Max(column_width.try_into().unwrap_or_default()), + Constraint::Max(help_width.try_into().unwrap_or_default()), + ] + } else { + CONSTRAINT_100.to_vec() + }; + + let split_bar = Layout::default() + .direction(Direction::Horizontal) + .constraints(splits) + .split(area); + + draw_loading_spinner(colors, f, fd, split_bar[0]); + draw_columns(colors, f, fd, gui_state, &split_bar); + draw_help(colors, f, fd, help_text, gui_state, &split_bar); } #[cfg(test)]