Merge branch 'fix/renderer' into dev

This commit is contained in:
Jack Wills
2025-02-21 18:04:27 +00:00
9 changed files with 197 additions and 57 deletions
+55 -3
View File
@@ -13,7 +13,7 @@ mod container_state;
use crate::{ use crate::{
app_error::AppError, app_error::AppError,
config::Config, config::Config,
ui::{log_sanitizer, GuiState, Status}, ui::{log_sanitizer, GuiState, Redraw, Status},
ENTRY_POINT, ENTRY_POINT,
}; };
pub use container_state::*; pub use container_state::*;
@@ -122,7 +122,9 @@ pub struct AppData {
error: Option<AppError>, error: Option<AppError>,
filter: Filter, filter: Filter,
hidden_containers: Vec<ContainerItem>, hidden_containers: Vec<ContainerItem>,
redraw: Arc<Redraw>,
sorted_by: Option<(Header, SortedOrder)>, sorted_by: Option<(Header, SortedOrder)>,
current_sorted_id: Vec<ContainerId>,
pub config: Config, pub config: Config,
} }
@@ -134,18 +136,22 @@ pub struct AppData {
pub error: Option<AppError>, pub error: Option<AppError>,
pub filter: Filter, pub filter: Filter,
pub hidden_containers: Vec<ContainerItem>, pub hidden_containers: Vec<ContainerItem>,
pub current_sorted_id: Vec<ContainerId>,
pub redraw: Arc<Redraw>,
pub sorted_by: Option<(Header, SortedOrder)>, pub sorted_by: Option<(Header, SortedOrder)>,
} }
impl AppData { impl AppData {
/// Generate a default app_state /// Generate a default app_state
pub fn default(config: Config) -> Self { pub fn new(config: Config, redraw: &Arc<Redraw>) -> Self {
Self { Self {
config, config,
containers: StatefulList::new(vec![]), containers: StatefulList::new(vec![]),
current_sorted_id: vec![],
error: None, error: None,
filter: Filter::new(), filter: Filter::new(),
hidden_containers: vec![], hidden_containers: vec![],
redraw: Arc::clone(redraw),
sorted_by: None, sorted_by: None,
} }
} }
@@ -186,6 +192,7 @@ impl AppData {
/// sets the state to start if any filtering has occurred /// sets the state to start if any filtering has occurred
/// Also search in the "hidden" vec for items and insert back into the main containers vec /// Also search in the "hidden" vec for items and insert back into the main containers vec
fn filter_containers(&mut self) { fn filter_containers(&mut self) {
self.redraw.set_true();
let pre_len = self.get_container_len(); let pre_len = self.get_container_len();
if !self.hidden_containers.is_empty() { if !self.hidden_containers.is_empty() {
@@ -289,6 +296,7 @@ impl AppData {
/// Remove the sorted header & order, and sort by default - created datetime /// Remove the sorted header & order, and sort by default - created datetime
pub fn reset_sorted(&mut self) { pub fn reset_sorted(&mut self) {
self.set_sorted(None); self.set_sorted(None);
self.redraw.set_true();
} }
/// Sort containers based on a given header, if headings match, and already ascending, remove sorting /// Sort containers based on a given header, if headings match, and already ascending, remove sorting
@@ -309,10 +317,19 @@ impl AppData {
self.sorted_by self.sorted_by
} }
/// Get a vec of the containers ID's
fn get_current_ids(&self) -> Vec<ContainerId> {
self.containers
.items
.iter()
.map(|i| i.id.clone())
.collect::<Vec<_>>()
}
/// Sort the containers vec, based on a heading (and if clash, then by name), either ascending or descending, /// Sort the containers vec, based on a heading (and if clash, then by name), either ascending or descending,
/// If not sort set, then sort by created time /// If not sort set, then sort by created time
pub fn sort_containers(&mut self) { pub fn sort_containers(&mut self) {
if let Some((head, ord)) = self.sorted_by { if let Some((head, ord)) = self.sorted_by {
let pre_order = self.get_current_ids();
let sort_closure = |a: &ContainerItem, b: &ContainerItem| -> std::cmp::Ordering { let sort_closure = |a: &ContainerItem, b: &ContainerItem| -> std::cmp::Ordering {
let item_ord = match ord { let item_ord = match ord {
SortedOrder::Asc => (a, b), SortedOrder::Asc => (a, b),
@@ -372,13 +389,19 @@ impl AppData {
.then_with(|| item_ord.0.id.cmp(&item_ord.1.id)), .then_with(|| item_ord.0.id.cmp(&item_ord.1.id)),
} }
}; };
self.containers.items.sort_by(sort_closure); self.containers.items.sort_by(sort_closure);
} else { if pre_order != self.get_current_ids() {
self.redraw.set_true();
}
} else if self.current_sorted_id != self.get_current_ids() {
self.containers.items.sort_by(|a, b| { self.containers.items.sort_by(|a, b| {
a.created a.created
.cmp(&b.created) .cmp(&b.created)
.then_with(|| a.name.get().cmp(b.name.get())) .then_with(|| a.name.get().cmp(b.name.get()))
}); });
self.redraw.set_true();
self.current_sorted_id = self.get_current_ids();
} }
} }
@@ -414,21 +437,25 @@ impl AppData {
/// Select the first container /// Select the first container
pub fn containers_start(&mut self) { pub fn containers_start(&mut self) {
self.containers.start(); self.containers.start();
self.redraw.set_true();
} }
/// select the last container /// select the last container
pub fn containers_end(&mut self) { pub fn containers_end(&mut self) {
self.containers.end(); self.containers.end();
self.redraw.set_true();
} }
/// Select the next container /// Select the next container
pub fn containers_next(&mut self) { pub fn containers_next(&mut self) {
self.containers.next(); self.containers.next();
self.redraw.set_true();
} }
/// select the previous container /// select the previous container
pub fn containers_previous(&mut self) { pub fn containers_previous(&mut self) {
self.containers.previous(); self.containers.previous();
self.redraw.set_true();
} }
/// Get ListState of containers /// Get ListState of containers
@@ -521,6 +548,11 @@ impl AppData {
self.get_selected_container().map(|i| i.id.clone()) self.get_selected_container().map(|i| i.id.clone())
} }
/// Check if a given ID matches the currently selected container
pub fn is_selected_container(&self, id: &ContainerId) -> bool {
self.get_selected_container().is_some_and(|i| &i.id == id)
}
/// Get the Id and State for the currently selected container - used by the exec check method /// Get the Id and State for the currently selected container - used by the exec check method
pub fn get_selected_container_id_state_name(&self) -> Option<(ContainerId, State, String)> { pub fn get_selected_container_id_state_name(&self) -> Option<(ContainerId, State, String)> {
self.get_selected_container() self.get_selected_container()
@@ -545,6 +577,7 @@ impl AppData {
pub fn docker_controls_next(&mut self) { pub fn docker_controls_next(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.next(); i.docker_controls.next();
self.redraw.set_true();
} }
} }
@@ -552,6 +585,7 @@ impl AppData {
pub fn docker_controls_previous(&mut self) { pub fn docker_controls_previous(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.previous(); i.docker_controls.previous();
self.redraw.set_true();
} }
} }
@@ -559,6 +593,7 @@ impl AppData {
pub fn docker_controls_start(&mut self) { pub fn docker_controls_start(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.start(); i.docker_controls.start();
self.redraw.set_true();
} }
} }
@@ -566,6 +601,7 @@ impl AppData {
pub fn docker_controls_end(&mut self) { pub fn docker_controls_end(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.docker_controls.end(); i.docker_controls.end();
self.redraw.set_true();
} }
} }
@@ -603,6 +639,7 @@ impl AppData {
pub fn log_next(&mut self) { pub fn log_next(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.logs.next(); i.logs.next();
self.redraw.set_true();
} }
} }
@@ -610,6 +647,7 @@ impl AppData {
pub fn log_previous(&mut self) { pub fn log_previous(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.logs.previous(); i.logs.previous();
self.redraw.set_true();
} }
} }
@@ -617,6 +655,7 @@ impl AppData {
pub fn log_end(&mut self) { pub fn log_end(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.logs.end(); i.logs.end();
self.redraw.set_true();
} }
} }
@@ -624,6 +663,7 @@ impl AppData {
pub fn log_start(&mut self) { pub fn log_start(&mut self) {
if let Some(i) = self.get_mut_selected_container() { if let Some(i) = self.get_mut_selected_container() {
i.logs.start(); i.logs.start();
self.redraw.set_true();
} }
} }
@@ -664,12 +704,14 @@ impl AppData {
/// Remove single app_state error /// Remove single app_state error
pub fn remove_error(&mut self) { pub fn remove_error(&mut self) {
self.error = None; self.error = None;
self.redraw.set_true();
} }
/// Insert single app_state error /// Insert single app_state error
pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) { pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
gui_state.lock().status_push(status); gui_state.lock().status_push(status);
self.error = Some(error); self.error = Some(error);
self.redraw.set_true();
} }
/// Check if the selected container is a dockerised version of oxker /// Check if the selected container is a dockerised version of oxker
@@ -758,6 +800,9 @@ impl AppData {
container.tx.update(tx); container.tx.update(tx);
container.mem_limit.update(mem_limit); container.mem_limit.update(mem_limit);
} }
if self.is_selected_container(id) {
self.redraw.set_true();
}
self.sort_containers(); self.sort_containers();
} }
@@ -793,6 +838,9 @@ impl AppData {
// Check is some, else can cause out of bounds error, if containers get removed before a docker update // Check is some, else can cause out of bounds error, if containers get removed before a docker update
if self.containers.items.get(index).is_some() { if self.containers.items.get(index).is_some() {
self.containers.items.remove(index); self.containers.items.remove(index);
if self.is_selected_container(id) {
self.redraw.set_true();
}
} }
} }
} }
@@ -872,6 +920,7 @@ impl AppData {
} }
} }
} }
// self.redraw.set_true("update_containers");
} }
} }
@@ -919,6 +968,9 @@ impl AppData {
container.logs.end(); container.logs.end();
} }
} }
if self.is_selected_container(id) {
self.redraw.set_true();
}
} }
} }
} }
+6 -6
View File
@@ -18,7 +18,7 @@ mod parse_config_file;
pub struct Config { pub struct Config {
pub app_colors: AppColors, pub app_colors: AppColors,
pub color_logs: bool, pub color_logs: bool,
pub docker_interval: u32, pub docker_interval_ms: u32,
pub gui: bool, pub gui: bool,
pub host: Option<String>, pub host: Option<String>,
pub in_container: bool, pub in_container: bool,
@@ -38,7 +38,7 @@ impl From<&Args> for Config {
Self { Self {
app_colors: AppColors::new(), app_colors: AppColors::new(),
color_logs: args.color, color_logs: args.color,
docker_interval: args.docker_interval, docker_interval_ms: args.docker_interval,
gui: !args.gui, gui: !args.gui,
host: args.host.clone(), host: args.host.clone(),
in_container: Self::check_if_in_container(), in_container: Self::check_if_in_container(),
@@ -60,7 +60,7 @@ impl From<ConfigFile> for Config {
Self { Self {
app_colors: AppColors::from(config_file.colors), app_colors: AppColors::from(config_file.colors),
color_logs: config_file.color_logs.unwrap_or(false), color_logs: config_file.color_logs.unwrap_or(false),
docker_interval: config_file.docker_interval.unwrap_or(1000), docker_interval_ms: config_file.docker_interval.unwrap_or(1000),
gui: config_file.gui.unwrap_or(true), gui: config_file.gui.unwrap_or(true),
host: config_file.host, host: config_file.host,
in_container: Self::check_if_in_container(), in_container: Self::check_if_in_container(),
@@ -129,7 +129,7 @@ impl Config {
/// make sure color_logs and raw_logs can't clash /// make sure color_logs and raw_logs can't clash
fn merge_args(mut self, config_from_cli: Self) -> Self { fn merge_args(mut self, config_from_cli: Self) -> Self {
self.color_logs = config_from_cli.color_logs; self.color_logs = config_from_cli.color_logs;
self.docker_interval = config_from_cli.docker_interval; self.docker_interval_ms = config_from_cli.docker_interval_ms;
self.gui = config_from_cli.gui; self.gui = config_from_cli.gui;
self.raw_logs = config_from_cli.raw_logs; self.raw_logs = config_from_cli.raw_logs;
self.show_self = config_from_cli.show_self; self.show_self = config_from_cli.show_self;
@@ -137,8 +137,8 @@ impl Config {
self.show_timestamp = config_from_cli.show_timestamp; self.show_timestamp = config_from_cli.show_timestamp;
self.use_cli = config_from_cli.use_cli; self.use_cli = config_from_cli.use_cli;
if config_from_cli.docker_interval < 1000 { if config_from_cli.docker_interval_ms < 1000 {
self.docker_interval = 1000; self.docker_interval_ms = 1000;
} }
if let Some(host) = config_from_cli.host { if let Some(host) = config_from_cli.host {
+2 -1
View File
@@ -400,7 +400,8 @@ impl DockerData {
/// Send an update message every x ms, where x is the args.docker_interval /// Send an update message every x ms, where x is the args.docker_interval
fn heartbeat(config: &Config, docker_tx: Sender<DockerMessage>) { fn heartbeat(config: &Config, docker_tx: Sender<DockerMessage>) {
let update_duration = std::time::Duration::from_millis(u64::from(config.docker_interval)); let update_duration =
std::time::Duration::from_millis(u64::from(config.docker_interval_ms));
let mut now = std::time::Instant::now(); let mut now = std::time::Instant::now();
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
+1 -1
View File
@@ -625,7 +625,7 @@ impl InputHandler {
self.gui_state.lock().status_push(Status::Help); self.gui_state.lock().status_push(Status::Help);
} }
self.gui_state.lock().get_intersect_panel(mouse_point); self.gui_state.lock().check_panel_intersect(mouse_point);
} }
_ => (), _ => (),
} }
+12 -6
View File
@@ -23,7 +23,7 @@ mod exec;
mod input_handler; mod input_handler;
mod ui; mod ui;
use ui::{GuiState, Status, Ui}; use ui::{GuiState, Redraw, Status, Ui};
use crate::docker_data::DockerMessage; use crate::docker_data::DockerMessage;
@@ -98,9 +98,10 @@ fn handler_init(
async fn main() { async fn main() {
setup_tracing(); setup_tracing();
let config = config::Config::new(); let config = config::Config::new();
let redraw = Arc::new(Redraw::new());
let app_data = Arc::new(Mutex::new(AppData::default(config.clone()))); let app_data = Arc::new(Mutex::new(AppData::new(config.clone(), &redraw)));
let gui_state = Arc::new(Mutex::new(GuiState::default())); let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw)));
let is_running = Arc::new(AtomicBool::new(true)); let is_running = Arc::new(AtomicBool::new(true));
let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32); let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32);
@@ -109,7 +110,7 @@ async fn main() {
if config.gui { if config.gui {
let (input_tx, input_rx) = tokio::sync::mpsc::channel(32); let (input_tx, input_rx) = tokio::sync::mpsc::channel(32);
handler_init(&app_data, &docker_tx, &gui_state, input_rx, &is_running); handler_init(&app_data, &docker_tx, &gui_state, input_rx, &is_running);
Ui::start(app_data, gui_state, input_tx, is_running).await; Ui::start(app_data, gui_state, input_tx, is_running, redraw).await;
} else { } else {
info!("in debug mode\n"); info!("in debug mode\n");
let mut now = std::time::Instant::now(); let mut now = std::time::Instant::now();
@@ -120,7 +121,7 @@ async fn main() {
error!("{}", err); error!("{}", err);
process::exit(1); process::exit(1);
} }
if let Some(Ok(to_sleep)) = u128::from(config.docker_interval) if let Some(Ok(to_sleep)) = u128::from(config.docker_interval_ms)
.checked_sub(now.elapsed().as_millis()) .checked_sub(now.elapsed().as_millis())
.map(u64::try_from) .map(u64::try_from)
{ {
@@ -148,6 +149,8 @@ async fn main() {
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod tests { mod tests {
use std::sync::Arc;
use bollard::service::{ContainerSummary, Port}; use bollard::service::{ContainerSummary, Port};
use crate::{ use crate::{
@@ -156,13 +159,14 @@ mod tests {
RunningState, State, StatefulList, RunningState, State, StatefulList,
}, },
config::{AppColors, Config, Keymap}, config::{AppColors, Config, Keymap},
ui::Redraw,
}; };
/// Default test config, has timestamps turned off /// Default test config, has timestamps turned off
pub fn gen_config() -> Config { pub fn gen_config() -> Config {
Config { Config {
color_logs: false, color_logs: false,
docker_interval: 1000, docker_interval_ms: 1000,
gui: true, gui: true,
host: None, host: None,
show_std_err: false, show_std_err: false,
@@ -200,8 +204,10 @@ mod tests {
AppData { AppData {
containers: StatefulList::new(containers.to_vec()), containers: StatefulList::new(containers.to_vec()),
hidden_containers: vec![], hidden_containers: vec![],
current_sorted_id: vec![],
error: None, error: None,
sorted_by: None, sorted_by: None,
redraw: Arc::new(Redraw::new()),
filter: Filter::new(), filter: Filter::new(),
config: gen_config(), config: gen_config(),
} }
+3 -2
View File
@@ -123,7 +123,7 @@ pub mod tests {
use crate::{ use crate::{
app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts}, app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts},
tests::{gen_appdata, gen_containers}, tests::{gen_appdata, gen_containers},
ui::{draw_frame, GuiState}, ui::{draw_frame, GuiState, Redraw},
}; };
use super::FrameData; use super::FrameData;
@@ -194,7 +194,8 @@ pub mod tests {
app_data.containers_start(); app_data.containers_start();
} }
let gui_state = GuiState::default(); let redraw = Arc::new(Redraw::new());
let gui_state = GuiState::new(&redraw);
let app_data = Arc::new(Mutex::new(app_data)); let app_data = Arc::new(Mutex::new(app_data));
let gui_state = Arc::new(Mutex::new(gui_state)); let gui_state = Arc::new(Mutex::new(gui_state));
+36 -9
View File
@@ -13,6 +13,8 @@ use crate::{
exec::ExecMode, exec::ExecMode,
}; };
use super::Redraw;
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)] #[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
pub enum SelectablePanel { pub enum SelectablePanel {
#[default] #[default]
@@ -171,22 +173,40 @@ pub enum Status {
} }
/// Global gui_state, stored in an Arc<Mutex> /// Global gui_state, stored in an Arc<Mutex>
#[derive(Debug, Default)] #[derive(Debug)]
pub struct GuiState { pub struct GuiState {
delete_container: Option<ContainerId>, delete_container: Option<ContainerId>,
exec_mode: Option<ExecMode>, exec_mode: Option<ExecMode>,
loading_handle: Option<JoinHandle<()>>,
loading_index: u8,
loading_set: HashSet<Uuid>,
intersect_delete: HashMap<DeleteButton, Rect>, intersect_delete: HashMap<DeleteButton, Rect>,
intersect_heading: HashMap<Header, Rect>, intersect_heading: HashMap<Header, Rect>,
intersect_help: Option<Rect>, intersect_help: Option<Rect>,
intersect_panel: HashMap<SelectablePanel, Rect>, intersect_panel: HashMap<SelectablePanel, Rect>,
loading_handle: Option<JoinHandle<()>>,
loading_index: u8,
loading_set: HashSet<Uuid>,
redraw: Arc<Redraw>,
selected_panel: SelectablePanel, selected_panel: SelectablePanel,
status: HashSet<Status>, status: HashSet<Status>,
pub info_box_text: Option<(String, Instant)>, pub info_box_text: Option<(String, Instant)>,
} }
impl GuiState { impl GuiState {
pub fn new(redraw: &Arc<Redraw>) -> Self {
Self {
delete_container: None,
exec_mode: None,
info_box_text: None,
intersect_delete: HashMap::new(),
intersect_heading: HashMap::new(),
intersect_help: None,
intersect_panel: HashMap::new(),
loading_handle: None,
loading_index: 0,
loading_set: HashSet::new(),
redraw: Arc::clone(redraw),
selected_panel: SelectablePanel::default(),
status: HashSet::new(),
}
}
/// 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.intersect_panel.clear(); self.intersect_panel.clear();
@@ -198,7 +218,7 @@ impl GuiState {
} }
/// 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 get_intersect_panel(&mut self, rect: Rect) { pub fn check_panel_intersect(&mut self, rect: Rect) {
if let Some(data) = self if let Some(data) = self
.intersect_panel .intersect_panel
.iter() .iter()
@@ -207,6 +227,7 @@ impl GuiState {
.first() .first()
{ {
self.selected_panel = *data.0; self.selected_panel = *data.0;
self.redraw.set_true();
} }
} }
@@ -299,6 +320,7 @@ impl GuiState {
} }
_ => (), _ => (),
} }
self.redraw.set_true();
} }
/// Inset the ExecMode into self, and set the Status as exec /// Inset the ExecMode into self, and set the Status as exec
@@ -307,6 +329,7 @@ impl GuiState {
pub fn set_exec_mode(&mut self, mode: ExecMode) { pub fn set_exec_mode(&mut self, mode: ExecMode) {
self.exec_mode = Some(mode); self.exec_mode = Some(mode);
self.status.insert(Status::Exec); self.status.insert(Status::Exec);
self.redraw.set_true();
} }
pub fn get_exec_mode(&self) -> Option<ExecMode> { pub fn get_exec_mode(&self) -> Option<ExecMode> {
@@ -316,22 +339,22 @@ impl GuiState {
/// Insert a gui_status into the current gui_status HashSet /// Insert a gui_status into the current gui_status HashSet
/// If the status is Exec, it won't get inserted, set_exec_mode() should be used instead /// If the status is Exec, it won't get inserted, set_exec_mode() should be used instead
pub fn status_push(&mut self, status: Status) { pub fn status_push(&mut self, status: Status) {
match status { if status != Status::Exec {
Status::Exec => (),
_ => {
self.status.insert(status); self.status.insert(status);
} self.redraw.set_true();
} }
} }
/// Change to next selectable panel /// Change to next selectable panel
pub fn next_panel(&mut self) { pub fn next_panel(&mut self) {
self.selected_panel = self.selected_panel.next(); self.selected_panel = self.selected_panel.next();
self.redraw.set_true();
} }
/// Change to previous selectable panel /// Change to previous selectable panel
pub fn previous_panel(&mut self) { pub fn previous_panel(&mut self) {
self.selected_panel = self.selected_panel.prev(); self.selected_panel = self.selected_panel.prev();
self.redraw.set_true();
} }
/// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array /// Insert a new loading_uuid into HashSet, and advance the loading_index by one frame, or reset to 0 if at end of array
@@ -342,6 +365,7 @@ impl GuiState {
self.loading_index += 1; self.loading_index += 1;
} }
self.loading_set.insert(uuid); self.loading_set.insert(uuid);
self.redraw.set_true();
} }
pub fn is_loading(&self) -> bool { pub fn is_loading(&self) -> bool {
@@ -374,6 +398,7 @@ impl GuiState {
/// Stop the loading_spin function, and reset gui loading status /// Stop the loading_spin function, and reset gui loading status
pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) { pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) {
self.loading_set.remove(&loading_uuid); self.loading_set.remove(&loading_uuid);
self.redraw.set_true();
if self.loading_set.is_empty() { if self.loading_set.is_empty() {
self.loading_index = 0; self.loading_index = 0;
if let Some(h) = &self.loading_handle { if let Some(h) = &self.loading_handle {
@@ -386,10 +411,12 @@ impl GuiState {
/// Set info box content /// Set info box content
pub fn set_info_box(&mut self, text: &str) { pub fn set_info_box(&mut self, text: &str) {
self.info_box_text = Some((text.to_owned(), std::time::Instant::now())); self.info_box_text = Some((text.to_owned(), std::time::Instant::now()));
self.redraw.set_true();
} }
/// Remove info box content /// Remove info box content
pub fn reset_info_box(&mut self) { pub fn reset_info_box(&mut self) {
self.info_box_text = None; self.info_box_text = None;
self.redraw.set_true();
} }
} }
+31 -3
View File
@@ -23,6 +23,8 @@ use tracing::error;
mod color_match; mod color_match;
mod draw_blocks; mod draw_blocks;
mod gui_state; mod gui_state;
mod redraw;
pub use redraw::Redraw;
pub use self::color_match::*; pub use self::color_match::*;
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
@@ -37,16 +39,19 @@ use crate::{
input_handler::InputMessages, input_handler::InputMessages,
}; };
const POLL_RATE: Duration = std::time::Duration::from_millis(100); const POLL_RATE: Duration = std::time::Duration::from_millis(50);
// could have a render struct, which takes in poll rate, and docker
pub struct Ui { pub struct Ui {
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
cursor_position: Position,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
input_tx: Sender<InputMessages>, input_tx: Sender<InputMessages>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
now: Instant, now: Instant,
redraw: Arc<Redraw>,
terminal: Terminal<CrosstermBackend<Stdout>>, terminal: Terminal<CrosstermBackend<Stdout>>,
cursor_position: Position,
} }
impl Ui { impl Ui {
@@ -68,6 +73,7 @@ impl Ui {
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
input_tx: Sender<InputMessages>, input_tx: Sender<InputMessages>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
redraw: Arc<Redraw>,
) { ) {
if let Ok(mut terminal) = Self::setup_terminal() { if let Ok(mut terminal) = Self::setup_terminal() {
let cursor_position = terminal.get_cursor_position().unwrap_or_default(); let cursor_position = terminal.get_cursor_position().unwrap_or_default();
@@ -78,6 +84,7 @@ impl Ui {
input_tx, input_tx,
is_running, is_running,
now: Instant::now(), now: Instant::now(),
redraw,
terminal, terminal,
}; };
if let Err(e) = ui.draw_ui().await { if let Err(e) = ui.draw_ui().await {
@@ -126,16 +133,19 @@ impl Ui {
let mut seconds = 5; let mut seconds = 5;
let colors = self.app_data.lock().config.app_colors; let colors = self.app_data.lock().config.app_colors;
let keymap = self.app_data.lock().config.keymap.clone(); let keymap = self.app_data.lock().config.keymap.clone();
let mut redraw = true;
loop { loop {
if self.now.elapsed() >= std::time::Duration::from_secs(1) { if self.now.elapsed() >= std::time::Duration::from_secs(1) {
seconds -= 1; seconds -= 1;
self.now = Instant::now(); self.now = Instant::now();
redraw = true;
if seconds < 1 { if seconds < 1 {
break; break;
} }
} }
if self if redraw
&& self
.terminal .terminal
.draw(|f| { .draw(|f| {
draw_blocks::error::draw( draw_blocks::error::draw(
@@ -150,6 +160,8 @@ impl Ui {
{ {
return Err(AppError::Terminal); return Err(AppError::Terminal);
} }
redraw = false;
std::thread::sleep(POLL_RATE);
} }
Ok(()) Ok(())
} }
@@ -173,12 +185,27 @@ impl Ui {
self.gui_state.lock().status_del(Status::Exec); self.gui_state.lock().status_del(Status::Exec);
} }
/// Use the previously redrawn time, the current time, the docker_interval, and the redraw struct, to calculate
/// if the screen should be redrawn or not
fn should_redraw(&self, previous: &mut Instant, docker_interval_ms: u128) -> bool {
let result = self.redraw.swap() || previous.elapsed().as_millis() >= docker_interval_ms;
if result {
*previous = std::time::Instant::now();
}
result
}
/// The loop for drawing the main UI to the terminal /// The loop for drawing the main UI to the terminal
async fn gui_loop(&mut self) -> Result<(), AppError> { async fn gui_loop(&mut self) -> Result<(), AppError> {
let colors = self.app_data.lock().config.app_colors; let colors = self.app_data.lock().config.app_colors;
let keymap = self.app_data.lock().config.keymap.clone(); let keymap = self.app_data.lock().config.keymap.clone();
let docker_interval_ms = u128::from(self.app_data.lock().config.docker_interval_ms);
let mut drawn_at = std::time::Instant::now();
while self.is_running.load(Ordering::SeqCst) { while self.is_running.load(Ordering::SeqCst) {
if self.should_redraw(&mut drawn_at, docker_interval_ms) {
let fd = FrameData::from(&*self); let fd = FrameData::from(&*self);
let exec = fd.status.contains(&Status::Exec); let exec = fd.status.contains(&Status::Exec);
if exec { if exec {
self.exec().await; self.exec().await;
@@ -193,6 +220,7 @@ impl Ui {
{ {
return Err(AppError::Terminal); return Err(AppError::Terminal);
} }
}
if crossterm::event::poll(POLL_RATE).unwrap_or(false) { if crossterm::event::poll(POLL_RATE).unwrap_or(false) {
if let Ok(event) = event::read() { if let Ok(event) = event::read() {
+25
View File
@@ -0,0 +1,25 @@
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug)]
pub struct Redraw(AtomicBool);
impl Redraw {
pub const fn new() -> Self {
Self(AtomicBool::new(true))
}
pub fn set_true(&self) {
self.0.store(true, Ordering::SeqCst);
}
/// Return the value of the self, and set to false
pub fn swap(&self) -> bool {
match self
.0
.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
{
Ok(previous_value) => previous_value,
Err(current_value) => current_value,
}
}
}