fix: Only re-draw the screen if data/layout has changed
This commit is contained in:
@@ -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));
|
||||
|
||||
+37
-10
@@ -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 => (),
|
||||
_ => {
|
||||
self.status.insert(status);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
+43
-20
@@ -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,25 +185,41 @@ 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();
|
||||
while self.is_running.load(Ordering::SeqCst) {
|
||||
let fd = FrameData::from(&*self);
|
||||
let exec = fd.status.contains(&Status::Exec);
|
||||
if exec {
|
||||
self.exec().await;
|
||||
}
|
||||
let docker_interval_ms = u128::from(self.app_data.lock().config.docker_interval_ms);
|
||||
let mut drawn_at = std::time::Instant::now();
|
||||
|
||||
if self
|
||||
.terminal
|
||||
.draw(|frame| {
|
||||
draw_frame(&self.app_data, colors, &keymap, frame, &fd, &self.gui_state);
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Err(AppError::Terminal);
|
||||
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;
|
||||
}
|
||||
|
||||
if self
|
||||
.terminal
|
||||
.draw(|frame| {
|
||||
draw_frame(&self.app_data, colors, &keymap, frame, &fd, &self.gui_state);
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
return Err(AppError::Terminal);
|
||||
}
|
||||
}
|
||||
|
||||
if crossterm::event::poll(POLL_RATE).unwrap_or(false) {
|
||||
|
||||
@@ -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