From 1017850a6cc91328abc1127bdb117495f5e909d8 Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Thu, 28 Apr 2022 21:25:27 +0000 Subject: [PATCH] feat: refactored centered box to diplay in any of 9 areas --- README.md | 13 +++-- src/app_error.rs | 25 +++------ src/input_handler/mod.rs | 35 ++++++++++++- src/main.rs | 2 + src/ui/draw_blocks.rs | 68 ++++++++++++++++++------- src/ui/gui_state.rs | 106 +++++++++++++++++++++++++++++++++------ src/ui/mod.rs | 10 +++- 7 files changed, 202 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ae524e6..4610130 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,21 @@

-## Download +## Download & install See releases +install +```bash +INSTALL_DIR="${HOME}/.local/bin" +tar xzvf oxker_linux_x86_64.tar.gz oxker +install -Dm 755 oxker -t "$INSTALL_DIR" +rm oxker_linux_x86_64.tar.gz oxker +``` ## Run -```./oxker``` +```oxker``` available command line arguments | argument|result| @@ -56,7 +63,7 @@ requires docker & error!("Unable to access docker daemon"), - Self::DockerInterval => error!("Docker update interval needs to be greater than 0"), - Self::InputPoll => error!("Unable to poll user input"), - Self::Terminal => error!("Unable to draw to terminal"), - Self::DockerCommand(s) => { - let error = format!("Unable to {} container", s); - error!(%error); - } - } - } -} - /// Convert errors into strings to display impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -39,6 +22,10 @@ impl fmt::Display for AppError { Self::InputPoll => "Unable to poll user input".to_owned(), Self::Terminal => "Unable to draw to terminal".to_owned(), Self::DockerCommand(s) => format!("Unable to {} container", s), + Self::MouseCapture(x) => { + let reason = if *x { "en" } else { "dis" }; + format!("Unable to {}able mouse capture", reason) + } }; write!(f, "{}", disp) } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 3e14fb5..610c0f5 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -4,7 +4,12 @@ use std::sync::{ }; use bollard::{container::StartContainerOptions, Docker}; -use crossterm::event::{KeyCode, MouseButton, MouseEvent, MouseEventKind}; +use crossterm::{ + event::{ + DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind, + }, + execute, +}; use parking_lot::Mutex; use tokio::sync::broadcast::Receiver; use tui::layout::Rect; @@ -25,6 +30,7 @@ pub struct InputHandler { gui_state: Arc>, is_running: Arc, rec: Receiver, + mouse_capture: bool, } impl InputHandler { @@ -42,6 +48,7 @@ impl InputHandler { gui_state, is_running, rec, + mouse_capture: true, }; inner.start().await; } @@ -69,6 +76,7 @@ impl InputHandler { 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') => { @@ -98,6 +106,31 @@ impl InputHandler { 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 => { diff --git a/src/main.rs b/src/main.rs index 97a3e50..112118a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use app_data::AppData; use app_error::AppError; use bollard::Docker; diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs index f3ac6c9..28830b0 100644 --- a/src/ui/draw_blocks.rs +++ b/src/ui/draw_blocks.rs @@ -19,6 +19,7 @@ use crate::{ app_error::AppError, }; +use super::gui_state::BoxLocation; use super::{GuiState, SelectablePanel}; const NAME_TEXT: &str = r#" @@ -497,7 +498,7 @@ pub fn draw_help_box(f: &mut Frame<'_, B>) { .border_type(BorderType::Rounded) .border_style(Style::default().fg(Color::Black)); - let area = centered_info(lines as u16, max_line_width as u16, f.size()); + let area = draw_popup(lines as u16, max_line_width as u16, f.size(), BoxLocation::MiddleCentre); let split_popup = Layout::default() .direction(Direction::Vertical) @@ -560,38 +561,67 @@ pub fn draw_error(f: &mut Frame<'_, B>, error: AppError, seconds: Op .block(block) .alignment(Alignment::Center); - let area = centered_info(lines as u16, max_line_width as u16, f.size()); + let area = draw_popup(lines as u16, max_line_width as u16, f.size(), BoxLocation::MiddleCentre); + f.render_widget(Clear, area); + f.render_widget(paragraph, area); +} + +/// Show info box in bottom right corner +pub fn draw_info(f: &mut Frame<'_, B>, text: String) { + let block = Block::default() + .title("") + .title_alignment(Alignment::Center) + .borders(Borders::NONE); + + // Add a blank line, so that the text is verticall centered + let text = format!("\n{}", text); + + let mut max_line_width = 0; + text.lines().into_iter().for_each(|line| { + let width = line.chars().count(); + if width > max_line_width { + max_line_width = width; + } + }); + + let mut lines = text.lines().count(); + + // Add some horizontal & vertical margins + max_line_width += 8; + lines += 3; + + let paragraph = Paragraph::new(text) + .style(Style::default().bg(Color::Blue).fg(Color::White)) + .block(block) + .alignment(Alignment::Center); + + let area = draw_popup(lines as u16, max_line_width as u16, f.size(), BoxLocation::BottomRight); f.render_widget(Clear, area); f.render_widget(paragraph, area); } /// draw a box in the center of the screen, based on max line width + number of lines -fn centered_info(number_lines: u16, max_line_width: u16, r: Rect) -> Rect { +fn draw_popup(text_lines: u16, text_width: u16, r: Rect, box_location:BoxLocation) -> Rect { // This can panic if number_lines or max_line_width is larger than r.height or r.width - let blank_vertical = (r.height - number_lines) / 2; - let blank_horizontal = (r.width - max_line_width) / 2; + let blank_vertical = (r.height - text_lines) / 2; + let blank_horizontal = (r.width - text_width) / 2; + + let vertical_constraints = box_location.get_vertical_constraints(blank_vertical, text_lines); + let horizontal_constraints = box_location.get_horizontal_constraints(blank_horizontal, text_width); + + let indexes = box_location.get_indexes(); let popup_layout = Layout::default() .direction(Direction::Vertical) .constraints( - [ - Constraint::Max(blank_vertical), - Constraint::Max(number_lines), - Constraint::Max(blank_vertical), - ] - .as_ref(), + vertical_constraints ) .split(r); Layout::default() .direction(Direction::Horizontal) .constraints( - [ - Constraint::Max(blank_horizontal), - Constraint::Max(max_line_width), - Constraint::Max(blank_horizontal), - ] - .as_ref(), + horizontal_constraints ) - .split(popup_layout[1])[1] -} + .split(popup_layout[indexes.0])[indexes.1] +} \ No newline at end of file diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index eb53924..7ee205e 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -1,5 +1,5 @@ use std::{collections::HashMap, fmt}; -use tui::layout::Rect; +use tui::layout::{Constraint, Rect}; #[derive(Debug, PartialEq, std::hash::Hash, std::cmp::Eq, Clone, Copy)] pub enum SelectablePanel { @@ -7,7 +7,78 @@ pub enum SelectablePanel { Commands, Logs, } -#[derive(Debug)] + +#[derive(Debug, Clone, Copy)] +pub enum BoxLocation { + TopLeft, + TopCentre, + TopRight, + MiddleLeft, + MiddleCentre, + MiddleRight, + BottomLeft, + BottomCentre, + BottomRight, +} + +impl BoxLocation { + pub fn get_indexes(&self) -> (usize, usize) { + match self { + Self::TopLeft => (0, 0), + Self::TopCentre => (0, 1), + Self::TopRight => (0, 2), + Self::MiddleLeft => (1, 0), + Self::MiddleCentre => (1, 1), + Self::MiddleRight => (1, 2), + Self::BottomLeft => (2, 0), + Self::BottomCentre => (2, 1), + Self::BottomRight => (2, 2), + } + } + + // Should combine and just return a tupple? + pub fn get_horizontal_constraints(&self, blank_vertical: u16, text_width: u16) -> [Constraint; 3] { + match self { + Self::TopLeft | Self::MiddleLeft | Self::BottomLeft => [ + Constraint::Max(text_width), + Constraint::Max(blank_vertical), + Constraint::Max(blank_vertical), + ], + Self::TopCentre | Self::MiddleCentre | Self::BottomCentre => [ + Constraint::Max(blank_vertical), + Constraint::Max(text_width), + Constraint::Max(blank_vertical), + ], + Self::TopRight | Self::MiddleRight | Self::BottomRight => [ + Constraint::Max(blank_vertical), + Constraint::Max(blank_vertical), + Constraint::Max(text_width), + ], + } + } + pub fn get_vertical_constraints(&self, blank_vertical: u16, number_lines: u16) -> [Constraint; 3] { + match self { + Self::TopLeft | Self::TopCentre | Self::TopRight => [ + Constraint::Max(number_lines), + Constraint::Max(blank_vertical), + Constraint::Max(blank_vertical), + ], + Self::MiddleLeft | Self::MiddleCentre | Self::MiddleRight => [ + Constraint::Max(blank_vertical), + Constraint::Max(number_lines), + Constraint::Max(blank_vertical), + ], + Self::BottomLeft | Self::BottomCentre | Self::BottomRight => [ + Constraint::Max(blank_vertical), + Constraint::Max(blank_vertical), + Constraint::Max(number_lines), + ], + } + } + +} + +#[derive(Debug, Clone)] pub enum Loading { One, Two, @@ -34,20 +105,9 @@ impl Loading { Self::Eight => Self::Nine, Self::Nine => Self::Ten, Self::Ten => Self::One, - // Self::Five => Self::One } } } -// "⠋", -// "⠙", -// "⠹", -// "⠸", -// "⠼", -// "⠴", -// "⠦", -// "⠧", -// "⠇", -// "⠏" impl fmt::Display for Loading { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -92,7 +152,7 @@ impl SelectablePanel { } /// Global gui_state, stored in an Arc -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct GuiState { // Think this should be a BMapTree, so can define order when iterating over potential intersects // Is an issue if two panels are in the same space, sush as a smaller panel embedded, yet infront of, a larger panel @@ -101,6 +161,8 @@ pub struct GuiState { loading: Loading, pub selected_panel: SelectablePanel, pub show_help: bool, + // show_info_panel: bool, + pub info_box_text: Option, } impl GuiState { @@ -111,6 +173,8 @@ impl GuiState { loading: Loading::One, selected_panel: SelectablePanel::Containers, show_help: false, + // show_info_panel: false, + info_box_text: None, } } @@ -158,4 +222,18 @@ impl GuiState { pub fn reset_loading(&mut self) { self.loading = Loading::One; } + + pub fn set_info_box(&mut self, text: String) { + self.info_box_text = Some(text); + // self.show_info_panel = true; + + // Should spawn and after 10 seconds close? + // Need to copy whatever we're doing with parsing logs icon + } + + pub fn reset_info_box(&mut self) { + // self.loading = Loading::One; + self.info_box_text = None; + // self.show_info_panel = false; + } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 002175a..65f3291 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -11,6 +11,7 @@ use std::{ sync::{atomic::Ordering, Arc}, }; use tokio::sync::broadcast::Sender; +use tracing::error; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout}, @@ -50,7 +51,7 @@ pub async fn create_ui( terminal.show_cursor().unwrap(); if let Err(err) = res { - err.disp() + error!(%err); } Ok(()) } @@ -126,6 +127,7 @@ fn ui( let log_index = app_data.lock().get_selected_log_index(); let selected_panel = gui_state.lock().selected_panel; let show_help = gui_state.lock().show_help; + let info_text = gui_state.lock().info_box_text.clone(); let whole_layout = Layout::default() .direction(Direction::Vertical) @@ -198,11 +200,17 @@ fn ui( show_help, ); + + // only draw charts if there are containers if has_containers { draw_chart(f, lower_main[1], app_data, log_index); } + if let Some(info) = info_text { + draw_info(f, info); + } + // Check if error, and show popup if so if show_help { draw_help_box(f);