use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; use bollard::{container::StartContainerOptions, Docker}; use crossterm::{ event::{ DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind, }, execute, }; use parking_lot::Mutex; use tokio::sync::broadcast::Receiver; use tui::layout::Rect; mod message; use crate::{ app_data::{AppData, DockerControls}, app_error::AppError, ui::{GuiState, SelectablePanel}, }; pub use message::InputMessages; /// Handle all input events #[derive(Debug)] pub struct InputHandler { app_data: Arc>, docker: Arc, gui_state: Arc>, is_running: Arc, rec: Receiver, mouse_capture: bool, } impl InputHandler { /// Initialize self, and running the message handling loop pub async fn init( app_data: Arc>, rec: Receiver, docker: Arc, gui_state: Arc>, is_running: Arc, ) { let mut inner = Self { app_data, docker, gui_state, is_running, rec, mouse_capture: true, }; inner.start().await; } /// check for incoming messages async fn start(&mut self) { while let Ok(message) = self.rec.recv().await { match message { InputMessages::ButtonPress(key_code) => self.button_press(key_code).await, InputMessages::MouseEvent(mouse_event) => { let show_error = self.app_data.lock().show_error; let show_info = self.gui_state.lock().show_help; if !show_error && !show_info { self.mouse_press(mouse_event); } } } if !self.is_running.load(Ordering::SeqCst) { break; } } } /// Handle any keyboard button events async fn button_press(&mut self, key_code: KeyCode) { let show_error = self.app_data.lock().show_error; let show_info = self.gui_state.lock().show_help; if show_error { match key_code { KeyCode::Char('q') => { self.is_running.store(false, Ordering::SeqCst); } KeyCode::Char('c') => { self.app_data.lock().show_error = false; self.app_data.lock().remove_error(); } _ => (), } } else if show_info { match key_code { KeyCode::Char('q') => { self.is_running.store(false, Ordering::SeqCst); } KeyCode::Char('h') => { self.gui_state.lock().show_help = false; } _ => (), } } else { match key_code { KeyCode::Char('q') => { self.is_running.store(false, Ordering::SeqCst); } KeyCode::Char('h') => { self.gui_state.lock().show_help = true; } KeyCode::Char('m') => { if self.mouse_capture { match execute!(std::io::stdout(), DisableMouseCapture) { Ok(_) => self .gui_state .lock() .set_info_box("Mouse capture disabled".to_owned()), Err(_) => self .app_data .lock() .set_error(AppError::MouseCapture(false)), } } else { match execute!(std::io::stdout(), EnableMouseCapture) { Ok(_) => self .gui_state .lock() .set_info_box("Mouse capture enabled".to_owned()), Err(_) => self.app_data.lock().set_error(AppError::MouseCapture(true)), } todo!("tokio spawn for x seconds and then reset, probably need to take in an arc clone for self.gui_state") // execute!(stdout, EnableMouseCapture).unwrap(); }; self.mouse_capture = !self.mouse_capture; } KeyCode::Tab => self.gui_state.lock().next_panel(), KeyCode::BackTab => self.gui_state.lock().previous_panel(), KeyCode::Home => { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { SelectablePanel::Containers => locked_data.containers.start(), SelectablePanel::Logs => locked_data.log_start(), SelectablePanel::Commands => locked_data.docker_command_start(), } } KeyCode::End => { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { SelectablePanel::Containers => locked_data.containers.end(), SelectablePanel::Logs => locked_data.log_end(), SelectablePanel::Commands => locked_data.docker_command_end(), } } KeyCode::Up => self.previous(), KeyCode::PageUp => { for _ in 0..=6 { self.previous() } } KeyCode::Down => self.next(), KeyCode::PageDown => { for _ in 0..=6 { self.next() } } KeyCode::Enter => { // Does is matter though? // This isn't great, just means you can't send docker commands before full initialization of the program // could change to to if loading = true, although at the moment don't have a loading bool let panel = self.gui_state.lock().selected_panel; if panel == SelectablePanel::Commands { let command = self.app_data.lock().get_docker_command(); if command.is_some() { let id = self.app_data.lock().get_selected_container_id(); let app_data = Arc::clone(&self.app_data); let docker = Arc::clone(&self.docker); if id.is_some() { let id = id.unwrap(); match command.unwrap() { DockerControls::Pause => { tokio::spawn(async move { docker.pause_container(&id).await.unwrap_or_else( |_| { app_data.lock().set_error( AppError::DockerCommand( DockerControls::Pause, ), ) }, ); }); } DockerControls::Unpause => { tokio::spawn(async move { docker.unpause_container(&id).await.unwrap_or_else( |_| { app_data.lock().set_error( AppError::DockerCommand( DockerControls::Unpause, ), ) }, ); }); } DockerControls::Start => { tokio::spawn(async move { docker .start_container( &id, None::>, ) .await .unwrap_or_else(|_| { app_data.lock().set_error( AppError::DockerCommand( DockerControls::Start, ), ) }); }); } DockerControls::Stop => { tokio::spawn(async move { docker.stop_container(&id, None).await.unwrap_or_else( |_| { app_data.lock().set_error( AppError::DockerCommand( DockerControls::Stop, ), ) }, ); }); } DockerControls::Restart => { tokio::spawn(async move { docker .restart_container(&id, None) .await .unwrap_or_else(|_| { app_data.lock().set_error( AppError::DockerCommand( DockerControls::Restart, ), ) }); }); } } } } } } _ => (), } } } /// Handle mouse button events fn mouse_press(&mut self, mouse_event: MouseEvent) { match mouse_event.kind { MouseEventKind::ScrollUp => self.previous(), MouseEventKind::ScrollDown => self.next(), MouseEventKind::Down(MouseButton::Left) => { self.gui_state.lock().rect_insersects(Rect::new( mouse_event.column, mouse_event.row, 1, 1, )); } _ => (), } } /// Change state of selected container fn next(&mut self) { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { SelectablePanel::Containers => locked_data.containers.next(), SelectablePanel::Logs => locked_data.log_next(), SelectablePanel::Commands => locked_data.docker_command_next(), }; } /// Change state of selected container fn previous(&mut self) { let mut locked_data = self.app_data.lock(); match self.gui_state.lock().selected_panel { SelectablePanel::Containers => locked_data.containers.previous(), SelectablePanel::Logs => locked_data.log_previous(), SelectablePanel::Commands => locked_data.docker_command_previous(), } } }