refactor: headers::draw()
This commit is contained in:
+146
-108
@@ -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,51 +15,14 @@ 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(
|
|
||||||
Block::default().style(gen_style(Some(colors.headers_bar.background), Color::Reset)),
|
|
||||||
area,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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| {
|
|
||||||
let mut color = colors.headers_bar.text;
|
|
||||||
let mut suffix = "";
|
|
||||||
if let Some((a, b)) = &fd.sorted_by {
|
|
||||||
if x == 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);
|
|
||||||
|
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"{x:<width$}{MARGIN}",
|
"{x:<width$}{MARGIN}",
|
||||||
@@ -70,7 +33,99 @@ pub fn draw(
|
|||||||
.style(gen_style(None, block.0))
|
.style(gen_style(None, block.0))
|
||||||
.alignment(Alignment::Left);
|
.alignment(Alignment::Left);
|
||||||
(status, count)
|
(status, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_style(bg: Option<Color>, 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"
|
||||||
|
};
|
||||||
|
|
||||||
|
if keymap.toggle_help == Keymap::new().toggle_help {
|
||||||
|
format!("( h ) {suffix} help{MARGIN}")
|
||||||
|
} else if let Some(secondary) = keymap.toggle_help.1 {
|
||||||
|
format!(
|
||||||
|
" ( {} | {secondary} ) {suffix} help{MARGIN}",
|
||||||
|
keymap.toggle_help.0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(" ( {} ) {suffix} help{MARGIN}", keymap.toggle_help.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the show/hide help section
|
||||||
|
fn draw_help(
|
||||||
|
colors: AppColors,
|
||||||
|
f: &mut Frame,
|
||||||
|
fd: &FrameData,
|
||||||
|
help_text: String,
|
||||||
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
|
split_bar: &Rc<[Rect]>,
|
||||||
|
) {
|
||||||
|
let help_text_color = if fd.status.contains(&Status::Help) {
|
||||||
|
colors.headers_bar.text
|
||||||
|
} else {
|
||||||
|
colors.headers_bar.text_selected
|
||||||
|
};
|
||||||
|
|
||||||
|
let help_paragraph = Paragraph::new(help_text)
|
||||||
|
.style(gen_style(None, help_text_color))
|
||||||
|
.alignment(Alignment::Right);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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 {
|
||||||
|
let header_section_width = split_bar[1].width;
|
||||||
|
|
||||||
|
let mut counter = 0;
|
||||||
|
|
||||||
// Meta data to iterate over to create blocks with correct widths
|
// Meta data to iterate over to create blocks with correct widths
|
||||||
let header_meta = [
|
let header_meta = [
|
||||||
@@ -85,59 +140,14 @@ pub fn draw(
|
|||||||
(Header::Tx, fd.columns.net_tx.1),
|
(Header::Tx, fd.columns.net_tx.1),
|
||||||
];
|
];
|
||||||
|
|
||||||
let suffix = if fd.status.contains(&Status::Help) {
|
|
||||||
"exit"
|
|
||||||
} else {
|
|
||||||
"show"
|
|
||||||
};
|
|
||||||
|
|
||||||
let info_text = if keymap.toggle_help == Keymap::new().toggle_help {
|
|
||||||
format!("( h ) {suffix} help{MARGIN}")
|
|
||||||
} else if let Some(secondary) = keymap.toggle_help.1 {
|
|
||||||
format!(
|
|
||||||
" ( {} | {secondary} ) {suffix} help{MARGIN}",
|
|
||||||
keymap.toggle_help.0
|
|
||||||
)
|
|
||||||
} 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()),
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
CONSTRAINT_100.to_vec()
|
|
||||||
};
|
|
||||||
|
|
||||||
let split_bar = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints(splits)
|
|
||||||
.split(area);
|
|
||||||
|
|
||||||
// Draw loading icon, or not, and a prefix with a single space
|
|
||||||
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]);
|
|
||||||
if fd.has_containers {
|
|
||||||
let header_section_width = split_bar[1].width;
|
|
||||||
|
|
||||||
let mut counter = 0;
|
|
||||||
|
|
||||||
// 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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user