refactor: Multiple UI improvements;
Use FrameData struct to store commonly accessed data, in order to reduce mutex locks. rename unpause to resume use get_selected_panel() function instead of directly gui_state.selected_panel debug mode now shows some usefull information
This commit is contained in:
+95
-74
@@ -4,7 +4,7 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
@@ -26,16 +26,16 @@ mod gui_state;
|
||||
pub use self::color_match::*;
|
||||
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
||||
use crate::{
|
||||
app_data::AppData, app_error::AppError, docker_data::DockerMessage,
|
||||
input_handler::InputMessages, parse_args::CliArgs,
|
||||
app_data::{AppData, Columns, ContainerId, Header, SortedOrder},
|
||||
app_error::AppError,
|
||||
input_handler::InputMessages,
|
||||
};
|
||||
|
||||
pub const DOCKER_COMMAND: &str = "docker";
|
||||
|
||||
pub struct Ui {
|
||||
args: CliArgs,
|
||||
// args: CliArgs,
|
||||
app_data: Arc<Mutex<AppData>>,
|
||||
docker_sx: Sender<DockerMessage>,
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
input_poll_rate: Duration,
|
||||
is_running: Arc<AtomicBool>,
|
||||
@@ -60,17 +60,14 @@ impl Ui {
|
||||
/// Create a new Ui struct, and execute the drawing loop
|
||||
pub async fn create(
|
||||
app_data: Arc<Mutex<AppData>>,
|
||||
docker_sx: Sender<DockerMessage>,
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
sender: Sender<InputMessages>,
|
||||
) {
|
||||
if let Ok(terminal) = Self::setup_terminal() {
|
||||
let args = app_data.lock().args.clone();
|
||||
// let args = app_data.lock().args.clone();
|
||||
let mut ui = Self {
|
||||
args,
|
||||
app_data,
|
||||
docker_sx,
|
||||
gui_state,
|
||||
input_poll_rate: std::time::Duration::from_millis(100),
|
||||
is_running,
|
||||
@@ -158,10 +155,8 @@ impl Ui {
|
||||
if child.kill().is_err() {
|
||||
std::process::exit(1)
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
self.terminal.clear().ok();
|
||||
self.reset_terminal().ok();
|
||||
Self::init_terminal().ok();
|
||||
@@ -170,16 +165,10 @@ impl Ui {
|
||||
|
||||
/// The loop for drawing the main UI to the terminal
|
||||
async fn gui_loop(&mut self) -> Result<(), AppError> {
|
||||
let update_duration =
|
||||
std::time::Duration::from_millis(u64::from(self.args.docker_interval));
|
||||
|
||||
while self.is_running.load(Ordering::SeqCst) {
|
||||
let exec = self.gui_state.lock().status_contains(&[Status::Exec]);
|
||||
|
||||
if exec {
|
||||
self.exec();
|
||||
self.docker_sx.send(DockerMessage::Update).await.ok();
|
||||
continue;
|
||||
}
|
||||
|
||||
if self
|
||||
@@ -212,12 +201,6 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should this be done in the docker thread instead?
|
||||
if self.now.elapsed() >= update_duration {
|
||||
self.docker_sx.send(DockerMessage::Update).await.ok();
|
||||
self.now = Instant::now();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -237,48 +220,94 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
/// This macro simplifies the definition and evaluation of variables by capturing and immediately evaluating an expression.
|
||||
macro_rules! value_capture {
|
||||
($name:ident, $lock_expr:expr) => {
|
||||
let $name = || $lock_expr;
|
||||
let $name = $name();
|
||||
};
|
||||
// #[macro_export]
|
||||
// /// This macro simplifies the definition and evaluation of variables by capturing and immediately evaluating an expression.
|
||||
// macro_rules! value_capture {
|
||||
// ($name:ident, $lock_expr:expr) => {
|
||||
// let $name = || $lock_expr;
|
||||
// let $name = $name();
|
||||
// };
|
||||
// }
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn get_wholelayout(f: &Frame) -> std::rc::Rc<[ratatui::layout::Rect]> {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||
.split(f.size())
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn get_wholelayout(f: &Frame) -> std::rc::Rc<[ratatui::layout::Rect]> {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(1), Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||
.split(f.size())
|
||||
}
|
||||
|
||||
/// Frequent data required by multiple framde drawing functions, can reduce mutex reads by placing it all in here
|
||||
#[derive(Debug)]
|
||||
pub struct FrameData {
|
||||
columns: Columns,
|
||||
delete_confirm: Option<ContainerId>,
|
||||
has_containers: bool,
|
||||
has_error: Option<AppError>,
|
||||
height: u16,
|
||||
help_visible: bool,
|
||||
init: bool,
|
||||
info_text: Option<String>,
|
||||
loading_icon: String,
|
||||
selected_panel: SelectablePanel,
|
||||
sorted_by: Option<(Header, SortedOrder)>,
|
||||
}
|
||||
|
||||
impl From<(MutexGuard<'_, AppData>, MutexGuard<'_, GuiState>)> for FrameData {
|
||||
fn from(data: (MutexGuard<'_, AppData>, MutexGuard<'_, GuiState>)) -> Self {
|
||||
// set max height for container section, needs +5 to deal with docker commands list and borders
|
||||
let height = data.0.get_container_len();
|
||||
let height = if height < 12 {
|
||||
u16::try_from(height + 5).unwrap_or_default()
|
||||
} else {
|
||||
12
|
||||
};
|
||||
|
||||
Self {
|
||||
columns: data.0.get_width(),
|
||||
delete_confirm: data.1.get_delete_container(),
|
||||
has_containers: data.0.get_container_len() > 1,
|
||||
has_error: data.0.get_error(),
|
||||
height,
|
||||
help_visible: data.1.status_contains(&[Status::Help]),
|
||||
init: data.1.status_contains(&[Status::Init]),
|
||||
info_text: data.1.info_box_text.clone(),
|
||||
loading_icon: data.1.get_loading().to_string(),
|
||||
selected_panel: data.1.get_selected_panel(),
|
||||
sorted_by: data.0.get_sorted(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the main ui to a frame of the terminal
|
||||
/// TODO add a single line area for debug message - if not in release mode?
|
||||
fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mutex<GuiState>>) {
|
||||
value_capture!(height, app_data.lock().get_container_len());
|
||||
value_capture!(column_widths, app_data.lock().get_width());
|
||||
value_capture!(has_containers, app_data.lock().get_container_len() > 0);
|
||||
value_capture!(sorted_by, app_data.lock().get_sorted());
|
||||
value_capture!(delete_confirm, gui_state.lock().get_delete_container());
|
||||
value_capture!(has_error, app_data.lock().get_error());
|
||||
value_capture!(info_text, gui_state.lock().info_box_text.clone());
|
||||
value_capture!(loading_icon, gui_state.lock().get_loading().to_string());
|
||||
let fd = FrameData::from((app_data.lock(), gui_state.lock()));
|
||||
|
||||
// set max height for container section, needs +5 to deal with docker commands list and borders
|
||||
let height = if height < 12 { height + 5 } else { 12 };
|
||||
let whole_layout = get_wholelayout(f);
|
||||
#[cfg(debug_assertions)]
|
||||
draw_blocks::debug_bar(whole_layout[0], f, app_data.lock().get_debug_string());
|
||||
|
||||
let whole_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(1), Constraint::Min(100)].as_ref())
|
||||
.split(f.size());
|
||||
#[cfg(debug_assertions)]
|
||||
let whole_layout_split = (1, 2);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let whole_layout_split = (0, 1);
|
||||
|
||||
// Split into 3, containers+controls, logs, then graphs
|
||||
let upper_main = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Max(height.try_into().unwrap_or_default()),
|
||||
Constraint::Percentage(50),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(whole_layout[1]);
|
||||
.constraints([Constraint::Max(fd.height), Constraint::Percentage(50)].as_ref())
|
||||
.split(whole_layout[whole_layout_split.1]);
|
||||
|
||||
let top_split = if has_containers {
|
||||
let top_split = if fd.has_containers {
|
||||
vec![Constraint::Percentage(90), Constraint::Percentage(10)]
|
||||
} else {
|
||||
vec![Constraint::Percentage(100)]
|
||||
@@ -289,7 +318,7 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
|
||||
.constraints(top_split)
|
||||
.split(upper_main[0]);
|
||||
|
||||
let lower_split = if has_containers {
|
||||
let lower_split = if fd.has_containers {
|
||||
vec![Constraint::Percentage(75), Constraint::Percentage(25)]
|
||||
} else {
|
||||
vec![Constraint::Percentage(100)]
|
||||
@@ -301,22 +330,14 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
|
||||
.constraints(lower_split)
|
||||
.split(upper_main[1]);
|
||||
|
||||
draw_blocks::containers(app_data, top_panel[0], f, gui_state, &column_widths);
|
||||
draw_blocks::containers(app_data, top_panel[0], f, &fd, gui_state, &fd.columns);
|
||||
|
||||
draw_blocks::logs(app_data, lower_main[0], f, gui_state, &loading_icon);
|
||||
draw_blocks::logs(app_data, lower_main[0], f, &fd, gui_state);
|
||||
|
||||
draw_blocks::heading_bar(
|
||||
whole_layout[0],
|
||||
&column_widths,
|
||||
f,
|
||||
has_containers,
|
||||
&loading_icon,
|
||||
sorted_by,
|
||||
gui_state,
|
||||
);
|
||||
draw_blocks::heading_bar(whole_layout[whole_layout_split.0], f, &fd, gui_state);
|
||||
|
||||
if let Some(id) = delete_confirm {
|
||||
app_data.lock().get_container_name_by_id(&id).map_or_else(
|
||||
if let Some(id) = fd.delete_confirm.as_ref() {
|
||||
app_data.lock().get_container_name_by_id(id).map_or_else(
|
||||
|| {
|
||||
// If a container is deleted outside of oxker but whilst the Delete Confirm dialog is open, it can get caught in kind of a dead lock situation
|
||||
// so if in that unique situation, just clear the delete_container id
|
||||
@@ -329,21 +350,21 @@ fn draw_frame(f: &mut Frame, app_data: &Arc<Mutex<AppData>>, gui_state: &Arc<Mut
|
||||
}
|
||||
|
||||
// only draw commands + charts if there are containers
|
||||
if has_containers {
|
||||
draw_blocks::commands(app_data, top_panel[1], f, gui_state);
|
||||
if fd.has_containers {
|
||||
draw_blocks::commands(app_data, top_panel[1], f, &fd, gui_state);
|
||||
draw_blocks::chart(f, lower_main[1], app_data);
|
||||
}
|
||||
|
||||
if let Some(info) = info_text {
|
||||
draw_blocks::info(f, info);
|
||||
if let Some(info) = fd.info_text {
|
||||
draw_blocks::info(f, &info);
|
||||
}
|
||||
|
||||
// Check if error, and show popup if so
|
||||
if gui_state.lock().status_contains(&[Status::Help]) {
|
||||
if fd.help_visible {
|
||||
draw_blocks::help_box(f);
|
||||
}
|
||||
|
||||
if let Some(error) = has_error {
|
||||
if let Some(error) = fd.has_error {
|
||||
draw_blocks::error(f, error, None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user