refactor: ui into a struct

This commit is contained in:
Jack Wills
2023-02-28 19:48:49 +00:00
parent c69ab4f7c3
commit 3437df5988
2 changed files with 143 additions and 118 deletions
+2 -4
View File
@@ -41,7 +41,7 @@ mod input_handler;
mod parse_args; mod parse_args;
mod ui; mod ui;
use ui::{create_ui, GuiState, Status}; use ui::{GuiState, Status, Ui};
use crate::docker_data::DockerMessage; use crate::docker_data::DockerMessage;
@@ -148,9 +148,7 @@ async fn main() {
handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running); handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running);
if args.gui { if args.gui {
create_ui(app_data, docker_sx, gui_state, is_running, input_sx) Ui::create(app_data, docker_sx, gui_state, is_running, input_sx).await;
.await
.unwrap_or(());
} else { } else {
// Debug mode for testing, mostly pointless, doesn't take terminal // Debug mode for testing, mostly pointless, doesn't take terminal
info!("in debug mode"); info!("in debug mode");
+141 -114
View File
@@ -6,12 +6,13 @@ use crossterm::{
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
io::{self, Write}, io::{self, Stdout, Write},
process::Stdio, process::Stdio,
sync::{atomic::Ordering, Arc}, sync::{atomic::Ordering, Arc},
}; };
use std::{sync::atomic::AtomicBool, time::Instant}; use std::{sync::atomic::AtomicBool, time::Instant};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use tracing::error;
use tui::{ use tui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
@@ -29,130 +30,156 @@ use crate::{
input_handler::InputMessages, input_handler::InputMessages,
}; };
/// Take control of the terminal in order to draw gui pub struct Ui {
pub async fn create_ui(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker_sx: Sender<DockerMessage>, docker_sx: Sender<DockerMessage>,
gui_state: Arc<Mutex<GuiState>>, gui_state: Arc<Mutex<GuiState>>,
is_running: Arc<AtomicBool>, is_running: Arc<AtomicBool>,
sender: Sender<InputMessages>, sender: Sender<InputMessages>,
) -> Result<()> { terminal: Terminal<CrosstermBackend<Stdout>>,
enable_raw_mode()?;
let mut stdout = io::stdout();
// EnableMouseCapture
execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
run_app(
app_data,
docker_sx,
gui_state,
is_running,
sender,
&mut terminal,
)
.await
.unwrap_or(());
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
} }
/// Run the error message loop, for 5 seconds, with countdown impl Ui {
fn err_loop<B: Backend + Send>( /// Create a new Ui struct, and execute the drawing loops
terminal: &mut Terminal<B>, pub async fn create(
) -> Result<(), AppError> { app_data: Arc<Mutex<AppData>>,
let mut seconds = 5; docker_sx: Sender<DockerMessage>,
let mut now = Instant::now(); gui_state: Arc<Mutex<GuiState>>,
loop { is_running: Arc<AtomicBool>,
// This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout sender: Sender<InputMessages>,
execute!(io::stdout(), EnableMouseCapture).unwrap_or(()); ) {
execute!(io::stdout(), DisableMouseCapture).unwrap_or(()); if let Ok(mut terminal) = Self::start_terminal() {
if seconds < 1 { let mut ui = Self {
break; app_data,
} docker_sx,
if now.elapsed() >= std::time::Duration::from_secs(1) { gui_state,
seconds -= 1; is_running,
now = Instant::now(); sender,
} terminal,
};
if terminal if let Err(e) = ui.draw_ui().await {
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds))) error!("{e}");
.is_err() }
{ if let Err(e) = ui.end_terminal() {
return Err(AppError::Terminal); error!("{e}");
};
} }
} }
Ok(())
} // Setup the terminal for full-screen drawing mode, with mouse capture
fn start_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
/// Run the normal application ui loop enable_raw_mode()?;
async fn run_loop<B: Backend + Send>( app_data: Arc<Mutex<AppData>>, let mut stdout = io::stdout();
docker_sx: Sender<DockerMessage>, execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?;
gui_state: Arc<Mutex<GuiState>>, let backend = CrosstermBackend::new(stdout);
is_running: Arc<AtomicBool>, Terminal::new(backend)
sender: Sender<InputMessages>, }
terminal: &mut Terminal<B>) -> Result<(), AppError>{
let input_poll_rate = std::time::Duration::from_millis(100); /// reset the terminal back to default settings
let update_duration = pub fn end_terminal(&mut self) -> Result<()> {
std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval)); disable_raw_mode()?;
let mut now = Instant::now(); execute!(
while is_running.load(Ordering::SeqCst) { self.terminal.backend_mut(),
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() { LeaveAlternateScreen,
return Err(AppError::Terminal); DisableMouseCapture
} )?;
if crossterm::event::poll(input_poll_rate).unwrap_or(false) { self.terminal.show_cursor()?;
if let Ok(event) = event::read() { Ok(())
if let Event::Key(key) = event { }
sender
.send(InputMessages::ButtonPress(key.code)) /// Draw the the error message ui, for 5 seconds, with a countdown
.await fn err_loop(&mut self) -> Result<(), AppError> {
.unwrap_or(()); let mut seconds = 5;
} else if let Event::Mouse(m) = event { let mut now = Instant::now();
sender loop {
.send(InputMessages::MouseEvent(m)) // This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout
.await std::thread::spawn(|| {
.unwrap_or(()); execute!(io::stdout(), EnableMouseCapture).unwrap_or(());
} else if let Event::Resize(_, _) = event { execute!(io::stdout(), DisableMouseCapture).unwrap_or(());
gui_state.lock().clear_area_map(); });
terminal.autoresize().unwrap_or(());
} if now.elapsed() >= std::time::Duration::from_secs(1) {
} seconds -= 1;
} now = Instant::now();
}
if now.elapsed() >= update_duration {
docker_sx.send(DockerMessage::Update).await.unwrap_or(()); if seconds < 1 {
now = Instant::now(); break;
} }
}
Ok(()) if self
} .terminal
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
/// Run a loop to draw the gui .is_err()
async fn run_app<B: Backend + Send>( {
app_data: Arc<Mutex<AppData>>, return Err(AppError::Terminal);
docker_sx: Sender<DockerMessage>, }
gui_state: Arc<Mutex<GuiState>>, }
is_running: Arc<AtomicBool>, Ok(())
sender: Sender<InputMessages>, }
terminal: &mut Terminal<B>,
) -> Result<(), AppError> { /// The loop for drawing the main UI to the terminal
let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]); async fn gui_loop(&mut self) -> Result<(), AppError> {
if status_dockerconnect { let input_poll_rate = std::time::Duration::from_millis(100);
err_loop(terminal).unwrap_or(()); let update_duration =
} else { std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval));
run_loop(app_data, docker_sx, gui_state, is_running, sender, terminal).await?; let mut now = Instant::now();
} while self.is_running.load(Ordering::SeqCst) {
Ok(()) if self
.terminal
.draw(|frame| ui(frame, &self.app_data, &self.gui_state))
.is_err()
{
return Err(AppError::Terminal);
}
if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
if let Ok(event) = event::read() {
if let Event::Key(key) = event {
self.sender
.send(InputMessages::ButtonPress(key.code))
.await
.unwrap_or(());
} else if let Event::Mouse(m) = event {
self.sender
.send(InputMessages::MouseEvent(m))
.await
.unwrap_or(());
} else if let Event::Resize(_, _) = event {
self.gui_state.lock().clear_area_map();
self.terminal.autoresize().unwrap_or(());
}
}
}
if now.elapsed() >= update_duration {
self.docker_sx
.send(DockerMessage::Update)
.await
.unwrap_or(());
now = Instant::now();
}
}
Ok(())
}
/// 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 {
self.err_loop()?;
} else {
self.gui_loop().await?;
}
Ok(())
}
} }
/// Draw the main ui to a frame of the terminal
fn ui<B: Backend>( fn ui<B: Backend>(
f: &mut Frame<'_, B>, f: &mut Frame<'_, B>,
app_data: &Arc<Mutex<AppData>>, app_data: &Arc<Mutex<AppData>>,