wip: header+column widths

This commit is contained in:
Jack Wills
2022-07-22 19:38:19 +00:00
parent d14744b378
commit 96ca208197
6 changed files with 430 additions and 170 deletions
+29 -6
View File
@@ -1,7 +1,10 @@
use bollard::models::ContainerSummary; use bollard::models::ContainerSummary;
use core::fmt; use core::fmt;
use std::time::{SystemTime, UNIX_EPOCH}; use std::{
use tui::widgets::ListItem; collections::HashMap,
time::{SystemTime, UNIX_EPOCH},
};
use tui::{layout::Rect, widgets::ListItem};
mod container_state; mod container_state;
@@ -18,6 +21,7 @@ pub struct AppData {
pub init: bool, pub init: bool,
pub show_error: bool, pub show_error: bool,
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
// heading_map: HashMap<Header, Rect>
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -26,7 +30,7 @@ pub enum SortedOrder {
Desc, Desc,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash, Eq)]
pub enum Header { pub enum Header {
State, State,
Status, Status,
@@ -62,12 +66,17 @@ impl AppData {
self.sorted_by.clone() self.sorted_by.clone()
} }
/// Change the sorted order, also set the selected pointer! /// Change the sorted order, also set the selected container state to match new order
pub fn set_sorted(&mut self, x: Option<(Header, SortedOrder)>) { pub fn set_sorted(&mut self, x: Option<(Header, SortedOrder)>) {
self.sorted_by = x; self.sorted_by = x;
let id = self.get_selected_container_id(); let id = self.get_selected_container_id();
self.sort_containers(); self.sort_containers();
self.containers.state.select(self.containers.items.iter().position(|i| Some(i.id.to_owned()) == id)); self.containers.state.select(
self.containers
.items
.iter()
.position(|i| Some(i.id.to_owned()) == id),
);
} }
/// Generate a default app_state /// Generate a default app_state
pub fn default(args: CliArgs) -> Self { pub fn default(args: CliArgs) -> Self {
@@ -78,10 +87,24 @@ impl AppData {
init: false, init: false,
logs_parsed: false, logs_parsed: false,
show_error: false, show_error: false,
sorted_by: Some((Header::Memory, SortedOrder::Asc)), sorted_by: None,
} }
} }
// fn heading_click(&self) {
// if let Some(data) = self
// .heading_map
// .iter()
// .filter(|i| i.1.intersects(rect))
// .collect::<Vec<_>>()
// .get(0)
// {
// // self.selected_panel = *data.0;
// }
// }
// Current time as unix timestamp // Current time as unix timestamp
fn get_systemtime(&self) -> u64 { fn get_systemtime(&self) -> u64 {
SystemTime::now() SystemTime::now()
+2 -1
View File
@@ -169,7 +169,8 @@ impl DockerData {
self.app_data.lock().update_containers(&output); self.app_data.lock().update_containers(&output);
// self.app_data.lock().sort_containers(SortedOrder::Asc, Header::State); let current_sort = self.app_data.lock().get_sorted();
self.app_data.lock().set_sorted(current_sort);
output output
.iter() .iter()
+28 -8
View File
@@ -117,11 +117,9 @@ impl InputHandler {
fn sort(&self, header: Header) { fn sort(&self, header: Header) {
let mut locked_data = self.app_data.lock(); let mut locked_data = self.app_data.lock();
if let Some((s, h)) = locked_data.get_sorted().as_ref() { if let Some((_, order)) = locked_data.get_sorted().as_ref() {
match (s, h) { match order {
(header, SortedOrder::Asc) => { SortedOrder::Asc => locked_data.set_sorted(Some((header, SortedOrder::Desc))),
locked_data.set_sorted(Some((header.to_owned(), SortedOrder::Desc)))
}
_ => locked_data.set_sorted(Some((header, SortedOrder::Asc))), _ => locked_data.set_sorted(Some((header, SortedOrder::Asc))),
} }
} else { } else {
@@ -154,13 +152,14 @@ impl InputHandler {
} }
} else { } else {
match key_code { match key_code {
KeyCode::Char('0') => self.app_data.lock().set_sorted(None),
KeyCode::Char('1') => self.sort(Header::State), KeyCode::Char('1') => self.sort(Header::State),
KeyCode::Char('2') => self.sort(Header::Status), KeyCode::Char('2') => self.sort(Header::Status),
KeyCode::Char('3') => self.sort(Header::Cpu), KeyCode::Char('3') => self.sort(Header::Cpu),
KeyCode::Char('4') => self.sort(Header::Memory), KeyCode::Char('4') => self.sort(Header::Memory),
KeyCode::Char('5') => self.sort(Header::Id), KeyCode::Char('5') => self.sort(Header::Id),
KeyCode::Char('6') => self.sort(Header::Image), KeyCode::Char('6') => self.sort(Header::Name),
KeyCode::Char('7') => self.sort(Header::Name), KeyCode::Char('7') => self.sort(Header::Image),
KeyCode::Char('8') => self.sort(Header::Rx), KeyCode::Char('8') => self.sort(Header::Rx),
KeyCode::Char('9') => self.sort(Header::Tx), KeyCode::Char('9') => self.sort(Header::Tx),
KeyCode::Char('q') => self.is_running.store(false, Ordering::SeqCst), KeyCode::Char('q') => self.is_running.store(false, Ordering::SeqCst),
@@ -247,7 +246,28 @@ impl InputHandler {
MouseEventKind::ScrollUp => self.previous(), MouseEventKind::ScrollUp => self.previous(),
MouseEventKind::ScrollDown => self.next(), MouseEventKind::ScrollDown => self.next(),
MouseEventKind::Down(MouseButton::Left) => { MouseEventKind::Down(MouseButton::Left) => {
self.gui_state.lock().rect_insersects(Rect::new( let header_int = self.gui_state.lock().header_intersect(Rect::new(
mouse_event.column,
mouse_event.row,
1,
1,
));
/// Don't like this
let order = if let Some((_, or)) = self.app_data.lock().get_sorted() {
match or {
SortedOrder::Asc => SortedOrder::Desc,
SortedOrder::Desc => SortedOrder::Asc,
}
} else {
SortedOrder::Asc
};
if let Some(header) = header_int {
self.app_data.lock().set_sorted(Some((header, order)))
}
self.gui_state.lock().panel_intersect(Rect::new(
mouse_event.column, mouse_event.column,
mouse_event.row, mouse_event.row,
1, 1,
+253 -62
View File
@@ -48,7 +48,7 @@ fn generate_block<'a>(
gui_state: &Arc<Mutex<GuiState>>, gui_state: &Arc<Mutex<GuiState>>,
panel: SelectablePanel, panel: SelectablePanel,
) -> Block<'a> { ) -> Block<'a> {
gui_state.lock().insert_into_area_map(panel, area); gui_state.lock().insert_into_panel_map(panel, area);
let mut block = Block::default() let mut block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded); .border_type(BorderType::Rounded);
@@ -124,6 +124,97 @@ pub fn draw_containers<B: Backend>(
widths: &Columns, widths: &Columns,
) { ) {
let block = generate_block(app_data, area, gui_state, SelectablePanel::Containers); let block = generate_block(app_data, area, gui_state, SelectablePanel::Containers);
let sorted = app_data.lock().get_sorted();
// if containers sorted, increase width to match headers
// let sorted_width = if app_data.lock().get_sorted().is_some() {
// 2
// }else {
// 0
// };
// Check if need to alter column width to watch the heading widths
let sorted_width = |(x,s): &(Header,usize)| {
let mut output = 0;
if let Some((h,_)) = &sorted {
if h == x {
output = 2;
}
}
// s + output
s.to_owned()
};
// let items = app_data
// .lock()
// .containers
// .items
// .iter()
// .map(|i| {
// let state_style = Style::default().fg(i.state.get_color());
// let blue = Style::default().fg(Color::Blue);
// let mems = format!(
// "{:>1} / {:>1}",
// i.mem_stats.back().unwrap_or(&ByteStats::new(0)),
// i.mem_limit
// );
// let lines = Spans::from(vec![
// Span::styled(
// format!("{:<width$}", i.state.to_string(), width = sorted_width(&widths.state)),
// state_style,
// ),
// Span::styled(
// format!("{}{:>width$}", MARGIN, i.status, width = sorted_width(&widths.status)),
// state_style,
// ),
// Span::styled(
// format!(
// "{}{:>width$}",
// MARGIN,
// i.cpu_stats.back().unwrap_or(&CpuStats::new(0.0)),
// width = sorted_width(&widths.cpu)
// ),
// state_style,
// ),
// Span::styled(
// format!("{}{:>width$}", MARGIN, mems, width = sorted_width(&widths.mem)),
// state_style,
// ),
// Span::styled(
// format!(
// "{}{:>width$}",
// MARGIN,
// i.id.chars().take(8).collect::<String>(),
// width =sorted_width(&widths.id),
// ),
// blue,
// ),
// Span::styled(
// format!("{}{:>width$}", MARGIN, i.name, width = sorted_width(&widths.name)),
// blue,
// ),
// Span::styled(
// format!("{}{:>width$}", MARGIN, i.image, width = sorted_width(&widths.image)),
// blue,
// ),
// Span::styled(
// format!("{}{:>width$}", MARGIN, i.net_rx, width = sorted_width(&widths.net_rx)),
// Style::default().fg(Color::Rgb(255, 233, 193)),
// ),
// Span::styled(
// format!("{}{:>width$}", MARGIN, i.net_tx, width = sorted_width(&widths.net_tx)),
// Style::default().fg(Color::Rgb(205, 140, 140)),
// ),
// ]);
// ListItem::new(lines)
// })
// .collect::<Vec<_>>();
let items = app_data let items = app_data
.lock() .lock()
.containers .containers
@@ -357,99 +448,149 @@ pub fn draw_heading_bar<B: Backend>(
f: &mut Frame<'_, B>, f: &mut Frame<'_, B>,
has_containers: bool, has_containers: bool,
loading_icon: String, loading_icon: String,
info_visible: bool,
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
gui_state: &Arc<Mutex<GuiState>>,
) { ) {
let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black)); let block = || Block::default().style(Style::default().bg(Color::Magenta).fg(Color::Black));
let info_visible = gui_state.lock().show_help;
f.render_widget(block(), area); f.render_widget(block(), area);
let aaa = |x: &Header| {
let mut output = ""; /// Generate a bloack for the header, if the header is currently being used to sort a column, then highlight it white
let header_block = |x: &Header| {
let mut color = Color::Black;
let mut s = "";
let mut c = 0;
if let Some((a, b)) = sorted_by.as_ref() { if let Some((a, b)) = sorted_by.as_ref() {
if x == a { if x == a {
output = match b { match b{
SortedOrder::Asc => "A", SortedOrder::Asc => s="",
SortedOrder::Desc => "B", SortedOrder::Desc => s="",
}
c = 2;
color = Color::White
}; };
}; };
};
output
};
// need to split this into blocks, and put each block in the split block, and set color to white if is selected (Block::default().style(Style::default().bg(Color::Magenta).fg(color)), s, c)
// then just put in the split in a horizontal fashion, with a width equal to widtrh, or char count? };
// let white = "\x1b[37m";
// let reset = "\x1b[0m";
// let mut column_headings = format!( // Create blocks for each header, count widths
// " {}{:>width$}{}", let state_block = header_block(&Header::State);
// loading_icon, let state_text = format!(
// columns.state.0, " {}{:>width$}{ic}",
// width = columns.state.1,
// );
// Each
let mut column_headings = format!(
" {}{:>width$}",
loading_icon, loading_icon,
columns.state.0, columns.state.0,
width = columns.state.1 ic=state_block.1,
width = columns.state.1 - state_block.2
); );
column_headings.push_str( let state_count = state_text.chars().count() as u16;
format!( let state = Paragraph::new(state_text)
"{} {:>width$}", .block(state_block.0)
.alignment(Alignment::Left);
let gen_header = |x: Header, w: usize| {
let status_block = header_block(&x);
let status_text = format!(
"{} {:>width$}{ic}",
MARGIN, MARGIN,
columns.status.0, x,
width = columns.status.1 ic=status_block.1,
) width = w - status_block.2
.as_str(),
); );
// Get selected and sorted let count = status_text.chars().count() as u16;
// Maybe each heading needs to be its own boock let status = Paragraph::new(status_text)
column_headings .block(status_block.0)
.push_str(format!("{}{:>width$}", MARGIN, columns.cpu.0, width = columns.cpu.1).as_str()); .alignment(Alignment::Left);
column_headings (status, count)
.push_str(format!("{}{:>width$}", MARGIN, columns.mem.0, width = columns.mem.1).as_str()); };
column_headings
.push_str(format!("{}{:>width$}", MARGIN, columns.id.0, width = columns.id.1).as_str()); // let (status, status_count) = gen_header(Header::Status,columns.status.1);
column_headings.push_str( // let (cpu, cpu_count) = gen_header(Header::Cpu,columns.cpu.1);
format!( // let (mem, mem_count) = gen_header(Header::Memory,columns.mem.1);
// let (id, id_count) = gen_header(Header::Id,columns.id.1);
// let (name, name_count) = gen_header(Header::Name,columns.name.1);
// let (image, image_count) = gen_header(Header::Image,columns.image.1);
// let (rx, rx_count) = gen_header(Header::Rx,columns.net_rx.1);
// let (tx, tx_count) = gen_header(Header::Tx,columns.net_tx.1);
let status_block = header_block(&Header::Status);
let status_text = format!(
"{} {:>width$}{ic}",
MARGIN,
columns.status.0,
ic=status_block.1,
width = columns.status.1 - status_block.2
);
let status_count = status_text.chars().count() as u16;
let status = Paragraph::new(status_text)
.block(status_block.0)
.alignment(Alignment::Left);
let cpu_text = format!("{}{:>width$}", MARGIN, columns.cpu.0, width = columns.cpu.1);
let cpu_count = cpu_text.chars().count() as u16;
let cpu = Paragraph::new(cpu_text)
.block(header_block(&Header::Cpu).0)
.alignment(Alignment::Left);
// TODO put block into the mouse click hashmap
let mem_text = format!("{}{:>width$}", MARGIN, columns.mem.0, width = columns.mem.1);
let mem_count = mem_text.chars().count() as u16;
let mem = Paragraph::new(mem_text)
.block(header_block(&Header::Memory).0)
.alignment(Alignment::Left);
let id_text = format!("{}{:>width$}", MARGIN, columns.id.0, width = columns.id.1);
let id_count = id_text.chars().count() as u16;
let id = Paragraph::new(id_text)
.block(header_block(&Header::Id).0)
.alignment(Alignment::Left);
let name_text = format!(
"{}{:>width$}", "{}{:>width$}",
MARGIN, MARGIN,
columns.name.0, columns.name.0,
width = columns.name.1 width = columns.name.1
)
.as_str(),
); );
column_headings.push_str( let name_count = name_text.chars().count() as u16;
format!( let name = Paragraph::new(name_text)
.block(header_block(&Header::Name).0)
.alignment(Alignment::Left);
let image_text = format!(
"{}{:>width$}", "{}{:>width$}",
MARGIN, MARGIN,
columns.image.0, columns.image.0,
width = columns.image.1 width = columns.image.1
)
.as_str(),
); );
column_headings.push_str( let image_count = image_text.chars().count() as u16;
format!( let image = Paragraph::new(image_text)
.block(header_block(&Header::Image).0)
.alignment(Alignment::Left);
let rx_text = format!(
"{}{:>width$}", "{}{:>width$}",
MARGIN, MARGIN,
columns.net_rx.0, columns.net_rx.0,
width = columns.net_rx.1 width = columns.net_rx.1
)
.as_str(),
); );
column_headings.push_str( let rx_count = rx_text.chars().count() as u16;
format!( let rx = Paragraph::new(rx_text)
.block(header_block(&Header::Rx).0)
.alignment(Alignment::Left);
let tx_text = format!(
"{}{:>width$}", "{}{:>width$}",
MARGIN, MARGIN,
columns.net_tx.0, columns.net_tx.0,
width = columns.net_tx.1 width = columns.net_tx.1
)
.as_str(),
); );
let tx_count = tx_text.chars().count() as u16;
let tx = Paragraph::new(tx_text)
.block(header_block(&Header::Tx).0)
.alignment(Alignment::Left);
let suffix = if info_visible { "exit" } else { "show" }; let suffix = if info_visible { "exit" } else { "show" };
let info_text = format!("( h ) {} help {}", suffix, MARGIN); let info_text = format!("( h ) {} help {}", suffix, MARGIN);
@@ -467,12 +608,61 @@ pub fn draw_heading_bar<B: Backend>(
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints(splits.as_ref()) .constraints(splits.as_ref())
.split(area); .split(area);
if has_containers { if has_containers {
let paragraph = Paragraph::new(column_headings) // let a = state_text.chars().count() as u16;
.block(block()) // let b = status_text.chars().count() as u16;
.alignment(Alignment::Left); let container_splits = vec![
f.render_widget(paragraph, split_bar[0]); Constraint::Max(state_count),
Constraint::Max(status_count),
Constraint::Max(cpu_count),
Constraint::Max(mem_count),
Constraint::Max(id_count),
Constraint::Max(name_count),
Constraint::Max(image_count),
Constraint::Max(rx_count),
Constraint::Max(tx_count),
// Constraint::Min(1)
];
// insert or update
let v = [
(Header::State,state),
(Header::Status,status),
(Header::Cpu,cpu),
(Header::Memory,mem),
(Header::Id,id),
(Header::Name,name),
(Header::Image,image),
(Header::Rx,rx),
(Header::Tx,tx)
];
let headers_section = Layout::default()
.direction(Direction::Horizontal)
.constraints(container_splits.as_ref())
.split(split_bar[0]);
for (index, (header, para)) in v.into_iter().enumerate() {
gui_state
.lock()
.insert_into_header_map(header, headers_section[index]);
f.render_widget(para, headers_section[index]);
}
// gui_state.lock()
// f.render_widget(tx, headers_section[8]);
// f.render_widget(rx, headers_section[7]);
// f.render_widget(image, headers_section[6]);
// f.render_widget(name, headers_section[5]);
// f.render_widget(id, headers_section[4]);
// f.render_widget(mem, headers_section[3]);
// f.render_widget(cpu, headers_section[2]);
// f.render_widget(status, headers_section[1]);
// f.render_widget(state, headers_section[0]);
} }
let paragraph = Paragraph::new(info_text) let paragraph = Paragraph::new(info_text)
@@ -506,7 +696,8 @@ pub fn draw_help_box<B: Backend>(f: &mut Frame<'_, B>) {
.push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line"); .push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line");
help_text.push_str("\n ( enter ) to send docker container commands"); help_text.push_str("\n ( enter ) to send docker container commands");
help_text.push_str("\n ( h ) to toggle this help information"); help_text.push_str("\n ( h ) to toggle this help information");
help_text.push_str("\n ( 1 - 9 ) order headers"); help_text.push_str("\n ( 0 ) stop sort");
help_text.push_str("\n ( 1 - 9 ) sort by matching header - or click header");
help_text.push_str( help_text.push_str(
"\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied", "\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied",
); );
+30 -7
View File
@@ -1,6 +1,8 @@
use std::{collections::HashMap, fmt}; use std::{collections::HashMap, fmt};
use tui::layout::{Constraint, Rect}; use tui::layout::{Constraint, Rect};
use crate::app_data::Header;
#[derive(Debug, PartialEq, std::hash::Hash, std::cmp::Eq, Clone, Copy)] #[derive(Debug, PartialEq, std::hash::Hash, std::cmp::Eq, Clone, Copy)]
pub enum SelectablePanel { pub enum SelectablePanel {
Containers, Containers,
@@ -165,7 +167,8 @@ pub struct GuiState {
// Think this should be a BMapTree, so can define order when iterating over potential intersects // Think this should be a BMapTree, so can define order when iterating over potential intersects
// Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel // Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel
// If a BMapTree think it would mean have to implement ordering for SelectablePanel // If a BMapTree think it would mean have to implement ordering for SelectablePanel
area_map: HashMap<SelectablePanel, Rect>, panel_map: HashMap<SelectablePanel, Rect>,
heading_map: HashMap<Header, Rect>,
loading_icon: Loading, loading_icon: Loading,
// Should be a vec, each time loading add a new to the vec, and reset remove from vec // Should be a vec, each time loading add a new to the vec, and reset remove from vec
// for for if is_loading just check if vec is empty or not // for for if is_loading just check if vec is empty or not
@@ -179,7 +182,8 @@ impl GuiState {
/// Generate a default gui_state /// Generate a default gui_state
pub fn default() -> Self { pub fn default() -> Self {
Self { Self {
area_map: HashMap::new(), panel_map: HashMap::new(),
heading_map: HashMap::new(),
loading_icon: Loading::One, loading_icon: Loading::One,
selected_panel: SelectablePanel::Containers, selected_panel: SelectablePanel::Containers,
show_help: false, show_help: false,
@@ -190,13 +194,13 @@ impl GuiState {
/// clear panels hash map, so on resize can fix the sizes for mouse clicks /// clear panels hash map, so on resize can fix the sizes for mouse clicks
pub fn clear_area_map(&mut self) { pub fn clear_area_map(&mut self) {
self.area_map.clear(); self.panel_map.clear();
} }
/// Check if a given Rect (a clicked area of 1x1), interacts with any known panels /// Check if a given Rect (a clicked area of 1x1), interacts with any known panels
pub fn rect_insersects(&mut self, rect: Rect) { pub fn panel_intersect(&mut self, rect: Rect) {
if let Some(data) = self if let Some(data) = self
.area_map .panel_map
.iter() .iter()
.filter(|i| i.1.intersects(rect)) .filter(|i| i.1.intersects(rect))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -206,9 +210,28 @@ impl GuiState {
} }
} }
/// Check if a given Rect (a clicked area of 1x1), interacts with any known panels
pub fn header_intersect(&mut self, rect: Rect) -> Option<Header> {
self.heading_map
.iter()
.filter(|i| i.1.intersects(rect))
.collect::<Vec<_>>()
.get(0)
.map(|data| data.0.to_owned())
}
/// Insert selectable gui panel into area map /// Insert selectable gui panel into area map
pub fn insert_into_area_map(&mut self, panel: SelectablePanel, area: Rect) { /// Remove each time, as terminal may have been resized!
self.area_map.entry(panel).or_insert(area); pub fn insert_into_panel_map(&mut self, panel: SelectablePanel, area: Rect) {
self.panel_map.remove(&panel);
self.panel_map.insert(panel, area);
}
/// Insert selectable gui panel into area map
/// Remove each time, as terminal may have been resized!
pub fn insert_into_header_map(&mut self, header: Header, area: Rect) {
self.heading_map.remove(&header);
self.heading_map.insert(header, area);
} }
/// Change to next selectable panel /// Change to next selectable panel
+4 -2
View File
@@ -154,10 +154,11 @@ fn ui<B: Backend>(
let has_containers = !app_data.lock().containers.items.is_empty(); let has_containers = !app_data.lock().containers.items.is_empty();
let has_error = app_data.lock().get_error(); let has_error = app_data.lock().get_error();
let log_index = app_data.lock().get_selected_log_index(); let log_index = app_data.lock().get_selected_log_index();
let sorted_by = app_data.lock().get_sorted();
let show_help = gui_state.lock().show_help; let show_help = gui_state.lock().show_help;
let info_text = gui_state.lock().info_box_text.clone(); let info_text = gui_state.lock().info_box_text.clone();
let loading_icon = gui_state.lock().get_loading(); let loading_icon = gui_state.lock().get_loading();
let sorted_by = app_data.lock().get_sorted();
let whole_layout = Layout::default() let whole_layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@@ -214,8 +215,9 @@ fn ui<B: Backend>(
f, f,
has_containers, has_containers,
loading_icon, loading_icon,
show_help,
sorted_by, sorted_by,
gui_state,
); );
// only draw charts if there are containers // only draw charts if there are containers