fix: Only re-draw the screen if data/layout has changed
This commit is contained in:
+55
-3
@@ -13,7 +13,7 @@ mod container_state;
|
||||
use crate::{
|
||||
app_error::AppError,
|
||||
config::Config,
|
||||
ui::{log_sanitizer, GuiState, Status},
|
||||
ui::{log_sanitizer, GuiState, Redraw, Status},
|
||||
ENTRY_POINT,
|
||||
};
|
||||
pub use container_state::*;
|
||||
@@ -122,7 +122,9 @@ pub struct AppData {
|
||||
error: Option<AppError>,
|
||||
filter: Filter,
|
||||
hidden_containers: Vec<ContainerItem>,
|
||||
redraw: Arc<Redraw>,
|
||||
sorted_by: Option<(Header, SortedOrder)>,
|
||||
current_sorted_id: Vec<ContainerId>,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
@@ -134,18 +136,22 @@ pub struct AppData {
|
||||
pub error: Option<AppError>,
|
||||
pub filter: Filter,
|
||||
pub hidden_containers: Vec<ContainerItem>,
|
||||
pub current_sorted_id: Vec<ContainerId>,
|
||||
pub redraw: Arc<Redraw>,
|
||||
pub sorted_by: Option<(Header, SortedOrder)>,
|
||||
}
|
||||
|
||||
impl AppData {
|
||||
/// Generate a default app_state
|
||||
pub fn default(config: Config) -> Self {
|
||||
pub fn new(config: Config, redraw: &Arc<Redraw>) -> Self {
|
||||
Self {
|
||||
config,
|
||||
containers: StatefulList::new(vec![]),
|
||||
current_sorted_id: vec![],
|
||||
error: None,
|
||||
filter: Filter::new(),
|
||||
hidden_containers: vec![],
|
||||
redraw: Arc::clone(redraw),
|
||||
sorted_by: None,
|
||||
}
|
||||
}
|
||||
@@ -186,6 +192,7 @@ impl AppData {
|
||||
/// 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
|
||||
fn filter_containers(&mut self) {
|
||||
self.redraw.set_true();
|
||||
let pre_len = self.get_container_len();
|
||||
|
||||
if !self.hidden_containers.is_empty() {
|
||||
@@ -289,6 +296,7 @@ impl AppData {
|
||||
/// Remove the sorted header & order, and sort by default - created datetime
|
||||
pub fn reset_sorted(&mut self) {
|
||||
self.set_sorted(None);
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Sort containers based on a given header, if headings match, and already ascending, remove sorting
|
||||
@@ -309,10 +317,19 @@ impl AppData {
|
||||
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,
|
||||
/// If not sort set, then sort by created time
|
||||
pub fn sort_containers(&mut self) {
|
||||
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 item_ord = match ord {
|
||||
SortedOrder::Asc => (a, b),
|
||||
@@ -372,13 +389,19 @@ impl AppData {
|
||||
.then_with(|| item_ord.0.id.cmp(&item_ord.1.id)),
|
||||
}
|
||||
};
|
||||
|
||||
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| {
|
||||
a.created
|
||||
.cmp(&b.created)
|
||||
.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
|
||||
pub fn containers_start(&mut self) {
|
||||
self.containers.start();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// select the last container
|
||||
pub fn containers_end(&mut self) {
|
||||
self.containers.end();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Select the next container
|
||||
pub fn containers_next(&mut self) {
|
||||
self.containers.next();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// select the previous container
|
||||
pub fn containers_previous(&mut self) {
|
||||
self.containers.previous();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Get ListState of containers
|
||||
@@ -521,6 +548,11 @@ impl AppData {
|
||||
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
|
||||
pub fn get_selected_container_id_state_name(&self) -> Option<(ContainerId, State, String)> {
|
||||
self.get_selected_container()
|
||||
@@ -545,6 +577,7 @@ impl AppData {
|
||||
pub fn docker_controls_next(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.docker_controls.next();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,6 +585,7 @@ impl AppData {
|
||||
pub fn docker_controls_previous(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.docker_controls.previous();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,6 +593,7 @@ impl AppData {
|
||||
pub fn docker_controls_start(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.docker_controls.start();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,6 +601,7 @@ impl AppData {
|
||||
pub fn docker_controls_end(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.docker_controls.end();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,6 +639,7 @@ impl AppData {
|
||||
pub fn log_next(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.next();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,6 +647,7 @@ impl AppData {
|
||||
pub fn log_previous(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.previous();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,6 +655,7 @@ impl AppData {
|
||||
pub fn log_end(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.end();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,6 +663,7 @@ impl AppData {
|
||||
pub fn log_start(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.start();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,12 +704,14 @@ impl AppData {
|
||||
/// Remove single app_state error
|
||||
pub fn remove_error(&mut self) {
|
||||
self.error = None;
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Insert single app_state error
|
||||
pub fn set_error(&mut self, error: AppError, gui_state: &Arc<Mutex<GuiState>>, status: Status) {
|
||||
gui_state.lock().status_push(status);
|
||||
self.error = Some(error);
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Check if the selected container is a dockerised version of oxker
|
||||
@@ -758,6 +800,9 @@ impl AppData {
|
||||
container.tx.update(tx);
|
||||
container.mem_limit.update(mem_limit);
|
||||
}
|
||||
if self.is_selected_container(id) {
|
||||
self.redraw.set_true();
|
||||
}
|
||||
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
|
||||
if self.containers.items.get(index).is_some() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
if self.is_selected_container(id) {
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -18,7 +18,7 @@ mod parse_config_file;
|
||||
pub struct Config {
|
||||
pub app_colors: AppColors,
|
||||
pub color_logs: bool,
|
||||
pub docker_interval: u32,
|
||||
pub docker_interval_ms: u32,
|
||||
pub gui: bool,
|
||||
pub host: Option<String>,
|
||||
pub in_container: bool,
|
||||
@@ -38,7 +38,7 @@ impl From<&Args> for Config {
|
||||
Self {
|
||||
app_colors: AppColors::new(),
|
||||
color_logs: args.color,
|
||||
docker_interval: args.docker_interval,
|
||||
docker_interval_ms: args.docker_interval,
|
||||
gui: !args.gui,
|
||||
host: args.host.clone(),
|
||||
in_container: Self::check_if_in_container(),
|
||||
@@ -60,7 +60,7 @@ impl From<ConfigFile> for Config {
|
||||
Self {
|
||||
app_colors: AppColors::from(config_file.colors),
|
||||
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),
|
||||
host: config_file.host,
|
||||
in_container: Self::check_if_in_container(),
|
||||
@@ -129,7 +129,7 @@ impl Config {
|
||||
/// make sure color_logs and raw_logs can't clash
|
||||
fn merge_args(mut self, config_from_cli: Self) -> Self {
|
||||
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.raw_logs = config_from_cli.raw_logs;
|
||||
self.show_self = config_from_cli.show_self;
|
||||
@@ -137,8 +137,8 @@ impl Config {
|
||||
self.show_timestamp = config_from_cli.show_timestamp;
|
||||
self.use_cli = config_from_cli.use_cli;
|
||||
|
||||
if config_from_cli.docker_interval < 1000 {
|
||||
self.docker_interval = 1000;
|
||||
if config_from_cli.docker_interval_ms < 1000 {
|
||||
self.docker_interval_ms = 1000;
|
||||
}
|
||||
|
||||
if let Some(host) = config_from_cli.host {
|
||||
|
||||
@@ -400,7 +400,8 @@ impl DockerData {
|
||||
|
||||
/// Send an update message every x ms, where x is the args.docker_interval
|
||||
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();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
|
||||
@@ -625,7 +625,7 @@ impl InputHandler {
|
||||
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
@@ -23,7 +23,7 @@ mod exec;
|
||||
mod input_handler;
|
||||
mod ui;
|
||||
|
||||
use ui::{GuiState, Status, Ui};
|
||||
use ui::{GuiState, Redraw, Status, Ui};
|
||||
|
||||
use crate::docker_data::DockerMessage;
|
||||
|
||||
@@ -98,9 +98,10 @@ fn handler_init(
|
||||
async fn main() {
|
||||
setup_tracing();
|
||||
let config = config::Config::new();
|
||||
let redraw = Arc::new(Redraw::new());
|
||||
|
||||
let app_data = Arc::new(Mutex::new(AppData::default(config.clone())));
|
||||
let gui_state = Arc::new(Mutex::new(GuiState::default()));
|
||||
let app_data = Arc::new(Mutex::new(AppData::new(config.clone(), &redraw)));
|
||||
let gui_state = Arc::new(Mutex::new(GuiState::new(&redraw)));
|
||||
let is_running = Arc::new(AtomicBool::new(true));
|
||||
let (docker_tx, docker_rx) = tokio::sync::mpsc::channel(32);
|
||||
|
||||
@@ -109,7 +110,7 @@ async fn main() {
|
||||
if config.gui {
|
||||
let (input_tx, input_rx) = tokio::sync::mpsc::channel(32);
|
||||
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 {
|
||||
info!("in debug mode\n");
|
||||
let mut now = std::time::Instant::now();
|
||||
@@ -120,7 +121,7 @@ async fn main() {
|
||||
error!("{}", err);
|
||||
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())
|
||||
.map(u64::try_from)
|
||||
{
|
||||
@@ -148,6 +149,8 @@ async fn main() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use bollard::service::{ContainerSummary, Port};
|
||||
|
||||
use crate::{
|
||||
@@ -156,13 +159,14 @@ mod tests {
|
||||
RunningState, State, StatefulList,
|
||||
},
|
||||
config::{AppColors, Config, Keymap},
|
||||
ui::Redraw,
|
||||
};
|
||||
|
||||
/// Default test config, has timestamps turned off
|
||||
pub fn gen_config() -> Config {
|
||||
Config {
|
||||
color_logs: false,
|
||||
docker_interval: 1000,
|
||||
docker_interval_ms: 1000,
|
||||
gui: true,
|
||||
host: None,
|
||||
show_std_err: false,
|
||||
@@ -200,8 +204,10 @@ mod tests {
|
||||
AppData {
|
||||
containers: StatefulList::new(containers.to_vec()),
|
||||
hidden_containers: vec![],
|
||||
current_sorted_id: vec![],
|
||||
error: None,
|
||||
sorted_by: None,
|
||||
redraw: Arc::new(Redraw::new()),
|
||||
filter: Filter::new(),
|
||||
config: gen_config(),
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ pub mod tests {
|
||||
use crate::{
|
||||
app_data::{AppData, ContainerId, ContainerImage, ContainerName, ContainerPorts},
|
||||
tests::{gen_appdata, gen_containers},
|
||||
ui::{draw_frame, GuiState},
|
||||
ui::{draw_frame, GuiState, Redraw},
|
||||
};
|
||||
|
||||
use super::FrameData;
|
||||
@@ -194,7 +194,8 @@ pub mod tests {
|
||||
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 gui_state = Arc::new(Mutex::new(gui_state));
|
||||
|
||||
+36
-9
@@ -13,6 +13,8 @@ use crate::{
|
||||
exec::ExecMode,
|
||||
};
|
||||
|
||||
use super::Redraw;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum SelectablePanel {
|
||||
#[default]
|
||||
@@ -171,22 +173,40 @@ pub enum Status {
|
||||
}
|
||||
|
||||
/// Global gui_state, stored in an Arc<Mutex>
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct GuiState {
|
||||
delete_container: Option<ContainerId>,
|
||||
exec_mode: Option<ExecMode>,
|
||||
loading_handle: Option<JoinHandle<()>>,
|
||||
loading_index: u8,
|
||||
loading_set: HashSet<Uuid>,
|
||||
intersect_delete: HashMap<DeleteButton, Rect>,
|
||||
intersect_heading: HashMap<Header, Rect>,
|
||||
intersect_help: Option<Rect>,
|
||||
intersect_panel: HashMap<SelectablePanel, Rect>,
|
||||
loading_handle: Option<JoinHandle<()>>,
|
||||
loading_index: u8,
|
||||
loading_set: HashSet<Uuid>,
|
||||
redraw: Arc<Redraw>,
|
||||
selected_panel: SelectablePanel,
|
||||
status: HashSet<Status>,
|
||||
pub info_box_text: Option<(String, Instant)>,
|
||||
}
|
||||
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
|
||||
pub fn clear_area_map(&mut self) {
|
||||
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
|
||||
pub fn get_intersect_panel(&mut self, rect: Rect) {
|
||||
pub fn check_panel_intersect(&mut self, rect: Rect) {
|
||||
if let Some(data) = self
|
||||
.intersect_panel
|
||||
.iter()
|
||||
@@ -207,6 +227,7 @@ impl GuiState {
|
||||
.first()
|
||||
{
|
||||
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
|
||||
@@ -307,6 +329,7 @@ impl GuiState {
|
||||
pub fn set_exec_mode(&mut self, mode: ExecMode) {
|
||||
self.exec_mode = Some(mode);
|
||||
self.status.insert(Status::Exec);
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
pub fn get_exec_mode(&self) -> Option<ExecMode> {
|
||||
@@ -316,22 +339,22 @@ impl GuiState {
|
||||
/// 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
|
||||
pub fn status_push(&mut self, status: Status) {
|
||||
match status {
|
||||
Status::Exec => (),
|
||||
_ => {
|
||||
if status != Status::Exec {
|
||||
self.status.insert(status);
|
||||
}
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
/// Change to next selectable panel
|
||||
pub fn next_panel(&mut self) {
|
||||
self.selected_panel = self.selected_panel.next();
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Change to previous selectable panel
|
||||
pub fn previous_panel(&mut self) {
|
||||
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
|
||||
@@ -342,6 +365,7 @@ impl GuiState {
|
||||
self.loading_index += 1;
|
||||
}
|
||||
self.loading_set.insert(uuid);
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
pub fn is_loading(&self) -> bool {
|
||||
@@ -374,6 +398,7 @@ impl GuiState {
|
||||
/// Stop the loading_spin function, and reset gui loading status
|
||||
pub fn stop_loading_animation(&mut self, loading_uuid: Uuid) {
|
||||
self.loading_set.remove(&loading_uuid);
|
||||
self.redraw.set_true();
|
||||
if self.loading_set.is_empty() {
|
||||
self.loading_index = 0;
|
||||
if let Some(h) = &self.loading_handle {
|
||||
@@ -386,10 +411,12 @@ impl GuiState {
|
||||
/// Set info box content
|
||||
pub fn set_info_box(&mut self, text: &str) {
|
||||
self.info_box_text = Some((text.to_owned(), std::time::Instant::now()));
|
||||
self.redraw.set_true();
|
||||
}
|
||||
|
||||
/// Remove info box content
|
||||
pub fn reset_info_box(&mut self) {
|
||||
self.info_box_text = None;
|
||||
self.redraw.set_true();
|
||||
}
|
||||
}
|
||||
|
||||
+29
-6
@@ -23,6 +23,8 @@ use tracing::error;
|
||||
mod color_match;
|
||||
mod draw_blocks;
|
||||
mod gui_state;
|
||||
mod redraw;
|
||||
pub use redraw::Redraw;
|
||||
|
||||
pub use self::color_match::*;
|
||||
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
||||
@@ -37,16 +39,19 @@ use crate::{
|
||||
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 {
|
||||
app_data: Arc<Mutex<AppData>>,
|
||||
cursor_position: Position,
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
input_tx: Sender<InputMessages>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
now: Instant,
|
||||
redraw: Arc<Redraw>,
|
||||
terminal: Terminal<CrosstermBackend<Stdout>>,
|
||||
cursor_position: Position,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
@@ -68,6 +73,7 @@ impl Ui {
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
input_tx: Sender<InputMessages>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
redraw: Arc<Redraw>,
|
||||
) {
|
||||
if let Ok(mut terminal) = Self::setup_terminal() {
|
||||
let cursor_position = terminal.get_cursor_position().unwrap_or_default();
|
||||
@@ -78,6 +84,7 @@ impl Ui {
|
||||
input_tx,
|
||||
is_running,
|
||||
now: Instant::now(),
|
||||
redraw,
|
||||
terminal,
|
||||
};
|
||||
if let Err(e) = ui.draw_ui().await {
|
||||
@@ -126,18 +133,18 @@ impl Ui {
|
||||
let mut seconds = 5;
|
||||
let colors = self.app_data.lock().config.app_colors;
|
||||
let keymap = self.app_data.lock().config.keymap.clone();
|
||||
let mut render = true;
|
||||
let mut redraw = true;
|
||||
loop {
|
||||
if self.now.elapsed() >= std::time::Duration::from_secs(1) {
|
||||
seconds -= 1;
|
||||
self.now = Instant::now();
|
||||
render = true;
|
||||
redraw = true;
|
||||
if seconds < 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if render
|
||||
if redraw
|
||||
&& self
|
||||
.terminal
|
||||
.draw(|f| {
|
||||
@@ -153,7 +160,7 @@ impl Ui {
|
||||
{
|
||||
return Err(AppError::Terminal);
|
||||
}
|
||||
render = false;
|
||||
redraw = false;
|
||||
std::thread::sleep(POLL_RATE);
|
||||
}
|
||||
Ok(())
|
||||
@@ -178,12 +185,27 @@ impl Ui {
|
||||
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
|
||||
async fn gui_loop(&mut self) -> Result<(), AppError> {
|
||||
let colors = self.app_data.lock().config.app_colors;
|
||||
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) {
|
||||
if self.should_redraw(&mut drawn_at, docker_interval_ms) {
|
||||
let fd = FrameData::from(&*self);
|
||||
|
||||
let exec = fd.status.contains(&Status::Exec);
|
||||
if exec {
|
||||
self.exec().await;
|
||||
@@ -198,6 +220,7 @@ impl Ui {
|
||||
{
|
||||
return Err(AppError::Terminal);
|
||||
}
|
||||
}
|
||||
|
||||
if crossterm::event::poll(POLL_RATE).unwrap_or(false) {
|
||||
if let Ok(event) = event::read() {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user