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);