refactor: headers::draw()

This commit is contained in:
Jack Wills
2025-02-23 13:14:22 +00:00
parent 6ea4ba86bd
commit bb4eec2b5e
+136 -98
View File
@@ -1,4 +1,4 @@
use std::sync::Arc; use std::{rc::Rc, sync::Arc};
use parking_lot::Mutex; use parking_lot::Mutex;
use ratatui::{ use ratatui::{
@@ -15,83 +15,59 @@ use crate::{
ui::{FrameData, GuiState, Status, gui_state::Region}, ui::{FrameData, GuiState, Status, gui_state::Region},
}; };
// Draw heading bar at top of program, always visible /// Generate a header paragrah with it's width
/// TODO Should separate into loading icon/headers/help functions fn gen_header<'a>(
#[allow(clippy::too_many_lines)]
pub fn draw(
area: Rect,
colors: AppColors, colors: AppColors,
frame: &mut Frame,
fd: &FrameData, fd: &FrameData,
gui_state: &Arc<Mutex<GuiState>>, header: Header,
keymap: &Keymap, width: usize,
) { ) -> (Paragraph<'a>, u16) {
let gen_style = |bg: Option<Color>, fg: Color| { let block = gen_header_block(colors, fd, header);
bg.map_or_else(
|| Style::default().fg(fg),
|bg| Style::default().bg(bg).fg(fg),
)
};
frame.render_widget( let text = format!(
Block::default().style(gen_style(Some(colors.headers_bar.background), Color::Reset)), "{x:<width$}{MARGIN}",
area, x = format!("{header}{ic}", ic = block.1),
); );
let count = u16::try_from(text.chars().count()).unwrap_or_default();
let status = Paragraph::new(text)
.style(gen_style(None, block.0))
.alignment(Alignment::Left);
(status, count)
}
// Generate a block for the header, if the header is currently being used to sort a column, then highlight it white // Generate a block for the header, if the header is currently being used to sort a column, then highlight it white
let header_block = |x: &Header, colors: AppColors| { fn gen_header_block<'a>(colors: AppColors, fd: &FrameData, header: Header) -> (Color, &'a str) {
let mut color = colors.headers_bar.text; let mut color = colors.headers_bar.text;
let mut suffix = ""; let mut suffix = "";
if let Some((a, b)) = &fd.sorted_by { if let Some((a, b)) = &fd.sorted_by {
if x == a { if &header == a {
match b { match b {
SortedOrder::Asc => suffix = "", SortedOrder::Asc => suffix = "",
SortedOrder::Desc => suffix = "", SortedOrder::Desc => suffix = "",
} }
color = colors.headers_bar.text_selected; color = colors.headers_bar.text_selected;
};
}; };
(color, suffix)
}; };
// Generate block for the headers, state and status has a specific layout, others all equal (color, suffix)
// 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);
let text = format!( fn gen_style(bg: Option<Color>, fg: Color) -> Style {
"{x:<width$}{MARGIN}", bg.map_or_else(
x = format!("{header}{ic}", ic = block.1), || Style::default().fg(fg),
); |bg| Style::default().bg(bg).fg(fg),
let count = u16::try_from(text.chars().count()).unwrap_or_default(); )
let status = Paragraph::new(text) }
.style(gen_style(None, block.0))
.alignment(Alignment::Left);
(status, count)
};
// 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),
];
/// 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) { let suffix = if fd.status.contains(&Status::Help) {
"exit" "exit"
} else { } else {
"show" "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}") format!("( h ) {suffix} help{MARGIN}")
} else if let Some(secondary) = keymap.toggle_help.1 { } else if let Some(secondary) = keymap.toggle_help.1 {
format!( format!(
@@ -100,44 +76,78 @@ pub fn draw(
) )
} else { } else {
format!(" ( {} ) {suffix} help{MARGIN}", keymap.toggle_help.0) 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); /// Draw the show/hide help section
let column_width = if column_width > 0 { column_width } else { 1 }; fn draw_help(
let splits = if fd.has_containers { colors: AppColors,
vec![ f: &mut Frame,
Constraint::Max(4), fd: &FrameData,
Constraint::Max(column_width.try_into().unwrap_or_default()), help_text: String,
Constraint::Max(info_width.try_into().unwrap_or_default()), gui_state: &Arc<Mutex<GuiState>>,
] split_bar: &Rc<[Rect]>,
) {
let help_text_color = if fd.status.contains(&Status::Help) {
colors.headers_bar.text
} else { } else {
CONSTRAINT_100.to_vec() colors.headers_bar.text_selected
}; };
let split_bar = Layout::default() let help_paragraph = Paragraph::new(help_text)
.direction(Direction::Horizontal) .style(gen_style(None, help_text_color))
.constraints(splits) .alignment(Alignment::Right);
.split(area);
// 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)) let loading_paragraph = Paragraph::new(format!("{:>2}", fd.loading_icon))
.style(gen_style(None, colors.headers_bar.loading_spinner)) .style(gen_style(None, colors.headers_bar.loading_spinner))
.alignment(Alignment::Left); .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<Mutex<GuiState>>,
split_bar: &Rc<[Rect]>,
) {
if fd.has_containers { if fd.has_containers {
let header_section_width = split_bar[1].width; let header_section_width = split_bar[1].width;
let mut counter = 0; 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 // Only show a header if the header cumulative header width is less than the header section width
let header_data = header_meta let header_data = header_meta
.iter() .into_iter()
.filter_map(|i| { .filter_map(|(header, width)| {
let header_block = gen_header(&i.0, i.1.into(), colors); let header_block = gen_header(colors, fd, header, usize::from(width));
counter += header_block.1; counter += header_block.1;
if counter <= header_section_width { 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 { } else {
None None
} }
@@ -155,27 +165,55 @@ pub fn draw(
gui_state gui_state
.lock() .lock()
.update_region_map(Region::Header(header), rect); .update_region_map(Region::Header(header), rect);
frame.render_widget(paragraph, rect); f.render_widget(paragraph, rect);
} }
} }
}
// show/hide help // Draw heading bar at top of program, always visible
let help_text_color = if fd.status.contains(&Status::Help) { pub fn draw(
colors.headers_bar.text area: Rect,
} else { colors: AppColors,
colors.headers_bar.text_selected f: &mut Frame,
fd: &FrameData,
gui_state: &Arc<Mutex<GuiState>>,
keymap: &Keymap,
) {
let gen_style = |bg: Option<Color>, fg: Color| {
bg.map_or_else(
|| Style::default().fg(fg),
|bg| Style::default().bg(bg).fg(fg),
)
}; };
let help_paragraph = Paragraph::new(info_text) f.render_widget(
.style(gen_style(None, help_text_color)) Block::default().style(gen_style(Some(colors.headers_bar.background), Color::Reset)),
.alignment(Alignment::Right); area,
);
// If no containers, don't display the headers, could maybe do this first? let help_text = gen_help_text(fd, keymap);
let help_index = if fd.has_containers { 2 } else { 0 }; let help_width = help_text.chars().count();
gui_state
.lock() let column_width = usize::from(area.width).saturating_sub(help_width);
.update_region_map(Region::HelpPanel, split_bar[help_index]); let column_width = if column_width > 0 { column_width } else { 1 };
frame.render_widget(help_paragraph, split_bar[help_index]); 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)] #[cfg(test)]