diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index aa08972..f355f2b 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -478,10 +478,11 @@ impl AppData { } /// Get Option of the current selected container's ports, sorted by private port - pub fn get_selected_ports(&mut self) -> Option<(&[ContainerPorts], State)> { - if let Some(item) = self.get_mut_selected_container() { - item.ports.sort_by(|a, b| a.private.cmp(&b.private)); - return Some((&item.ports, item.state)); + pub fn get_selected_ports(&self) -> Option<(Vec, State)> { + if let Some(item) = self.get_selected_container() { + let mut ports = item.ports.clone(); + ports.sort_by(|a, b| a.private.cmp(&b.private)); + return Some((ports, item.state)); } None } @@ -646,12 +647,12 @@ impl AppData { /// Chart data related methods /// Get mutable Option of the currently selected container chart data - pub fn get_chart_data(&mut self) -> Option<(CpuTuple, MemTuple)> { + pub fn get_chart_data(&self) -> Option<(CpuTuple, MemTuple)> { self.containers .state .selected() - .and_then(|i| self.containers.items.get_mut(i)) - .map(|i| i.get_chart_data()) + .and_then(|i| self.containers.items.get(i)) + .map(container_state::ContainerItem::get_chart_data) } /// Error related methods @@ -2180,8 +2181,7 @@ mod tests { private: 8001, public: None } - ] - .as_slice(), + ], State::Running(RunningState::Healthy), )) ); @@ -2193,7 +2193,7 @@ mod tests { assert_eq!( result, - Some((vec![].as_slice(), State::Running(RunningState::Healthy))) + Some((vec![], State::Running(RunningState::Healthy))) ); } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 6799aaa..4e05ad4 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -64,19 +64,17 @@ impl InputHandler { match message { InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await, InputMessages::MouseEvent(mouse_event) => { - if !self.gui_state.lock().status_contains(&[ - Status::Error, - Status::Help, - Status::DeleteConfirm, - Status::Filter, - ]) { + let status = self.gui_state.lock().get_status(); + let contains = |s: Status| status.contains(&s); + + if !contains(Status::Error) + | !contains(Status::Help) + | !contains(Status::DeleteConfirm) + | !contains(Status::Filter) + { self.mouse_press(mouse_event); } - let delete_confirm = self - .gui_state - .lock() - .status_contains(&[Status::DeleteConfirm]); - if delete_confirm { + if contains(Status::DeleteConfirm) { self.button_intersect(mouse_event).await; } } @@ -92,11 +90,9 @@ impl InputHandler { /// Send a quit message to docker, to abort all spawns, if an error is returned, set is_running to false here instead /// If gui_status is Error or Init, then just set the is_running to false immediately, for a quicker exit fn quit(&self) { - let error_init = self - .gui_state - .lock() - .status_contains(&[Status::Error, Status::Init]); - if !error_init { + let status = self.gui_state.lock().get_status(); + let contains = |s: Status| status.contains(&s); + if !contains(Status::Error) | !contains(Status::Init) { self.is_running .store(false, std::sync::atomic::Ordering::SeqCst); } @@ -235,10 +231,11 @@ impl InputHandler { /// Attempt to save the currently selected container logs to a file async fn s_key(&self) { - let log_status = Status::Logs; - let status = self.gui_state.lock().status_contains(&[log_status]); - if !status { - self.gui_state.lock().status_push(log_status); + let status = self.gui_state.lock().get_status(); + let contains = |s: Status| status.contains(&s); + + if !contains(Status::Logs) { + self.gui_state.lock().status_push(Status::Logs); let uuid = Uuid::new_v4(); GuiState::start_loading_animation(&self.gui_state, uuid); if self.save_logs().await.is_err() { @@ -248,7 +245,7 @@ impl InputHandler { Status::Error, ); } - self.gui_state.lock().status_del(log_status); + self.gui_state.lock().status_del(Status::Logs); self.gui_state.lock().stop_loading_animation(uuid); } } @@ -426,17 +423,14 @@ impl InputHandler { } /// Handle keyboard button events async fn button_press(&mut self, key_code: KeyCode, key_modifier: KeyModifiers) { - let contains_delete = self - .gui_state - .lock() - .status_contains(&[Status::DeleteConfirm]); - - let contains = |s: Status| self.gui_state.lock().status_contains(&[s]); + let status = self.gui_state.lock().get_status(); + let contains = |s: Status| status.contains(&s); let contains_error = contains(Status::Error); let contains_help = contains(Status::Help); let contains_exec = contains(Status::Exec); - let contains_filter: bool = contains(Status::Filter); + let contains_filter = contains(Status::Filter); + let contains_delete = contains(Status::DeleteConfirm); if !contains_exec { let is_c = || key_code == KeyCode::Char('c') || key_code == KeyCode::Char('C'); diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index cebfb02..08f4752 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -96,7 +96,7 @@ fn generate_block<'a>( .borders(Borders::ALL) .border_type(BorderType::Rounded) .title(title); - if fd.selected_panel == panel && !gui_state.lock().status_contains(&[Status::Filter]) { + if fd.selected_panel == panel && !fd.status.contains(&Status::Filter) { block = block.border_style(Style::default().fg(Color::LightCyan)); } block @@ -260,7 +260,7 @@ pub fn logs( gui_state: &Arc>, ) { let block = generate_block(area, fd, gui_state, SelectablePanel::Logs); - if fd.init { + if fd.status.contains(&Status::Init) { let paragraph = Paragraph::new(format!("parsing logs {}", fd.loading_icon)) .style(Style::default()) .block(block) @@ -274,7 +274,7 @@ pub fn logs( .alignment(Alignment::Center); f.render_widget(paragraph, area); } else { - let items = List::new(app_data.lock().get_logs()) + let items = List::new(logs) .block(block) .highlight_symbol(RIGHT_ARROW) .highlight_style(Style::default().add_modifier(Modifier::BOLD)); @@ -287,15 +287,8 @@ pub fn logs( } // Display the ports in a formatted list -pub fn ports( - f: &mut Frame, - area: Rect, - app_data: &Arc>, - max_lens: (usize, usize, usize), -) { - let mut data = app_data.lock(); - let ports = data.get_selected_ports(); - if let Some(ports) = ports { +pub fn ports(f: &mut Frame, area: Rect, fd: &FrameData) { + if let Some(ports) = fd.ports.as_ref() { let block = Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) @@ -307,7 +300,7 @@ pub fn ports( .add_modifier(Modifier::BOLD), )); - let (ip, private, public) = max_lens; + let (ip, private, public) = fd.port_max_lens; if ports.0.is_empty() { let text = match ports.1 { @@ -318,7 +311,6 @@ pub fn ports( .alignment(Alignment::Center) .block(block); f.render_widget(paragraph, area); - drop(data); } else { let mut output = vec![Line::from( Span::from(format!( @@ -327,7 +319,7 @@ pub fn ports( )) .fg(Color::Yellow), )]; - for item in ports.0 { + for item in &ports.0 { let fg = Color::White; let strings = item.get_all(); @@ -345,9 +337,8 @@ pub fn ports( } /// Draw the cpu + mem charts -pub fn chart(f: &mut Frame, area: Rect, app_data: &Arc>) { - let cpu_mem = app_data.lock().get_chart_data(); - if let Some((cpu, mem)) = cpu_mem { +pub fn chart(f: &mut Frame, area: Rect, fd: &FrameData) { + if let Some((cpu, mem)) = fd.chart_data.as_ref() { let area = Layout::default() .direction(Direction::Horizontal) .constraints(CONSTRAINT_50_50) @@ -489,10 +480,11 @@ pub fn filter_bar(area: Rect, frame: &mut Frame, fd: &FrameData) { /// 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 heading_bar( area: Rect, frame: &mut Frame, - data: &FrameData, + fd: &FrameData, gui_state: &Arc>, ) { let block = |fg: Color| Block::default().style(Style::default().bg(Color::Magenta).fg(fg)); @@ -503,7 +495,7 @@ pub fn heading_bar( let header_block = |x: &Header| { let mut color = Color::Black; let mut suffix = ""; - if let Some((a, b)) = &data.sorted_by { + if let Some((a, b)) = &fd.sorted_by { if x == a { match b { SortedOrder::Asc => suffix = " ▲", @@ -536,26 +528,30 @@ pub fn heading_bar( // Meta data to iterate over to create blocks with correct widths let header_meta = [ - (Header::Name, data.columns.name.1), - (Header::State, data.columns.state.1), - (Header::Status, data.columns.status.1), - (Header::Cpu, data.columns.cpu.1), - (Header::Memory, data.columns.mem.1 + data.columns.mem.2 + 3), - (Header::Id, data.columns.id.1), - (Header::Image, data.columns.image.1), - (Header::Rx, data.columns.net_rx.1), - (Header::Tx, data.columns.net_tx.1), + (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), ]; // Need to add widths to this - let suffix = if data.help_visible { "exit" } else { "show" }; + let suffix = if fd.status.contains(&Status::Help) { + "exit" + } else { + "show" + }; let info_text = format!("( h ) {suffix} help{MARGIN}",); 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 data.has_containers { + let splits = if fd.has_containers { vec![ Constraint::Max(4), Constraint::Max(column_width.try_into().unwrap_or_default()), @@ -571,11 +567,11 @@ pub fn heading_bar( .split(area); // Draw loading icon, or not, and a prefix with a single space - let loading_paragraph = Paragraph::new(format!("{:>2}", data.loading_icon)) + let loading_paragraph = Paragraph::new(format!("{:>2}", fd.loading_icon)) .block(block(Color::White)) .alignment(Alignment::Left); frame.render_widget(loading_paragraph, split_bar[0]); - if data.has_containers { + if fd.has_containers { let header_section_width = split_bar[1].width; let mut counter = 0; @@ -610,7 +606,7 @@ pub fn heading_bar( } // show/hide help - let color = if data.help_visible { + let color = if fd.status.contains(&Status::Help) { Color::Black } else { Color::White @@ -620,7 +616,7 @@ pub fn heading_bar( .alignment(Alignment::Right); // If no containers, don't display the headers, could maybe do this first? - let help_index = if data.has_containers { 2 } else { 0 }; + let help_index = if fd.has_containers { 2 } else { 0 }; frame.render_widget(help_paragraph, split_bar[help_index]); } @@ -1004,7 +1000,7 @@ pub fn error(f: &mut Frame, error: AppError, seconds: Option) { /// Draw info box in one of the 9 BoxLocations // TODO is this broken - I don't think so -pub fn info(f: &mut Frame, text: String, instant: Instant, gui_state: &Arc>) { +pub fn info(f: &mut Frame, text: String, instant: &Instant, gui_state: &Arc>) { let block = Block::default() .title("") .title_alignment(Alignment::Center) @@ -1084,7 +1080,7 @@ mod tests { }, app_error::AppError, tests::{gen_appdata, gen_container_summary, gen_containers}, - ui::{draw_frame, GuiState}, + ui::{draw_frame, GuiState, Status}, }; use super::{FrameData, ORANGE, VERSION}; @@ -1100,6 +1096,42 @@ mod tests { const BORDER_CHARS: [&str; 6] = ["╭", "╮", "─", "│", "╰", "╯"]; + impl From<(&Arc>, &Arc>)> for FrameData { + fn from(data: (&Arc>, &Arc>)) -> Self { + let (app_data, gui_data) = (data.0.lock(), data.1.lock()); + + // set max height for container section, needs +5 to deal with docker commands list and borders + let height = app_data.get_container_len(); + let height = if height < 12 { + u16::try_from(height + 5).unwrap_or_default() + } else { + 12 + }; + + let (filter_by, filter_term) = app_data.get_filter(); + Self { + chart_data: app_data.get_chart_data(), + columns: app_data.get_width(), + container_title: app_data.get_container_title(), + delete_confirm: gui_data.get_delete_container(), + filter_by, + filter_term: filter_term.cloned(), + has_containers: app_data.get_container_len() > 0, + has_error: app_data.get_error(), + height, + ports: app_data.get_selected_ports(), + port_max_lens: app_data.get_longest_port(), + info_text: gui_data.info_box_text.clone(), + is_loading: gui_data.is_loading(), + loading_icon: gui_data.get_loading().to_string(), + log_title: app_data.get_log_title(), + selected_panel: gui_data.get_selected_panel(), + sorted_by: app_data.get_sorted(), + status: gui_data.get_status(), + } + } + } + /// Generate state to be used in *most* gui tests fn test_setup(w: u16, h: u16, control_start: bool, container_start: bool) -> TuiTestSetup { let backend = TestBackend::new(w, h); @@ -1118,7 +1150,6 @@ mod tests { let app_data = Arc::new(Mutex::new(app_data)); let gui_state = Arc::new(Mutex::new(gui_state)); - let fd = FrameData::from((&app_data, &gui_state)); let area = Rect::new(0, 0, w, h); TuiTestSetup { @@ -1944,7 +1975,7 @@ mod tests { ]; let mut fd = FrameData::from((&setup.app_data, &setup.gui_state)); - fd.init = true; + fd.status.insert(Status::Init); setup .terminal @@ -1974,7 +2005,7 @@ mod tests { ]; let mut fd = FrameData::from((&setup.app_data, &setup.gui_state)); - fd.init = true; + fd.status.insert(Status::Init); setup .terminal .draw(|f| { @@ -2188,10 +2219,11 @@ mod tests { let (w, h) = (80, 10); let mut setup = test_setup(w, h, true, true); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::chart(f, setup.area, &setup.app_data); + super::chart(f, setup.area, &fd); }) .unwrap(); @@ -2238,11 +2270,12 @@ mod tests { let mut setup = test_setup(w, h, true, true); insert_chart_data(&setup); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::chart(f, setup.area, &setup.app_data); + super::chart(f, setup.area, &fd); }) .unwrap(); @@ -2285,11 +2318,12 @@ mod tests { insert_chart_data(&setup); setup.app_data.lock().containers.items[0].state = State::Paused; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::chart(f, setup.area, &setup.app_data); + super::chart(f, setup.area, &fd); }) .unwrap(); @@ -2327,11 +2361,12 @@ mod tests { let mut setup = test_setup(w, h, true, true); insert_chart_data(&setup); setup.app_data.lock().containers.items[0].state = State::Dead; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::chart(f, setup.area, &setup.app_data); + super::chart(f, setup.area, &fd); }) .unwrap(); @@ -2393,7 +2428,7 @@ mod tests { } } - fd.help_visible = true; + fd.status.insert(Status::Help); let expected = [" ( h ) exit help "]; setup .terminal @@ -2807,7 +2842,7 @@ mod tests { super::info( f, "test".to_owned(), - std::time::Instant::now(), + &std::time::Instant::now(), &setup.gui_state, ); }) @@ -3079,11 +3114,11 @@ mod tests { let mut setup = test_setup(w, h, true, true); setup.app_data.lock().containers.items[0].ports = vec![]; - let max_lens = setup.app_data.lock().get_longest_port(); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::ports(f, setup.area, &setup.app_data, max_lens); + super::ports(f, setup.area, &fd); }) .unwrap(); @@ -3124,11 +3159,12 @@ mod tests { // When state is "State::Running | State::Paused | State::Restarting, won't show "no ports" setup.app_data.lock().containers.items[0].state = State::Dead; - let max_lens = setup.app_data.lock().get_longest_port(); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::ports(f, setup.area, &setup.app_data, max_lens); + super::ports(f, setup.area, &fd); }) .unwrap(); @@ -3179,12 +3215,11 @@ mod tests { public: Some(8003), }); - let max_lens = setup.app_data.lock().get_longest_port(); - + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::ports(f, setup.area, &setup.app_data, max_lens); + super::ports(f, setup.area, &fd); }) .unwrap(); @@ -3233,12 +3268,12 @@ mod tests { fn test_draw_blocks_ports_container_state() { let (w, h) = (32, 8); let mut setup = test_setup(w, h, true, true); - let max_lens = setup.app_data.lock().get_longest_port(); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::ports(f, setup.area, &setup.app_data, max_lens); + super::ports(f, setup.area, &fd); }) .unwrap(); @@ -3266,10 +3301,11 @@ mod tests { } setup.app_data.lock().containers.items[0].state = State::Paused; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::ports(f, setup.area, &setup.app_data, max_lens); + super::ports(f, setup.area, &fd); }) .unwrap(); @@ -3286,10 +3322,11 @@ mod tests { } setup.app_data.lock().containers.items[0].state = State::Exited; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - super::ports(f, setup.area, &setup.app_data, max_lens); + super::ports(f, setup.area, &fd); }) .unwrap(); @@ -3357,10 +3394,12 @@ mod tests { "│ │ ││ │ ││ │", "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯", ]; + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - draw_frame(f, &setup.app_data, &setup.gui_state); + draw_frame(f, &setup.app_data, &setup.gui_state, &fd); }) .unwrap(); @@ -3421,10 +3460,11 @@ mod tests { "│ │ ││ │ ││ │", "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯", ]; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - draw_frame(f, &setup.app_data, &setup.gui_state); + draw_frame(f, &setup.app_data, &setup.gui_state, &fd); }) .unwrap(); @@ -3475,10 +3515,11 @@ mod tests { "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯", " Esc clear ← by → Name Image Status All term: r_1 ", ]; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - draw_frame(f, &setup.app_data, &setup.gui_state); + draw_frame(f, &setup.app_data, &setup.gui_state, &fd); }) .unwrap(); @@ -3543,10 +3584,11 @@ mod tests { "│ │ ││ │ ││ │", "╰──────────────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────────╯╰────────────────────────────╯", ]; + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup .terminal .draw(|f| { - draw_frame(f, &setup.app_data, &setup.gui_state); + draw_frame(f, &setup.app_data, &setup.gui_state, &fd); }) .unwrap(); diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index ce007e8..b7b1850 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -266,10 +266,9 @@ impl GuiState { self.delete_container = id; } - /// Check if the current gui_status contains any of the given status' - /// Don't really like this methodology for gui state, needs a re-think - pub fn status_contains(&self, status: &[Status]) -> bool { - status.iter().any(|i| self.status.contains(i)) + /// Return a copy of the Status HashSet + pub fn get_status(&self) -> HashSet { + self.status.clone() } /// Remove a gui_status into the current gui_status HashSet diff --git a/src/ui/mod.rs b/src/ui/mod.rs index de51655..4d22d86 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -11,6 +11,7 @@ use ratatui::{ Frame, Terminal, }; use std::{ + collections::HashSet, io::{self, Stdout, Write}, sync::{atomic::Ordering, Arc}, time::Duration, @@ -26,7 +27,10 @@ mod gui_state; pub use self::color_match::*; pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; use crate::{ - app_data::{AppData, Columns, ContainerId, FilterBy, Header, SortedOrder}, + app_data::{ + AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, MemTuple, + SortedOrder, State, + }, app_error::AppError, exec::TerminalSize, input_handler::InputMessages, @@ -163,14 +167,15 @@ impl Ui { /// The loop for drawing the main UI to the terminal async fn gui_loop(&mut self) -> Result<(), AppError> { while self.is_running.load(Ordering::SeqCst) { - let exec = self.gui_state.lock().status_contains(&[Status::Exec]); + let fd = FrameData::from(&*self); + let exec = fd.status.contains(&Status::Exec); if exec { self.exec().await; } if self .terminal - .draw(|frame| draw_frame(frame, &self.app_data, &self.gui_state)) + .draw(|frame| draw_frame(frame, &self.app_data, &self.gui_state, &fd)) .is_err() { return Err(AppError::Terminal); @@ -206,11 +211,8 @@ impl Ui { /// Draw either the Error, or main oxker ui, to the terminal async fn draw_ui(&mut self) -> Result<(), AppError> { - let status_dockerconnect = self - .gui_state - .lock() - .status_contains(&[Status::DockerConnect]); - if status_dockerconnect { + let status = self.gui_state.lock().get_status(); + if status.contains(&Status::DockerConnect) { self.err_loop()?; } else { self.gui_loop().await?; @@ -221,8 +223,8 @@ impl Ui { /// Frequent data required by multiple framde drawing functions, can reduce mutex reads by placing it all in here #[derive(Debug, Clone)] -#[allow(clippy::struct_excessive_bools)] pub struct FrameData { + chart_data: Option<(CpuTuple, MemTuple)>, columns: Columns, container_title: String, delete_confirm: Option, @@ -231,19 +233,20 @@ pub struct FrameData { has_containers: bool, has_error: Option, height: u16, - help_visible: bool, info_text: Option<(String, Instant)>, - init: bool, is_loading: bool, loading_icon: String, log_title: String, + port_max_lens: (usize, usize, usize), + ports: Option<(Vec, State)>, selected_panel: SelectablePanel, sorted_by: Option<(Header, SortedOrder)>, + status: HashSet, } -impl From<(&Arc>, &Arc>)> for FrameData { - fn from(data: (&Arc>, &Arc>)) -> Self { - let (app_data, gui_data) = (data.0.lock(), data.1.lock()); +impl From<&Ui> for FrameData { + fn from(ui: &Ui) -> Self { + let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); // set max height for container section, needs +5 to deal with docker commands list and borders let height = app_data.get_container_len(); @@ -255,6 +258,7 @@ impl From<(&Arc>, &Arc>)> for FrameData { let (filter_by, filter_term) = app_data.get_filter(); Self { + chart_data: app_data.get_chart_data(), columns: app_data.get_width(), container_title: app_data.get_container_title(), delete_confirm: gui_data.get_delete_container(), @@ -263,24 +267,27 @@ impl From<(&Arc>, &Arc>)> for FrameData { has_containers: app_data.get_container_len() > 0, has_error: app_data.get_error(), height, - help_visible: gui_data.status_contains(&[Status::Help]), info_text: gui_data.info_box_text.clone(), - init: gui_data.status_contains(&[Status::Init]), is_loading: gui_data.is_loading(), loading_icon: gui_data.get_loading().to_string(), log_title: app_data.get_log_title(), + port_max_lens: app_data.get_longest_port(), + ports: app_data.get_selected_ports(), selected_panel: gui_data.get_selected_panel(), sorted_by: app_data.get_sorted(), + status: gui_data.get_status(), } } } /// Draw the main ui to a frame of the terminal -fn draw_frame(f: &mut Frame, app_data: &Arc>, gui_state: &Arc>) { - let fd = FrameData::from((app_data, gui_state)); - let contains_filter = gui_state.lock().status_contains(&[Status::Filter]); - - let whole_constraints = if contains_filter { +fn draw_frame( + f: &mut Frame, + app_data: &Arc>, + gui_state: &Arc>, + fd: &FrameData, +) { + let whole_constraints = if fd.status.contains(&Status::Filter) { vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)] } else { vec![Constraint::Max(1), Constraint::Min(1)] @@ -320,15 +327,15 @@ fn draw_frame(f: &mut Frame, app_data: &Arc>, gui_state: &Arc>, gui_state: &Arc