Merge branch 'feat/mouse_capture' into dev
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@ parking_lot = {version= "0.12.0"}
|
||||
tokio = {version = "1.17.0", features=["full"]}
|
||||
tracing = "0.1.32"
|
||||
tracing-subscriber = "0.3.9"
|
||||
tui = "0.17"
|
||||
tui = "0.18"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
|
||||
@@ -21,14 +21,21 @@
|
||||
</p>
|
||||
|
||||
|
||||
## Download
|
||||
## Download & install
|
||||
|
||||
See <a href="https://github.com/mrjackwills/oxker/releases" target='_blank' rel='noopener noreferrer'>releases</a>
|
||||
|
||||
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 & <a href='https://github.com/cross-rs/cross' target='_blank' re
|
||||
|
||||
#### 32bit pi (pi zero w)
|
||||
|
||||
Tested, and fully working on pi zero w, running Raspberry Pi OS 32 bit, the initial logs parsing can take an extended period of time if thousands of lines long
|
||||
Tested, and fully working on pi zero w, running Raspberry Pi OS 32 bit, the initial logs parsing can take an extended period of time if thousands of lines long, suggest running with a -d argument of 5000
|
||||
|
||||
```cross build --target arm-unknown-linux-musleabihf --release```
|
||||
|
||||
|
||||
@@ -398,12 +398,13 @@ impl ContainerItem {
|
||||
/// Container information panel headings + widths, for nice pretty formatting
|
||||
#[derive(Debug)]
|
||||
pub struct Columns {
|
||||
pub cpu: (String, usize),
|
||||
pub image: (String, usize),
|
||||
pub name: (String, usize),
|
||||
pub state: (String, usize),
|
||||
pub status: (String, usize),
|
||||
pub cpu: (String, usize),
|
||||
pub mem: (String, usize),
|
||||
pub id: (String, usize),
|
||||
pub name: (String, usize),
|
||||
pub image: (String, usize),
|
||||
pub net_rx: (String, usize),
|
||||
pub net_tx: (String, usize),
|
||||
}
|
||||
@@ -411,13 +412,14 @@ pub struct Columns {
|
||||
impl Columns {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
// 7 to allow for 100.00%
|
||||
cpu: (String::from("cpu"), 7),
|
||||
image: (String::from("image"), 5),
|
||||
name: (String::from("name"), 4),
|
||||
state: (String::from("state"), 11),
|
||||
status: (String::from("status"), 16),
|
||||
// 7 to allow for "100.00%"
|
||||
cpu: (String::from("cpu"), 7),
|
||||
mem: (String::from("memory/limit"), 12),
|
||||
id: (String::from("id"), 8),
|
||||
name: (String::from("name"), 4),
|
||||
image: (String::from("image"), 5),
|
||||
net_rx: (String::from("↓ rx"), 5),
|
||||
net_tx: (String::from("↑ tx"), 5),
|
||||
}
|
||||
|
||||
+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)
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ impl DockerData {
|
||||
docker,
|
||||
gui_state,
|
||||
initialised: false,
|
||||
sleep_duration: Duration::from_millis(args.docker as u64),
|
||||
sleep_duration: Duration::from_millis(args.docker_interval as u64),
|
||||
timestamps: args.timestamp,
|
||||
};
|
||||
inner.initialise_container_data().await;
|
||||
|
||||
+53
-14
@@ -4,9 +4,14 @@ 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 tokio::{sync::broadcast::Receiver, task::JoinHandle};
|
||||
use tui::layout::Rect;
|
||||
|
||||
mod message;
|
||||
@@ -25,6 +30,8 @@ pub struct InputHandler {
|
||||
gui_state: Arc<Mutex<GuiState>>,
|
||||
is_running: Arc<AtomicBool>,
|
||||
rec: Receiver<InputMessages>,
|
||||
mouse_capture: bool,
|
||||
info_sleep: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
@@ -42,6 +49,8 @@ impl InputHandler {
|
||||
gui_state,
|
||||
is_running,
|
||||
rec,
|
||||
mouse_capture: true,
|
||||
info_sleep: None,
|
||||
};
|
||||
inner.start().await;
|
||||
}
|
||||
@@ -65,10 +74,46 @@ impl InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn m_button(&mut self) {
|
||||
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)),
|
||||
}
|
||||
};
|
||||
|
||||
let gui_state = Arc::clone(&self.gui_state);
|
||||
|
||||
if self.info_sleep.is_some() {
|
||||
self.info_sleep.as_ref().unwrap().abort()
|
||||
}
|
||||
self.info_sleep = Some(tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(4000)).await;
|
||||
gui_state.lock().reset_info_box()
|
||||
}));
|
||||
|
||||
self.mouse_capture = !self.mouse_capture;
|
||||
}
|
||||
|
||||
/// 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') => {
|
||||
@@ -82,22 +127,16 @@ impl InputHandler {
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
KeyCode::Char('q') => self.is_running.store(false, Ordering::SeqCst),
|
||||
KeyCode::Char('h') => self.gui_state.lock().show_help = false,
|
||||
KeyCode::Char('m') => self.m_button(),
|
||||
_ => (),
|
||||
}
|
||||
} 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('q') => self.is_running.store(false, Ordering::SeqCst),
|
||||
KeyCode::Char('h') => self.gui_state.lock().show_help = true,
|
||||
KeyCode::Char('m') => self.m_button(),
|
||||
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;
|
||||
|
||||
@@ -8,13 +8,16 @@ use tracing::error;
|
||||
|
||||
pub struct CliArgs {
|
||||
/// Docker update interval in ms, minimum 1, reccomended 500+
|
||||
#[clap(short = 'd', default_value_t = 1000)]
|
||||
pub docker: u32,
|
||||
#[clap(short = 'd', value_name = "ms", default_value_t = 1000)]
|
||||
pub docker_interval: u32,
|
||||
|
||||
/// Don't draw gui - for debugging - mostly pointless
|
||||
#[clap(short = 'g')]
|
||||
pub gui: bool,
|
||||
|
||||
// /// Install to ./local/bin
|
||||
// #[clap(short = 'i')]
|
||||
// pub install: bool,
|
||||
/// Remove timestamps from Docker logs
|
||||
#[clap(short = 't')]
|
||||
pub timestamp: bool,
|
||||
@@ -35,15 +38,16 @@ impl CliArgs {
|
||||
|
||||
// Quit the program if the docker update argument is 0
|
||||
// Should maybe change it to check if less than 100
|
||||
if args.docker == 0 {
|
||||
if args.docker_interval == 0 {
|
||||
error!("docker args needs to be greater than 0");
|
||||
process::exit(1)
|
||||
}
|
||||
Self {
|
||||
color: args.color,
|
||||
docker: args.docker,
|
||||
docker_interval: args.docker_interval,
|
||||
gui: !args.gui,
|
||||
raw: args.raw,
|
||||
// install: args.install,
|
||||
timestamp: !args.timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
+111
-44
@@ -19,6 +19,7 @@ use crate::{
|
||||
app_error::AppError,
|
||||
};
|
||||
|
||||
use super::gui_state::BoxLocation;
|
||||
use super::{GuiState, SelectablePanel};
|
||||
|
||||
const NAME_TEXT: &str = r#"
|
||||
@@ -34,17 +35,20 @@ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8
|
||||
const NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const REPO: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
|
||||
const ORANGE: Color = Color::Rgb(255, 178, 36);
|
||||
const MARGIN: &str = " ";
|
||||
|
||||
/// Generate block, add a bored if is the selected panel,
|
||||
/// Generate block, add a border if is the selected panel,
|
||||
/// add custom title based on state of each panel
|
||||
fn generate_block<'a>(
|
||||
selectable_panel: Option<SelectablePanel>,
|
||||
app_data: &Arc<Mutex<AppData>>,
|
||||
selected_panel: &SelectablePanel,
|
||||
) -> Block<'a> {
|
||||
let mut block = Block::default().borders(Borders::ALL);
|
||||
let mut block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded);
|
||||
|
||||
if let Some(panel) = selectable_panel {
|
||||
let title = match panel {
|
||||
@@ -62,11 +66,7 @@ fn generate_block<'a>(
|
||||
};
|
||||
block = block.title(title);
|
||||
if selected_panel == &panel {
|
||||
let selected_style = Style::default().fg(Color::LightCyan);
|
||||
let selected_border = BorderType::Plain;
|
||||
block = block
|
||||
.border_style(selected_style)
|
||||
.border_type(selected_border);
|
||||
block = block.border_style(Style::default().fg(Color::LightCyan));
|
||||
}
|
||||
}
|
||||
block
|
||||
@@ -170,6 +170,15 @@ pub fn draw_containers<B: Backend>(
|
||||
format!("{}{:>width$}", MARGIN, mems, width = widths.mem.1),
|
||||
state_style,
|
||||
),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{}{:>width$}",
|
||||
MARGIN,
|
||||
i.id.chars().take(8).collect::<String>(),
|
||||
width = widths.id.1
|
||||
),
|
||||
blue,
|
||||
),
|
||||
Span::styled(
|
||||
format!("{}{:>width$}", MARGIN, i.name, width = widths.name.1),
|
||||
blue,
|
||||
@@ -330,7 +339,7 @@ fn make_chart<T: Stats + Display>(
|
||||
.add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Plain),
|
||||
.border_type(BorderType::Rounded),
|
||||
)
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
@@ -348,13 +357,12 @@ fn make_chart<T: Stats + Display>(
|
||||
.fg(label_color),
|
||||
),
|
||||
])
|
||||
// add 0.01, for cases when the value is 0
|
||||
.bounds([0.0, max.get_value() + 0.01]),
|
||||
)
|
||||
}
|
||||
|
||||
/// Show error popup over whole screen
|
||||
pub fn draw_info_bar<B: Backend>(
|
||||
pub fn draw_heading_bar<B: Backend>(
|
||||
area: Rect,
|
||||
columns: &Columns,
|
||||
f: &mut Frame<'_, B>,
|
||||
@@ -379,6 +387,8 @@ pub fn draw_info_bar<B: Backend>(
|
||||
.push_str(format!("{}{:>width$}", MARGIN, columns.cpu.0, width = columns.cpu.1).as_str());
|
||||
column_headings
|
||||
.push_str(format!("{}{:>width$}", MARGIN, columns.mem.0, width = columns.mem.1).as_str());
|
||||
column_headings
|
||||
.push_str(format!("{}{:>width$}", MARGIN, columns.id.0, width = columns.id.1).as_str());
|
||||
column_headings.push_str(
|
||||
format!(
|
||||
"{}{:>width$}",
|
||||
@@ -455,18 +465,21 @@ pub fn draw_info_bar<B: Backend>(
|
||||
pub fn draw_help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
||||
let title = format!(" {} ", VERSION);
|
||||
|
||||
let mut description_text =
|
||||
String::from("\n A basic docker container information viewer and controller.");
|
||||
description_text.push_str("\n Tab or Alt+Tab to change panels, arrows to change lines, enter to send docker container commands.");
|
||||
description_text.push_str("\n Mouse input also available.");
|
||||
description_text.push_str("\n ( q ) to quit at any time.");
|
||||
description_text
|
||||
.push_str("\n\n currenty an early work in progress, all and any input appreciated");
|
||||
description_text.push_str(format!("\n {}", REPO.trim()).as_str());
|
||||
let description_text = format!("\n{}", DESCRIPTION);
|
||||
|
||||
let mut help_text = String::from("\n ( tab ) or ( alt+tab ) to change panels");
|
||||
help_text.push_str("\n ( ↑ ↓ ← → ) to change selected line");
|
||||
help_text.push_str("\n ( enter ) to send docker container commands");
|
||||
help_text.push_str("\n ( h ) to toggle this help information");
|
||||
help_text.push_str("\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied");
|
||||
help_text.push_str("\n ( q ) to quit at any time");
|
||||
help_text.push_str("\n mouse scrolling & clicking also available");
|
||||
help_text.push_str("\n\n currenty an early work in progress, all and any input appreciated");
|
||||
help_text.push_str(format!("\n {}", REPO.trim()).as_str());
|
||||
|
||||
let mut max_line_width = 0;
|
||||
|
||||
let all_text = format!("{}{}", NAME_TEXT, description_text);
|
||||
let all_text = format!("{}{}{}", NAME_TEXT, description_text, help_text);
|
||||
|
||||
all_text.lines().into_iter().for_each(|line| {
|
||||
let width = line.chars().count();
|
||||
@@ -486,7 +499,12 @@ pub fn draw_help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
let description_paragraph = Paragraph::new(description_text.as_str())
|
||||
let description_paragrpah = Paragraph::new(description_text.as_str())
|
||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Center);
|
||||
|
||||
let help_paragraph = Paragraph::new(help_text.as_str())
|
||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Left);
|
||||
@@ -497,7 +515,12 @@ 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)
|
||||
@@ -505,6 +528,7 @@ pub fn draw_help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
||||
[
|
||||
Constraint::Max(NAME_TEXT.lines().count() as u16),
|
||||
Constraint::Max(description_text.lines().count() as u16),
|
||||
Constraint::Max(help_text.lines().count() as u16),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
@@ -513,7 +537,8 @@ pub fn draw_help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
||||
// Order is important here
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(name_paragraph, split_popup[0]);
|
||||
f.render_widget(description_paragraph, split_popup[1]);
|
||||
f.render_widget(description_paragrpah, split_popup[1]);
|
||||
f.render_widget(help_paragraph, split_popup[2]);
|
||||
f.render_widget(block, area);
|
||||
}
|
||||
|
||||
@@ -560,38 +585,80 @@ 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);
|
||||
|
||||
|
||||
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 += 2;
|
||||
|
||||
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 {
|
||||
// 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;
|
||||
fn draw_popup(text_lines: u16, text_width: u16, r: Rect, box_location: BoxLocation) -> Rect {
|
||||
// Make sure blank_space can't be an negative, as will crash
|
||||
let blank_vertical = if r.height > text_lines {
|
||||
(r.height - text_lines) / 2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let blank_horizontal = if r.width > text_width {
|
||||
(r.width - text_width) / 2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
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(),
|
||||
)
|
||||
.constraints(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(),
|
||||
)
|
||||
.split(popup_layout[1])[1]
|
||||
.constraints(horizontal_constraints)
|
||||
.split(popup_layout[indexes.0])[indexes.1]
|
||||
}
|
||||
|
||||
+99
-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,85 @@ 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 +112,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 +159,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 +168,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 +180,8 @@ impl GuiState {
|
||||
loading: Loading::One,
|
||||
selected_panel: SelectablePanel::Containers,
|
||||
show_help: false,
|
||||
// show_info_panel: false,
|
||||
info_box_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,4 +229,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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -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)
|
||||
@@ -190,7 +192,7 @@ fn ui<B: Backend>(
|
||||
&selected_panel,
|
||||
);
|
||||
|
||||
draw_info_bar(
|
||||
draw_heading_bar(
|
||||
whole_layout[0],
|
||||
&column_widths,
|
||||
f,
|
||||
@@ -203,6 +205,10 @@ fn ui<B: Backend>(
|
||||
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