refactor: ui into a struct
This commit is contained in:
+2
-4
@@ -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");
|
||||||
|
|||||||
+86
-59
@@ -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,61 +30,85 @@ 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(
|
impl Ui {
|
||||||
|
/// Create a new Ui struct, and execute the drawing loops
|
||||||
|
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(mut terminal) = Self::start_terminal() {
|
||||||
|
let mut ui = Self {
|
||||||
app_data,
|
app_data,
|
||||||
docker_sx,
|
docker_sx,
|
||||||
gui_state,
|
gui_state,
|
||||||
is_running,
|
is_running,
|
||||||
sender,
|
sender,
|
||||||
&mut terminal,
|
terminal,
|
||||||
)
|
};
|
||||||
.await
|
if let Err(e) = ui.draw_ui().await {
|
||||||
.unwrap_or(());
|
error!("{e}");
|
||||||
|
}
|
||||||
|
if let Err(e) = ui.end_terminal() {
|
||||||
|
error!("{e}");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the terminal for full-screen drawing mode, with mouse capture
|
||||||
|
fn start_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnableMouseCapture, EnterAlternateScreen)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
Terminal::new(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reset the terminal back to default settings
|
||||||
|
pub fn end_terminal(&mut self) -> Result<()> {
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
execute!(
|
execute!(
|
||||||
terminal.backend_mut(),
|
self.terminal.backend_mut(),
|
||||||
LeaveAlternateScreen,
|
LeaveAlternateScreen,
|
||||||
DisableMouseCapture
|
DisableMouseCapture
|
||||||
)?;
|
)?;
|
||||||
|
self.terminal.show_cursor()?;
|
||||||
terminal.show_cursor()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the error message loop, for 5 seconds, with countdown
|
/// Draw the the error message ui, for 5 seconds, with a countdown
|
||||||
fn err_loop<B: Backend + Send>(
|
fn err_loop(&mut self) -> Result<(), AppError> {
|
||||||
terminal: &mut Terminal<B>,
|
|
||||||
) -> Result<(), AppError> {
|
|
||||||
let mut seconds = 5;
|
let mut seconds = 5;
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
// This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout
|
// This is a fix for a weird bug on Linux + WSL which will output mouse movement to the stdout
|
||||||
|
std::thread::spawn(|| {
|
||||||
execute!(io::stdout(), EnableMouseCapture).unwrap_or(());
|
execute!(io::stdout(), EnableMouseCapture).unwrap_or(());
|
||||||
execute!(io::stdout(), DisableMouseCapture).unwrap_or(());
|
execute!(io::stdout(), DisableMouseCapture).unwrap_or(());
|
||||||
if seconds < 1 {
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
if now.elapsed() >= std::time::Duration::from_secs(1) {
|
if now.elapsed() >= std::time::Duration::from_secs(1) {
|
||||||
seconds -= 1;
|
seconds -= 1;
|
||||||
now = Instant::now();
|
now = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
if terminal
|
if seconds < 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.terminal
|
||||||
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
|
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
@@ -91,68 +116,70 @@ fn err_loop<B: Backend + Send>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the normal application ui loop
|
/// The loop for drawing the main UI to the terminal
|
||||||
async fn run_loop<B: Backend + Send>( app_data: Arc<Mutex<AppData>>,
|
async fn gui_loop(&mut self) -> Result<(), AppError> {
|
||||||
docker_sx: Sender<DockerMessage>,
|
|
||||||
gui_state: Arc<Mutex<GuiState>>,
|
|
||||||
is_running: Arc<AtomicBool>,
|
|
||||||
sender: Sender<InputMessages>,
|
|
||||||
terminal: &mut Terminal<B>) -> Result<(), AppError>{
|
|
||||||
let input_poll_rate = std::time::Duration::from_millis(100);
|
let input_poll_rate = std::time::Duration::from_millis(100);
|
||||||
let update_duration =
|
let update_duration =
|
||||||
std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval));
|
std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval));
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
while is_running.load(Ordering::SeqCst) {
|
while self.is_running.load(Ordering::SeqCst) {
|
||||||
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
|
if self
|
||||||
|
.terminal
|
||||||
|
.draw(|frame| ui(frame, &self.app_data, &self.gui_state))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
return Err(AppError::Terminal);
|
return Err(AppError::Terminal);
|
||||||
}
|
}
|
||||||
if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
|
if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
|
||||||
if let Ok(event) = event::read() {
|
if let Ok(event) = event::read() {
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
sender
|
self.sender
|
||||||
.send(InputMessages::ButtonPress(key.code))
|
.send(InputMessages::ButtonPress(key.code))
|
||||||
.await
|
.await
|
||||||
.unwrap_or(());
|
.unwrap_or(());
|
||||||
} else if let Event::Mouse(m) = event {
|
} else if let Event::Mouse(m) = event {
|
||||||
sender
|
self.sender
|
||||||
.send(InputMessages::MouseEvent(m))
|
.send(InputMessages::MouseEvent(m))
|
||||||
.await
|
.await
|
||||||
.unwrap_or(());
|
.unwrap_or(());
|
||||||
} else if let Event::Resize(_, _) = event {
|
} else if let Event::Resize(_, _) = event {
|
||||||
gui_state.lock().clear_area_map();
|
self.gui_state.lock().clear_area_map();
|
||||||
terminal.autoresize().unwrap_or(());
|
self.terminal.autoresize().unwrap_or(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if now.elapsed() >= update_duration {
|
if now.elapsed() >= update_duration {
|
||||||
docker_sx.send(DockerMessage::Update).await.unwrap_or(());
|
self.docker_sx
|
||||||
|
.send(DockerMessage::Update)
|
||||||
|
.await
|
||||||
|
.unwrap_or(());
|
||||||
now = Instant::now();
|
now = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
/// Run a loop to draw the gui
|
|
||||||
async fn run_app<B: Backend + Send>(
|
|
||||||
app_data: Arc<Mutex<AppData>>,
|
|
||||||
docker_sx: Sender<DockerMessage>,
|
|
||||||
gui_state: Arc<Mutex<GuiState>>,
|
|
||||||
is_running: Arc<AtomicBool>,
|
|
||||||
sender: Sender<InputMessages>,
|
|
||||||
terminal: &mut Terminal<B>,
|
|
||||||
) -> Result<(), AppError> {
|
|
||||||
let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]);
|
|
||||||
if status_dockerconnect {
|
|
||||||
err_loop(terminal).unwrap_or(());
|
|
||||||
} else {
|
|
||||||
run_loop(app_data, docker_sx, gui_state, is_running, sender, terminal).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 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(())
|
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>>,
|
||||||
|
|||||||
Reference in New Issue
Block a user