Merge branch 'fix/mouse_capture' into dev
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
"seccomp=unconfined"
|
"seccomp=unconfined"
|
||||||
],
|
],
|
||||||
|
|
||||||
"postCreateCommand": "cargo install cross typos-cli",
|
"postCreateCommand": "rustup target add x86_64-unknown-linux-musl && cargo install cross typos-cli",
|
||||||
|
|
||||||
"mounts": [
|
"mounts": [
|
||||||
"source=/etc/timezone,target=/etc/timezone,type=bind,readonly"
|
"source=/etc/timezone,target=/etc/timezone,type=bind,readonly"
|
||||||
|
|||||||
@@ -1,3 +1,23 @@
|
|||||||
|
### Chores
|
||||||
|
+ dependencies updated, [aac3ef2b1def3345d749d813d9b76020d6b5e5ca], [4723be7fb2eb101024bb9d5a514e2c6cc51eb6f6], [c69ab4f7c3b873f25ea46958add37be78d23e9cf], [ba6437862dae0f422660a602aeabd6217d023fac]
|
||||||
|
+ dev container install x86 musl toolchain, [e650034d50f01a7598876d4f2887df691700e06a]
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
+ typos removed, [23ad9a5fb3cacf3fb8cb70c65ca9133ed9949e45], [cebb975cb82f653407ec801fd8c726ca6ed68289], [fdc67c9249a239bac97a78b20c9378472865209c]
|
||||||
|
+ comments improved, [ec962295a8789ff8010604e974969bf618ea7108]
|
||||||
|
|
||||||
|
### Features
|
||||||
|
+ Mouse capture is now more specific, should have substantial performance impact, two to four time reduction in cpu usage when mouse is moved, especially on low end machines (i.e. raspberry pi), as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03]
|
||||||
|
+ Improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd]
|
||||||
|
+ use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d]
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
+ Dead code removed, [b8f5792d1865d3a398cd7f23aa9473a55dc6ea44]
|
||||||
|
+ improve the get_width function, [04c26fe8fc7c79506921b9cff42825b1ee132737]
|
||||||
|
+ Place ui methods into a Ui struct, [3437df59884f084624031fceb34ea3012a8e2251]
|
||||||
|
+ get_horizotal/vertical constraints into single method, [e8f5cf9c6f8cd5f807a05fb61e31d7cd1426486f]
|
||||||
|
+ docker update_everything variables, [074cb957f274675a468f08fecb1c43ff7453217d]
|
||||||
|
|
||||||
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.2.3'>v0.2.3</a>
|
# <a href='https://github.com/mrjackwills/oxker/releases/tag/v0.2.3'>v0.2.3</a>
|
||||||
### 2023-02-04
|
### 2023-02-04
|
||||||
|
|
||||||
|
|||||||
Generated
+14
-29
@@ -131,9 +131,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.1.6"
|
version = "4.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
|
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -148,9 +148,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.1.0"
|
version = "4.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
|
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
@@ -370,9 +370,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.15"
|
version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -543,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -555,7 +555,7 @@ dependencies = [
|
|||||||
"hermit-abi 0.3.1",
|
"hermit-abi 0.3.1",
|
||||||
"io-lifetimes",
|
"io-lifetimes",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -634,7 +634,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -732,7 +732,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -871,7 +871,7 @@ dependencies = [
|
|||||||
"io-lifetimes",
|
"io-lifetimes",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1125,9 +1125,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.25.0"
|
version = "1.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
|
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1140,7 +1140,7 @@ dependencies = [
|
|||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.42.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1427,21 +1427,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.42.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm",
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_gnullvm",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ COPY ./target/x86_64-unknown-linux-musl/release/oxker /app/
|
|||||||
ENTRYPOINT [ "/app/oxker"]
|
ENTRYPOINT [ "/app/oxker"]
|
||||||
|
|
||||||
# Dev build for testing
|
# Dev build for testing
|
||||||
# docker build -t oxker_dev -f Dockerfile . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev
|
# docker build -t oxker_dev -f containerised/Dockerfile_dev . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev
|
||||||
|
|
||||||
# Dev build one liner, x86 host
|
# Dev build one liner, x86 host
|
||||||
# docker image prune -a; cargo build --release --target x86_64-unknown-linux-musl && docker build -t oxker_dev -f containerised/Dockerfile_dev . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev
|
# docker image prune -a; cargo build --release --target x86_64-unknown-linux-musl && docker build -t oxker_dev -f containerised/Dockerfile_dev . && docker run --rm -it --volume /var/run/docker.sock:/var/run/docker.sock:ro oxker_dev
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ impl State {
|
|||||||
_ => Color::Red,
|
_ => Color::Red,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dirty way to create order for the state, rather than impl Ord
|
/// Dirty way to create order for the state, rather than impl Ord
|
||||||
pub const fn order(self) -> u8 {
|
pub const fn order(self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
Self::Running => 0,
|
Self::Running => 0,
|
||||||
|
|||||||
+11
-38
@@ -203,7 +203,7 @@ impl AppData {
|
|||||||
self.containers.start();
|
self.containers.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// select the last container
|
/// select the last container
|
||||||
pub fn containers_end(&mut self) {
|
pub fn containers_end(&mut self) {
|
||||||
self.containers.end();
|
self.containers.end();
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ impl AppData {
|
|||||||
self.containers.next();
|
self.containers.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// select the previous container
|
/// select the previous container
|
||||||
pub fn containers_previous(&mut self) {
|
pub fn containers_previous(&mut self) {
|
||||||
self.containers.previous();
|
self.containers.previous();
|
||||||
}
|
}
|
||||||
@@ -428,12 +428,6 @@ impl AppData {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let rx_count = count(&container.rx.to_string());
|
|
||||||
let tx_count = count(&container.tx.to_string());
|
|
||||||
let image_count = count(&container.image);
|
|
||||||
let name_count = count(&container.name);
|
|
||||||
let state_count = count(&container.state.to_string());
|
|
||||||
let status_count = count(&container.status);
|
|
||||||
let mem_current_count = count(
|
let mem_current_count = count(
|
||||||
&container
|
&container
|
||||||
.mem_stats
|
.mem_stats
|
||||||
@@ -441,35 +435,16 @@ impl AppData {
|
|||||||
.unwrap_or(&ByteStats::default())
|
.unwrap_or(&ByteStats::default())
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
let mem_limit_count = count(&container.mem_limit.to_string());
|
|
||||||
|
|
||||||
if cpu_count > columns.cpu.1 {
|
columns.cpu.1 = columns.cpu.1.max(cpu_count);
|
||||||
columns.cpu.1 = cpu_count;
|
columns.image.1 = columns.image.1.max(count(&container.image));
|
||||||
};
|
columns.mem.1 = columns.mem.1.max(mem_current_count);
|
||||||
if image_count > columns.image.1 {
|
columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string()));
|
||||||
columns.image.1 = image_count;
|
columns.name.1 = columns.name.1.max(count(&container.name));
|
||||||
};
|
columns.net_rx.1 = columns.net_rx.1.max(count(&container.rx.to_string()));
|
||||||
if mem_current_count > columns.mem.1 {
|
columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string()));
|
||||||
columns.mem.1 = mem_current_count;
|
columns.state.1 = columns.state.1.max(count(&container.state.to_string()));
|
||||||
};
|
columns.status.1 = columns.status.1.max(count(&container.status));
|
||||||
if mem_limit_count > columns.mem.2 {
|
|
||||||
columns.mem.2 = mem_limit_count;
|
|
||||||
};
|
|
||||||
if name_count > columns.name.1 {
|
|
||||||
columns.name.1 = name_count;
|
|
||||||
};
|
|
||||||
if state_count > columns.state.1 {
|
|
||||||
columns.state.1 = state_count;
|
|
||||||
};
|
|
||||||
if status_count > columns.status.1 {
|
|
||||||
columns.status.1 = status_count;
|
|
||||||
};
|
|
||||||
if rx_count > columns.net_rx.1 {
|
|
||||||
columns.net_rx.1 = rx_count;
|
|
||||||
};
|
|
||||||
if tx_count > columns.net_tx.1 {
|
|
||||||
columns.net_tx.1 = tx_count;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
columns
|
columns
|
||||||
}
|
}
|
||||||
@@ -519,9 +494,7 @@ impl AppData {
|
|||||||
container.mem_limit.update(mem_limit);
|
container.mem_limit.update(mem_limit);
|
||||||
}
|
}
|
||||||
// need to benchmark this?
|
// need to benchmark this?
|
||||||
// if self.get_sorted().is_some() {
|
|
||||||
self.sort_containers();
|
self.sort_containers();
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update, or insert, containers
|
/// Update, or insert, containers
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ use futures_util::StreamExt;
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{
|
sync::{atomic::AtomicBool, Arc},
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use tokio::{sync::mpsc::Receiver, task::JoinHandle};
|
use tokio::{sync::mpsc::Receiver, task::JoinHandle};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -407,7 +404,8 @@ impl DockerData {
|
|||||||
.values()
|
.values()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(tokio::task::JoinHandle::abort);
|
.for_each(tokio::task::JoinHandle::abort);
|
||||||
self.is_running.store(false, Ordering::SeqCst);
|
self.is_running
|
||||||
|
.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,7 +434,6 @@ impl DockerData {
|
|||||||
spawns: Arc::new(Mutex::new(HashMap::new())),
|
spawns: Arc::new(Mutex::new(HashMap::new())),
|
||||||
};
|
};
|
||||||
inner.initialise_container_data().await;
|
inner.initialise_container_data().await;
|
||||||
|
|
||||||
inner.message_handler().await;
|
inner.message_handler().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ use std::sync::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{
|
event::{DisableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind},
|
||||||
DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind,
|
|
||||||
},
|
|
||||||
execute,
|
execute,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@@ -21,7 +19,7 @@ use crate::{
|
|||||||
app_data::{AppData, DockerControls, Header},
|
app_data::{AppData, DockerControls, Header},
|
||||||
app_error::AppError,
|
app_error::AppError,
|
||||||
docker_data::DockerMessage,
|
docker_data::DockerMessage,
|
||||||
ui::{GuiState, SelectablePanel, Status},
|
ui::{GuiState, SelectablePanel, Status, Ui},
|
||||||
};
|
};
|
||||||
pub use message::InputMessages;
|
pub use message::InputMessages;
|
||||||
|
|
||||||
@@ -94,15 +92,10 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match execute!(std::io::stdout(), EnableMouseCapture) {
|
Ui::enable_mouse_capture();
|
||||||
Ok(_) => self
|
self.gui_state
|
||||||
.gui_state
|
|
||||||
.lock()
|
.lock()
|
||||||
.set_info_box("✓ mouse capture enabled".to_owned()),
|
.set_info_box("✓ mouse capture enabled".to_owned());
|
||||||
Err(_) => {
|
|
||||||
self.app_data.lock().set_error(AppError::MouseCapture(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the info box sleep handle is currently being executed, as in 'm' is pressed twice within a 4000ms window
|
// If the info box sleep handle is currently being executed, as in 'm' is pressed twice within a 4000ms window
|
||||||
@@ -134,7 +127,8 @@ impl InputHandler {
|
|||||||
.lock()
|
.lock()
|
||||||
.status_contains(&[Status::Error, Status::Init]);
|
.status_contains(&[Status::Error, Status::Init]);
|
||||||
if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() {
|
if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() {
|
||||||
self.is_running.store(false, Ordering::SeqCst);
|
self.is_running
|
||||||
|
.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-7
@@ -23,7 +23,7 @@ use docker_data::DockerData;
|
|||||||
use input_handler::InputMessages;
|
use input_handler::InputMessages;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use parse_args::CliArgs;
|
use parse_args::CliArgs;
|
||||||
use std::{sync::{atomic::AtomicBool, Arc}, io::Write};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{info, Level};
|
use tracing::{info, Level};
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ mod input_handler;
|
|||||||
mod parse_args;
|
mod parse_args;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use ui::{create_ui, GuiState, Status};
|
use ui::{GuiState, Status, Ui};
|
||||||
|
|
||||||
use crate::docker_data::DockerMessage;
|
use crate::docker_data::DockerMessage;
|
||||||
|
|
||||||
@@ -132,9 +132,7 @@ async fn main() {
|
|||||||
handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running);
|
handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running);
|
||||||
|
|
||||||
if args.gui {
|
if args.gui {
|
||||||
create_ui(app_data, docker_sx, gui_state, is_running, input_sx)
|
Ui::create(app_data, docker_sx, gui_state, is_running, input_sx).await;
|
||||||
.await
|
|
||||||
.unwrap_or(());
|
|
||||||
} else {
|
} else {
|
||||||
// Debug mode for testing, mostly pointless, doesn't take terminal
|
// Debug mode for testing, mostly pointless, doesn't take terminal
|
||||||
info!("in debug mode");
|
info!("in debug mode");
|
||||||
@@ -146,6 +144,4 @@ async fn main() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear screen
|
|
||||||
std::io::stdout().flush().unwrap_or(());
|
|
||||||
}
|
}
|
||||||
|
|||||||
+225
-53
@@ -481,93 +481,253 @@ pub fn heading_bar<B: Backend>(
|
|||||||
|
|
||||||
/// From a given &str, return the maximum number of chars on a single line
|
/// From a given &str, return the maximum number of chars on a single line
|
||||||
fn max_line_width(text: &str) -> usize {
|
fn max_line_width(text: &str) -> usize {
|
||||||
let mut max_line_width = 0;
|
text.lines()
|
||||||
text.lines().into_iter().for_each(|line| {
|
.into_iter()
|
||||||
let width = line.chars().count();
|
.map(|i| i.chars().count())
|
||||||
if width > max_line_width {
|
.max()
|
||||||
max_line_width = width;
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Help popup box needs these three pieces of information
|
||||||
|
struct HelpInfo {
|
||||||
|
spans: Vec<Spans<'static>>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HelpInfo {
|
||||||
|
/// Find the max width of a Span in &[Spans], although it isn't calculating it correctly
|
||||||
|
fn calc_width(spans: &[Spans]) -> usize {
|
||||||
|
spans
|
||||||
|
.iter()
|
||||||
|
.flat_map(|x| x.0.iter())
|
||||||
|
.map(tui::text::Span::width)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Just an empty span, i.e. a new line
|
||||||
|
fn empty_span<'a>() -> Spans<'a> {
|
||||||
|
Spans::from(String::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generate a span, of given &str and given color
|
||||||
|
fn span<'a>(input: &str, color: Color) -> Span<'a> {
|
||||||
|
Span::styled(input.to_owned(), Style::default().fg(color))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Span to black text span
|
||||||
|
fn black_span<'a>(input: &str) -> Span<'a> {
|
||||||
|
Self::span(input, Color::Black)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Span to white text span
|
||||||
|
fn white_span<'a>(input: &str) -> Span<'a> {
|
||||||
|
Self::span(input, Color::White)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the `oxker` name span + metadata
|
||||||
|
fn gen_name() -> Self {
|
||||||
|
let mut spans = NAME_TEXT
|
||||||
|
.lines()
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| Spans::from(Self::white_span(i)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
spans.insert(0, Self::empty_span());
|
||||||
|
let width = Self::calc_width(&spans);
|
||||||
|
let height = spans.len();
|
||||||
|
Self {
|
||||||
|
spans,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the description span + metadata
|
||||||
|
fn gen_description() -> Self {
|
||||||
|
let spans = [
|
||||||
|
Self::empty_span(),
|
||||||
|
Spans::from(Self::white_span(DESCRIPTION)),
|
||||||
|
Self::empty_span(),
|
||||||
|
];
|
||||||
|
let width = Self::calc_width(&spans);
|
||||||
|
let height = spans.len();
|
||||||
|
Self {
|
||||||
|
spans: spans.to_vec(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the button information span + metadata
|
||||||
|
fn gen_button() -> Self {
|
||||||
|
let button_item = |x: &str| Self::white_span(&format!(" {x} "));
|
||||||
|
let button_desc = |x: &str| Self::black_span(x);
|
||||||
|
let or = || button_desc("or");
|
||||||
|
let space = || button_desc(" ");
|
||||||
|
|
||||||
|
let spans = [
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( tab )"),
|
||||||
|
or(),
|
||||||
|
button_item("( shift+tab )"),
|
||||||
|
button_desc("to change panels"),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( ↑ ↓ )"),
|
||||||
|
or(),
|
||||||
|
button_item("( j k )"),
|
||||||
|
or(),
|
||||||
|
button_item("( PgUp PgDown )"),
|
||||||
|
or(),
|
||||||
|
button_item("( Home End )"),
|
||||||
|
button_desc("to change selected line"),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( enter )"),
|
||||||
|
button_desc("to send docker container command"),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( h )"),
|
||||||
|
button_desc("to toggle this help information"),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( 0 )"),
|
||||||
|
button_desc("to stop sort"),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( 1 - 9 )"),
|
||||||
|
button_desc("sort by header - or click header"),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( m )"),
|
||||||
|
button_desc(
|
||||||
|
"to toggle mouse capture - if disabled, text on screen can be selected & copied",
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
Spans::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("( q )"),
|
||||||
|
button_desc("to quit at any time"),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let height = spans.len();
|
||||||
|
let width = Self::calc_width(&spans);
|
||||||
|
Self {
|
||||||
|
spans: spans.to_vec(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the final lines, GitHub link etc, + metadata
|
||||||
|
fn gen_final() -> Self {
|
||||||
|
let spans = [
|
||||||
|
Self::empty_span(),
|
||||||
|
Spans::from(vec![Self::black_span(
|
||||||
|
"currently an early work in progress, all and any input appreciated",
|
||||||
|
)]),
|
||||||
|
Spans::from(vec![Span::styled(
|
||||||
|
REPO.to_owned(),
|
||||||
|
Style::default()
|
||||||
|
.bg(Color::Magenta)
|
||||||
|
.fg(Color::Black)
|
||||||
|
.add_modifier(Modifier::UNDERLINED),
|
||||||
|
)]),
|
||||||
|
];
|
||||||
|
let height = spans.len();
|
||||||
|
let width = Self::calc_width(&spans);
|
||||||
|
Self {
|
||||||
|
spans: spans.to_vec(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
max_line_width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the help box in the centre of the screen
|
/// Draw the help box in the centre of the screen
|
||||||
/// TODO should make every line it's own renderable span
|
|
||||||
pub fn help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
pub fn help_box<B: Backend>(f: &mut Frame<'_, B>) {
|
||||||
let title = format!(" {VERSION} ");
|
let title = format!(" {VERSION} ");
|
||||||
|
|
||||||
let description_text = format!("\n{DESCRIPTION}");
|
let name_info = HelpInfo::gen_name();
|
||||||
|
let description_info = HelpInfo::gen_description();
|
||||||
|
let button_info = HelpInfo::gen_button();
|
||||||
|
let final_info = HelpInfo::gen_final();
|
||||||
|
|
||||||
let mut help_text = String::from("\n ( tab ) or ( shift+tab ) to change panels");
|
// have to add 10, but shouldn't need to, is an error somewhere
|
||||||
help_text
|
let max_line_width = [
|
||||||
.push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line");
|
name_info.width,
|
||||||
help_text.push_str("\n ( enter ) to send docker container commands");
|
description_info.width,
|
||||||
help_text.push_str("\n ( h ) to toggle this help information");
|
button_info.width,
|
||||||
help_text.push_str("\n ( 0 ) stop sort");
|
final_info.width,
|
||||||
help_text.push_str("\n ( 1 - 9 ) sort by header - or click header");
|
]
|
||||||
help_text.push_str(
|
.into_iter()
|
||||||
"\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied",
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
+ 10;
|
||||||
|
let max_height =
|
||||||
|
name_info.height + description_info.height + button_info.height + final_info.height + 2;
|
||||||
|
|
||||||
|
let area = popup(
|
||||||
|
max_height,
|
||||||
|
max_line_width,
|
||||||
|
f.size(),
|
||||||
|
BoxLocation::MiddleCentre,
|
||||||
);
|
);
|
||||||
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 currently an early work in progress, all and any input appreciated");
|
|
||||||
help_text.push_str(format!("\n {}", REPO.trim()).as_str());
|
|
||||||
|
|
||||||
// Find the maximum line widths & height
|
let split_popup = Layout::default()
|
||||||
let all_text = format!("{NAME_TEXT}{description_text}{help_text}");
|
.direction(Direction::Vertical)
|
||||||
let mut max_line_width = max_line_width(&all_text);
|
.constraints(
|
||||||
let mut lines = all_text.lines().count();
|
[
|
||||||
|
Constraint::Max(name_info.height.try_into().unwrap_or_default()),
|
||||||
|
Constraint::Max(description_info.height.try_into().unwrap_or_default()),
|
||||||
|
Constraint::Max(button_info.height.try_into().unwrap_or_default()),
|
||||||
|
Constraint::Max(final_info.height.try_into().unwrap_or_default()),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(area);
|
||||||
|
|
||||||
// Add some vertical and horizontal padding to the info box
|
let name_paragraph = Paragraph::new(name_info.spans)
|
||||||
lines += 3;
|
|
||||||
max_line_width += 4;
|
|
||||||
|
|
||||||
let name_paragraph = Paragraph::new(NAME_TEXT)
|
|
||||||
.style(Style::default().bg(Color::Magenta).fg(Color::White))
|
.style(Style::default().bg(Color::Magenta).fg(Color::White))
|
||||||
.block(Block::default())
|
.block(Block::default())
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
let description_paragraph = Paragraph::new(description_text.as_str())
|
let description_paragraph = Paragraph::new(description_info.spans)
|
||||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||||
.block(Block::default())
|
.block(Block::default())
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
let help_paragraph = Paragraph::new(help_text.as_str())
|
let help_paragraph = Paragraph::new(button_info.spans)
|
||||||
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||||
.block(Block::default())
|
.block(Block::default())
|
||||||
.alignment(Alignment::Left);
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
|
let final_paragraph = Paragraph::new(final_info.spans)
|
||||||
|
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
|
||||||
|
.block(Block::default())
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(title)
|
.title(title)
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.border_style(Style::default().fg(Color::Black));
|
.border_style(Style::default().fg(Color::Black));
|
||||||
|
|
||||||
let area = popup(lines, max_line_width, f.size(), BoxLocation::MiddleCentre);
|
|
||||||
|
|
||||||
let split_popup = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Max(NAME_TEXT.lines().count().try_into().unwrap_or_default()),
|
|
||||||
Constraint::Max(
|
|
||||||
description_text
|
|
||||||
.lines()
|
|
||||||
.count()
|
|
||||||
.try_into()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
),
|
|
||||||
Constraint::Max(help_text.lines().count().try_into().unwrap_or_default()),
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(area);
|
|
||||||
|
|
||||||
// Order is important here
|
// Order is important here
|
||||||
f.render_widget(Clear, area);
|
f.render_widget(Clear, area);
|
||||||
f.render_widget(name_paragraph, split_popup[0]);
|
f.render_widget(name_paragraph, split_popup[0]);
|
||||||
f.render_widget(description_paragraph, split_popup[1]);
|
f.render_widget(description_paragraph, split_popup[1]);
|
||||||
f.render_widget(help_paragraph, split_popup[2]);
|
f.render_widget(help_paragraph, split_popup[2]);
|
||||||
|
f.render_widget(final_paragraph, split_popup[3]);
|
||||||
f.render_widget(block, area);
|
f.render_widget(block, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,3 +828,15 @@ fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocatio
|
|||||||
.constraints(h_constraints)
|
.constraints(h_constraints)
|
||||||
.split(popup_layout[indexes.0])[indexes.1]
|
.split(popup_layout[indexes.0])[indexes.1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw nothing, as in a blank screen
|
||||||
|
// pub fn nothing<B: Backend>(f: &mut Frame<'_, B>) {
|
||||||
|
// let whole_layout = Layout::default()
|
||||||
|
// .direction(Direction::Vertical)
|
||||||
|
// .constraints([Constraint::Min(100)].as_ref())
|
||||||
|
// .split(f.size());
|
||||||
|
|
||||||
|
// let block = Block::default()
|
||||||
|
// .borders(Borders::NONE);
|
||||||
|
// f.render_widget(block, whole_layout[0]);
|
||||||
|
// }
|
||||||
|
|||||||
+131
-58
@@ -1,16 +1,18 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event},
|
event::{self, DisableMouseCapture, Event},
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io::{self, Stdout, Write},
|
||||||
sync::{atomic::Ordering, Arc},
|
sync::{atomic::Ordering, Arc},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use std::{sync::atomic::AtomicBool, time::Instant};
|
use std::{sync::atomic::AtomicBool, time::Instant};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use tracing::error;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::{Backend, CrosstermBackend},
|
backend::{Backend, CrosstermBackend},
|
||||||
layout::{Constraint, Direction, Layout},
|
layout::{Constraint, Direction, Layout},
|
||||||
@@ -28,109 +30,180 @@ use crate::{
|
|||||||
input_handler::InputMessages,
|
input_handler::InputMessages,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Take control of the terminal in order to draw gui
|
pub struct Ui {
|
||||||
pub async fn create_ui(
|
app_data: Arc<Mutex<AppData>>,
|
||||||
|
docker_sx: Sender<DockerMessage>,
|
||||||
|
gui_state: Arc<Mutex<GuiState>>,
|
||||||
|
input_poll_rate: Duration,
|
||||||
|
is_running: Arc<AtomicBool>,
|
||||||
|
now: Instant,
|
||||||
|
sender: Sender<InputMessages>,
|
||||||
|
terminal: Terminal<CrosstermBackend<Stdout>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
/// Enable mouse capture, but don't enable capture of all the mouse movements, doing so will improve performance, and is part of the fix for the weird mouse event output bug
|
||||||
|
pub fn enable_mouse_capture() {
|
||||||
|
io::stdout()
|
||||||
|
.write_all(
|
||||||
|
concat!(
|
||||||
|
crossterm::csi!("?1000h"),
|
||||||
|
crossterm::csi!("?1015h"),
|
||||||
|
crossterm::csi!("?1006h"),
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap_or(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Ui struct, and execute the drawing loop
|
||||||
|
pub async fn create(
|
||||||
app_data: Arc<Mutex<AppData>>,
|
app_data: Arc<Mutex<AppData>>,
|
||||||
docker_sx: Sender<DockerMessage>,
|
docker_sx: Sender<DockerMessage>,
|
||||||
gui_state: Arc<Mutex<GuiState>>,
|
gui_state: Arc<Mutex<GuiState>>,
|
||||||
is_running: Arc<AtomicBool>,
|
is_running: Arc<AtomicBool>,
|
||||||
sender: Sender<InputMessages>,
|
sender: Sender<InputMessages>,
|
||||||
) -> Result<()> {
|
) {
|
||||||
enable_raw_mode()?;
|
if let Ok(terminal) = Self::setup_terminal() {
|
||||||
let mut stdout = io::stdout();
|
let mut ui = Self {
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
|
||||||
let mut terminal = Terminal::new(backend)?;
|
|
||||||
|
|
||||||
let res = run_app(
|
|
||||||
app_data,
|
app_data,
|
||||||
docker_sx,
|
docker_sx,
|
||||||
gui_state,
|
gui_state,
|
||||||
|
input_poll_rate: std::time::Duration::from_millis(100),
|
||||||
is_running,
|
is_running,
|
||||||
|
now: Instant::now(),
|
||||||
sender,
|
sender,
|
||||||
&mut terminal,
|
terminal,
|
||||||
)
|
};
|
||||||
.await;
|
if let Err(e) = ui.draw_ui().await {
|
||||||
disable_raw_mode()?;
|
error!("{e}");
|
||||||
|
}
|
||||||
|
if let Err(e) = ui.reset_terminal() {
|
||||||
|
error!("{e}");
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
error!("Terminal Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setup the terminal for full-screen drawing mode, with mouse capture
|
||||||
|
fn setup_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
|
Self::enable_mouse_capture();
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
Terminal::new(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a fix for mouse-events being printed to screen, read an event and do nothing with it
|
||||||
|
fn nullify_event_read(&self) {
|
||||||
|
if crossterm::event::poll(self.input_poll_rate).unwrap_or(true) {
|
||||||
|
event::read().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reset the terminal back to default settings
|
||||||
|
pub fn reset_terminal(&mut self) -> Result<()> {
|
||||||
|
self.terminal.clear()?;
|
||||||
|
|
||||||
execute!(
|
execute!(
|
||||||
terminal.backend_mut(),
|
self.terminal.backend_mut(),
|
||||||
LeaveAlternateScreen,
|
LeaveAlternateScreen,
|
||||||
DisableMouseCapture
|
DisableMouseCapture
|
||||||
)?;
|
)?;
|
||||||
terminal.show_cursor()?;
|
disable_raw_mode()?;
|
||||||
|
self.terminal.show_cursor()?;
|
||||||
if let Err(err) = res {
|
|
||||||
println!("error: {err}");
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a loop to draw the gui
|
/// Draw the the error message ui, for 5 seconds, with a countdown
|
||||||
async fn run_app<B: Backend + Send>(
|
fn err_loop(&mut self) -> Result<(), AppError> {
|
||||||
app_data: Arc<Mutex<AppData>>,
|
|
||||||
docker_sx: Sender<DockerMessage>,
|
|
||||||
gui_state: Arc<Mutex<GuiState>>,
|
|
||||||
is_running: Arc<AtomicBool>,
|
|
||||||
sender: Sender<InputMessages>,
|
|
||||||
terminal: &mut Terminal<B>,
|
|
||||||
) -> Result<(), AppError> {
|
|
||||||
let update_duration =
|
|
||||||
std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval));
|
|
||||||
let input_poll_rate = std::time::Duration::from_millis(75);
|
|
||||||
let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]);
|
|
||||||
let mut now = Instant::now();
|
|
||||||
if status_dockerconnect {
|
|
||||||
let mut seconds = 5;
|
let mut seconds = 5;
|
||||||
loop {
|
loop {
|
||||||
|
if self.now.elapsed() >= std::time::Duration::from_secs(1) {
|
||||||
|
seconds -= 1;
|
||||||
|
self.now = Instant::now();
|
||||||
if seconds < 1 {
|
if seconds < 1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if now.elapsed() >= std::time::Duration::from_secs(1) {
|
|
||||||
seconds -= 1;
|
|
||||||
now = Instant::now();
|
|
||||||
}
|
}
|
||||||
if terminal
|
|
||||||
|
// This is a fix for mouse-events being printed to screen
|
||||||
|
self.nullify_event_read();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.terminal
|
||||||
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
|
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
return Err(AppError::Terminal);
|
return Err(AppError::Terminal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
Ok(())
|
||||||
while is_running.load(Ordering::SeqCst) {
|
}
|
||||||
if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
|
|
||||||
|
/// The loop for drawing the main UI to the terminal
|
||||||
|
async fn gui_loop(&mut self) -> Result<(), AppError> {
|
||||||
|
let update_duration =
|
||||||
|
std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval));
|
||||||
|
|
||||||
|
while self.is_running.load(Ordering::SeqCst) {
|
||||||
|
if self
|
||||||
|
.terminal
|
||||||
|
.draw(|frame| draw_frame(frame, &self.app_data, &self.gui_state))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(AppError::Terminal);
|
||||||
|
}
|
||||||
|
if crossterm::event::poll(self.input_poll_rate).unwrap_or(false) {
|
||||||
if let Ok(event) = event::read() {
|
if let Ok(event) = event::read() {
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
sender
|
self.sender
|
||||||
.send(InputMessages::ButtonPress(key.code))
|
.send(InputMessages::ButtonPress(key.code))
|
||||||
.await
|
.await
|
||||||
.unwrap_or(());
|
.unwrap_or(());
|
||||||
} else if let Event::Mouse(m) = event {
|
} else if let Event::Mouse(m) = event {
|
||||||
sender
|
self.sender
|
||||||
.send(InputMessages::MouseEvent(m))
|
.send(InputMessages::MouseEvent(m))
|
||||||
.await
|
.await
|
||||||
.unwrap_or(());
|
.unwrap_or(());
|
||||||
} else if let Event::Resize(_, _) = event {
|
} else if let Event::Resize(_, _) = event {
|
||||||
gui_state.lock().clear_area_map();
|
self.gui_state.lock().clear_area_map();
|
||||||
terminal.autoresize().unwrap_or(());
|
self.terminal.autoresize().unwrap_or(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if now.elapsed() >= update_duration {
|
if self.now.elapsed() >= update_duration {
|
||||||
docker_sx.send(DockerMessage::Update).await.unwrap_or(());
|
self.docker_sx
|
||||||
now = Instant::now();
|
.send(DockerMessage::Update)
|
||||||
}
|
.await
|
||||||
if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
|
.unwrap_or(());
|
||||||
return Err(AppError::Terminal);
|
self.now = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
terminal.clear().unwrap_or(());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui<B: Backend>(
|
/// Draw either the Error, or main oxker ui, to the terminal
|
||||||
|
async fn draw_ui(&mut self) -> Result<(), AppError> {
|
||||||
|
let status_dockerconnect = self
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_contains(&[Status::DockerConnect]);
|
||||||
|
if status_dockerconnect {
|
||||||
|
self.err_loop()?;
|
||||||
|
} else {
|
||||||
|
self.gui_loop().await?;
|
||||||
|
}
|
||||||
|
self.nullify_event_read();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the main ui to a frame of the terminal
|
||||||
|
fn draw_frame<B: Backend>(
|
||||||
f: &mut Frame<'_, B>,
|
f: &mut Frame<'_, B>,
|
||||||
app_data: &Arc<Mutex<AppData>>,
|
app_data: &Arc<Mutex<AppData>>,
|
||||||
gui_state: &Arc<Mutex<GuiState>>,
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
@@ -182,7 +255,7 @@ fn ui<B: Backend>(
|
|||||||
vec![Constraint::Percentage(100)]
|
vec![Constraint::Percentage(100)]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Split into 3, containers+controls, logs, then graphs
|
// Split into 2, logs, and optional charts
|
||||||
let lower_main = Layout::default()
|
let lower_main = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(lower_split.as_ref())
|
.constraints(lower_split.as_ref())
|
||||||
|
|||||||
Reference in New Issue
Block a user