feat: refactored centered box to diplay in any of 9 areas
This commit is contained in:
+6
-19
@@ -1,7 +1,5 @@
|
||||
use core::fmt;
|
||||
use tracing::error;
|
||||
|
||||
use crate::app_data::DockerControls;
|
||||
use core::fmt;
|
||||
|
||||
/// app errors to set in global state
|
||||
#[allow(unused)]
|
||||
@@ -11,25 +9,10 @@ pub enum AppError {
|
||||
DockerInterval,
|
||||
InputPoll,
|
||||
DockerCommand(DockerControls),
|
||||
MouseCapture(bool),
|
||||
Terminal,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
/// for handling errors from terminal
|
||||
pub fn disp(&self) {
|
||||
match self {
|
||||
Self::DockerConnect => 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)
|
||||
}
|
||||
|
||||
@@ -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<Mutex<GuiState>>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
rec: Receiver<InputMessages>,
|
||||
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 => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use app_data::AppData;
|
||||
use app_error::AppError;
|
||||
use bollard::Docker;
|
||||
|
||||
+49
-19
@@ -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<B: Backend>(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<B: Backend>(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<B: Backend>(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]
|
||||
}
|
||||
+92
-14
@@ -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<Mutex>
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -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<B: Backend>(
|
||||
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<B: Backend>(
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user