initial UI framework
This commit is contained in:
parent
93195bab1b
commit
6b1098c863
|
@ -15,8 +15,8 @@ dependencies = [
|
|||
name = "arkham"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"console",
|
||||
"criterion",
|
||||
"crossterm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"textwrap 0.14.2",
|
||||
|
@ -42,9 +42,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
|
@ -90,21 +90,6 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"regex",
|
||||
"terminal_size",
|
||||
"unicode-width",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.4"
|
||||
|
@ -185,6 +170,32 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.1.6"
|
||||
|
@ -214,10 +225,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
name = "futures-core"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
|
@ -234,6 +245,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
|
@ -275,9 +295,18 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.88"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -303,6 +332,37 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
|
@ -334,6 +394,31 @@ version = "11.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
|
@ -414,6 +499,15 @@ dependencies = [
|
|||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.3"
|
||||
|
@ -527,6 +621,42 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.1"
|
||||
|
@ -544,16 +674,6 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
|
|
|
@ -14,12 +14,12 @@ categories = ["command-line-interface", "config"]
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# indicatif = "0.15.0"
|
||||
console = "0.14.0"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true}
|
||||
toml = { version="0.5.8", optional = true}
|
||||
textwrap = "0.14.2"
|
||||
crossterm = { version = "0.22.1", features=["event-stream"]}
|
||||
|
||||
|
||||
|
||||
[features]
|
||||
|
@ -35,3 +35,7 @@ criterion = "0.3"
|
|||
name = "arg_parsing"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "space_fill"
|
||||
harness = false
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
use arkham::ui;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn space_fill(c: &mut Criterion) {
|
||||
c.bench_function("large_space_fill", |b| {
|
||||
let mut space = ui::View::new(200, 200);
|
||||
space.fill(
|
||||
ui::Rect::new(50, 50, 100, 100),
|
||||
*ui::Cell::default()
|
||||
.fg(ui::Color::White)
|
||||
.bg(ui::Color::Green),
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
space
|
||||
.render(ui::Pos::new(0, 0), &mut output)
|
||||
.expect("couldnt render");
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("small_space_fill", |b| {
|
||||
let mut space = ui::View::new(20, 20);
|
||||
space.fill(
|
||||
ui::Rect::new(0, 0, 20, 20),
|
||||
*ui::Cell::default()
|
||||
.fg(ui::Color::White)
|
||||
.bg(ui::Color::Green),
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
space
|
||||
.render(ui::Pos::new(0, 0), &mut output)
|
||||
.expect("couldnt render");
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("fill_all", |b| {
|
||||
let mut space = ui::View::new(200, 200);
|
||||
space.fill_all(
|
||||
*ui::Cell::default()
|
||||
.fg(ui::Color::White)
|
||||
.bg(ui::Color::Green),
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
space
|
||||
.render(ui::Pos::new(0, 0), &mut output)
|
||||
.expect("couldnt render");
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, space_fill);
|
||||
criterion_main!(benches);
|
|
@ -0,0 +1,29 @@
|
|||
use arkham::{
|
||||
ui::{Cell, Color, Component, Rect, UI},
|
||||
Result,
|
||||
};
|
||||
|
||||
pub struct OuterComponent;
|
||||
|
||||
impl Component for OuterComponent {
|
||||
fn view(&mut self, ctx: &mut arkham::ui::Context) -> Result<()> {
|
||||
ctx.view
|
||||
.fill_all(*Cell::default().content(' ').bg(Color::Red));
|
||||
ctx.add_component(Rect::new(20, 20, 40, 40), &mut InnerComponent)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InnerComponent;
|
||||
|
||||
impl Component for InnerComponent {
|
||||
fn view(&mut self, ctx: &mut arkham::ui::Context) -> Result<()> {
|
||||
ctx.view
|
||||
.fill_all(*Cell::default().content(' ').bg(Color::Green));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
UI::new(OuterComponent).run().expect("Couldnt run UI loop");
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// use arkham::TaskGroup;
|
||||
|
||||
fn main() {
|
||||
// let tasks = TaskGroup::new();
|
||||
// let task = tasks.start_task("task 1");
|
||||
// task.tick();
|
||||
// tasks.join();
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use crate::{
|
||||
context::Context,
|
||||
use super::{
|
||||
opt::{self, Opt},
|
||||
vox, App,
|
||||
App, Context,
|
||||
};
|
||||
|
||||
use super::helpers;
|
||||
|
||||
pub type Handler = fn(&App, &Context, &[String]);
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -58,7 +59,7 @@ impl Command {
|
|||
}
|
||||
|
||||
pub(crate) fn help(app: &App, _ctx: &Context, args: &[String]) {
|
||||
vox::print(app.application_header());
|
||||
helpers::print(app.application_header());
|
||||
print_command_help(&app.root, args);
|
||||
}
|
||||
|
||||
|
@ -68,17 +69,17 @@ pub(crate) fn print_command_help(cmd: &Command, args: &[String]) {
|
|||
return print_command_help(cmd, &args[1..]);
|
||||
}
|
||||
}
|
||||
vox::print("");
|
||||
helpers::print("");
|
||||
if let Some(desc) = cmd.long_desc.as_ref().or_else(|| cmd.short_desc.as_ref()) {
|
||||
if cmd.name != "root" {
|
||||
vox::header(&cmd.name.to_uppercase());
|
||||
helpers::header(&cmd.name.to_uppercase());
|
||||
}
|
||||
vox::print(desc);
|
||||
vox::print("");
|
||||
helpers::print(desc);
|
||||
helpers::print("");
|
||||
}
|
||||
if !cmd.opts.is_empty() {
|
||||
vox::header("OPTIONS");
|
||||
vox::description_list(
|
||||
helpers::header("OPTIONS");
|
||||
helpers::description_list(
|
||||
cmd.opts
|
||||
.iter()
|
||||
.map(|o| (o.usage(), o.desc.clone().unwrap_or_default()))
|
||||
|
@ -87,8 +88,8 @@ pub(crate) fn print_command_help(cmd: &Command, args: &[String]) {
|
|||
}
|
||||
|
||||
if !cmd.commands.is_empty() {
|
||||
vox::header("Commands");
|
||||
vox::description_list(
|
||||
helpers::header("Commands");
|
||||
helpers::description_list(
|
||||
cmd.commands
|
||||
.iter()
|
||||
.map(|c| (c.name.clone(), c.short_desc.clone().unwrap_or_default()))
|
|
@ -1,6 +1,8 @@
|
|||
use crate::{Command, Opt};
|
||||
use std::{collections::BTreeMap, env};
|
||||
|
||||
use super::command::print_command_help;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Map(BTreeMap<String, ContextValue>);
|
||||
|
||||
|
@ -240,7 +242,7 @@ impl Context {
|
|||
|
||||
/// Can be used to display the automatic help message for the current command.
|
||||
pub fn display_help(&self) {
|
||||
crate::command::print_command_help(&self.cmd, &[])
|
||||
print_command_help(&self.cmd, &[])
|
||||
}
|
||||
|
||||
pub(crate) fn load_config_file(&mut self, filename: &str) {
|
|
@ -1,4 +1,4 @@
|
|||
use console::style;
|
||||
use crossterm::style::{self, style, Stylize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
|
@ -13,10 +13,9 @@ pub enum Color {
|
|||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
Color256(u8),
|
||||
}
|
||||
|
||||
impl From<Color> for console::Color {
|
||||
impl From<Color> for style::Color {
|
||||
fn from(fr: Color) -> Self {
|
||||
match fr {
|
||||
Color::Black => Self::Black,
|
||||
|
@ -27,33 +26,23 @@ impl From<Color> for console::Color {
|
|||
Color::Magenta => Self::Magenta,
|
||||
Color::Cyan => Self::Cyan,
|
||||
Color::White => Self::White,
|
||||
Color::Color256(v) => Self::Color256(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn labeled(color: Color, label: &str, msg: &str) {
|
||||
let label = format!("[{}]", label);
|
||||
println!("{} {}", console::style(label).fg(color.into()).bold(), msg);
|
||||
}
|
||||
|
||||
pub fn message<T: Display>(str: T) {
|
||||
println!("{}", style(str).white().bold());
|
||||
pub fn print<T: Display>(s: T) {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
pub fn header<T: Display>(str: T) {
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style("-=[").red().dim(),
|
||||
style(str).white().bold(),
|
||||
style("]=-").red().dim()
|
||||
"-=[".red().dim(),
|
||||
style::style(str).white().bold(),
|
||||
"]=-".red().dim()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn note<T: Display>(str: T) {
|
||||
println!("{}", style(str).white().dim());
|
||||
}
|
||||
|
||||
pub fn description_list(list: HashMap<String, String>) {
|
||||
let mut lines = list.into_iter().collect::<Vec<(String, String)>>();
|
||||
let key_max_length = lines.iter().map(|(v, _)| v.len()).max().unwrap_or(10) + 3;
|
||||
|
@ -80,10 +69,6 @@ pub fn description_list(list: HashMap<String, String>) {
|
|||
println!("{}", output);
|
||||
}
|
||||
|
||||
pub fn print<T: Display>(s: T) {
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
pub fn error<T: Display>(s: T) {
|
||||
println!("{}", style(s).red().bold());
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
use crate::{print_command_help, vox};
|
||||
mod command;
|
||||
mod context;
|
||||
mod helpers;
|
||||
mod opt;
|
||||
|
||||
use super::command::{help, Command, Handler};
|
||||
use super::context::Context;
|
||||
use super::opt::{Opt, OptError, OptKind};
|
||||
use command::help;
|
||||
pub use command::{Command, Handler};
|
||||
pub use context::Context;
|
||||
pub use opt::{Opt, OptError, OptKind};
|
||||
|
||||
use std::env;
|
||||
|
||||
use command::print_command_help;
|
||||
|
||||
type Result<T> = std::result::Result<T, OptError>;
|
||||
|
||||
pub struct App {
|
||||
|
@ -196,7 +202,7 @@ impl App {
|
|||
if let Err(e) = run_command(self, &self.root, &args, &mut ctx) {
|
||||
match e {
|
||||
OptError::InvalidOpt(opt) => {
|
||||
vox::error(format!("Invalid options {}", &opt));
|
||||
helpers::error(format!("Invalid options {}", &opt));
|
||||
Err(OptError::InvalidOpt(opt))
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +321,7 @@ fn run_command(app: &App, cmd: &Command, args: &[String], ctx: &mut Context) ->
|
|||
|
||||
// Automatic command help display
|
||||
if ignored.iter().any(|a| a == "-h" || a == "--help") {
|
||||
super::command::print_command_help(cmd, &[]);
|
||||
print_command_help(cmd, &[]);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -332,7 +338,7 @@ fn run_command(app: &App, cmd: &Command, args: &[String], ctx: &mut Context) ->
|
|||
}
|
||||
handler(app, ctx, &ignored);
|
||||
} else {
|
||||
crate::vox::print(app.application_header());
|
||||
helpers::print(app.application_header());
|
||||
print_command_help(cmd, &[])
|
||||
}
|
||||
|
|
@ -124,7 +124,7 @@ impl Opt {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum OptKind {
|
||||
pub enum OptKind {
|
||||
Flag,
|
||||
String,
|
||||
}
|
40
src/lib.rs
40
src/lib.rs
|
@ -2,13 +2,35 @@
|
|||
mod macros;
|
||||
|
||||
mod app;
|
||||
mod command;
|
||||
mod context;
|
||||
mod opt;
|
||||
pub use console;
|
||||
pub mod vox;
|
||||
pub mod ui;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
pub use app::*;
|
||||
pub use command::*;
|
||||
pub use context::*;
|
||||
pub use opt::*;
|
||||
pub use vox::*;
|
||||
pub use app::{App, Command, Opt};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ArkhamError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArkhamError {
|
||||
IO(String),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Error for ArkhamError {}
|
||||
|
||||
impl Display for ArkhamError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Other(v) => f.write_str(v),
|
||||
Self::IO(v) => f.write_str(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ArkhamError {
|
||||
fn from(fr: std::io::Error) -> Self {
|
||||
Self::IO(fr.to_string())
|
||||
}
|
||||
}
|
||||
|
|
64
src/tasks.rs
64
src/tasks.rs
|
@ -1,64 +0,0 @@
|
|||
use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressStyle};
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TaskGroup {
|
||||
mp: Arc<MultiProgress>,
|
||||
}
|
||||
|
||||
impl TaskGroup {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
mp: Arc::new(MultiProgress::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_task(&self, desc: &str) -> Task {
|
||||
let pb = self.mp.add(ProgressBar::new(1));
|
||||
let spinner_style = ProgressStyle::default_spinner()
|
||||
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
|
||||
.template("{prefix:.bold.dim} {spinner} {wide_msg}");
|
||||
pb.set_style(spinner_style);
|
||||
Task::new(TaskState {
|
||||
pb,
|
||||
desc: desc.into(),
|
||||
started_at: Instant::now(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn join(&self) {
|
||||
self.mp.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Task(Arc<Mutex<TaskState>>);
|
||||
|
||||
impl Task {
|
||||
pub fn new(state: TaskState) -> Self {
|
||||
Self(Arc::new(Mutex::new(state)))
|
||||
}
|
||||
|
||||
pub fn tick(&self) {
|
||||
let state = self.0.lock().unwrap();
|
||||
state.pb.set_message(&format!(
|
||||
"{} [{}]",
|
||||
state.desc,
|
||||
style(HumanDuration(state.started_at.elapsed())).yellow()
|
||||
));
|
||||
state.pb.tick();
|
||||
}
|
||||
|
||||
pub fn complete(&self) {
|
||||
self.0.lock().unwrap().pb.finish_with_message("OK");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TaskState {
|
||||
pb: ProgressBar,
|
||||
desc: String,
|
||||
started_at: Instant,
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
use crossterm::queue;
|
||||
use crossterm::style::{
|
||||
Attribute, Attributes, SetAttributes, SetBackgroundColor, SetForegroundColor,
|
||||
};
|
||||
use crossterm::style::{Color, Print};
|
||||
use std::io::Write;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq)]
|
||||
pub struct Cell {
|
||||
empty: bool,
|
||||
fg: Color,
|
||||
bg: Color,
|
||||
content: char,
|
||||
attributes: Attributes,
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
empty: true,
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
content: ' ',
|
||||
attributes: Attributes::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn content(&mut self, v: char) -> &mut Cell {
|
||||
self.empty = false;
|
||||
self.content = v;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fg(&mut self, color: Color) -> &mut Cell {
|
||||
self.fg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bg(&mut self, color: Color) -> &mut Cell {
|
||||
self.bg = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bold(&mut self, v: bool) -> &mut Cell {
|
||||
if v {
|
||||
self.attributes.set(Attribute::Bold);
|
||||
} else {
|
||||
self.attributes.unset(Attribute::Bold);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render(&self, output: &mut impl Write) -> Result<()> {
|
||||
if !self.empty {
|
||||
queue!(output, SetForegroundColor(self.fg))?;
|
||||
queue!(output, SetBackgroundColor(self.bg))?;
|
||||
queue!(output, SetAttributes(self.attributes))?;
|
||||
queue!(output, Print(&self.content))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> Vec<u8> {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
self.render(&mut output).expect("Render error");
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn cell_render() {
|
||||
let mut cell = Cell::default();
|
||||
cell.fg(Color::Red);
|
||||
cell.bg(Color::White);
|
||||
cell.content = 'T';
|
||||
cell.bold(true);
|
||||
let mut output: Vec<u8> = vec![];
|
||||
cell.render(&mut output).expect("Render error");
|
||||
let out_str = String::from_utf8(output).expect("Couldnt unwrap to utf8");
|
||||
assert_eq!(out_str, "\u{1b}[38;5;9m\u{1b}[48;5;15m\u{1b}[1mT");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
use crate::Result;
|
||||
|
||||
use super::Context;
|
||||
|
||||
pub trait Component {
|
||||
fn view(&mut self, _ctx: &mut Context) -> Result<()>;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
use crossterm::event::Event;
|
||||
|
||||
use super::{Component, Rect, View};
|
||||
use crate::Result;
|
||||
|
||||
pub struct Context {
|
||||
pub view: View,
|
||||
pub event: Option<Event>,
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
fn default() -> Self {
|
||||
Context {
|
||||
view: View::fullscreen(),
|
||||
event: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(width: u16, height: u16, event: Option<Event>) -> Self {
|
||||
Context {
|
||||
view: View::new(width, height),
|
||||
event,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_component(&mut self, rect: Rect, cmp: &mut impl Component) -> Result<()> {
|
||||
let mut ctx = Context::new(rect.width, rect.height, self.event);
|
||||
cmp.view(&mut ctx)?;
|
||||
self.view.merge(rect.pos, ctx.view);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
use std::ops::{Add, Sub};
|
||||
|
||||
pub struct Rect {
|
||||
pub pos: Pos,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
|
||||
Rect {
|
||||
pos: Pos::new(x, y),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Pos(u16, u16);
|
||||
|
||||
impl Pos {
|
||||
pub fn new(x: u16, y: u16) -> Self {
|
||||
Pos(x, y)
|
||||
}
|
||||
|
||||
pub fn x(&self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn y(&self) -> u16 {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Pos {
|
||||
type Output = Pos;
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Pos(self.x() + rhs.x(), self.y() + rhs.y())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Pos {
|
||||
type Output = Pos;
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Pos(
|
||||
(self.x() as i32 - rhs.x() as i32).max(0) as u16,
|
||||
(self.y() as i32 - rhs.y() as i32).max(0) as u16,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
mod cell;
|
||||
mod component;
|
||||
mod context;
|
||||
mod geometry;
|
||||
mod view;
|
||||
|
||||
pub use cell::Cell;
|
||||
pub use component::Component;
|
||||
pub use context::Context;
|
||||
pub use crossterm::style::{Color, Stylize};
|
||||
use crossterm::terminal::enable_raw_mode;
|
||||
pub use geometry::{Pos, Rect};
|
||||
pub use std::io::Write;
|
||||
pub use view::View;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub struct UI {
|
||||
root: Box<dyn Component>,
|
||||
}
|
||||
|
||||
impl UI {
|
||||
pub fn new<C: Component + 'static>(root: C) -> Self {
|
||||
Self {
|
||||
root: Box::new(root),
|
||||
}
|
||||
}
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
let mut output = std::io::stdout();
|
||||
let mut context = Context::default();
|
||||
loop {
|
||||
// enable_raw_mode();
|
||||
self.root.view(&mut context)?;
|
||||
context.view.render(Pos::new(0, 0), &mut output)?;
|
||||
output.flush().expect("Couldnt flush output");
|
||||
|
||||
let event = crossterm::event::read()?;
|
||||
context.event = Some(event);
|
||||
self.root.view(&mut context)?;
|
||||
context.view.render(Pos::new(0, 0), &mut output)?;
|
||||
output.flush().expect("Couldnt flush output");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
use super::Rect;
|
||||
use std::io::Write;
|
||||
|
||||
use crossterm::{
|
||||
cursor::{MoveDown, MoveTo, MoveToColumn},
|
||||
queue, terminal,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
use super::{Cell, Pos};
|
||||
|
||||
pub struct View {
|
||||
cells: Vec<Cell>,
|
||||
width: u16,
|
||||
#[allow(dead_code)]
|
||||
height: u16,
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn new(width: u16, height: u16) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
cells: vec![Cell::default(); (width * height) as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fullscreen() -> Self {
|
||||
let (width, height) = terminal::size().expect("Couldnt detect terminal size");
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
cells: vec![Cell::default(); (width * height) as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, pos: Pos) -> Option<&Cell> {
|
||||
self.cells.get((pos.y() * self.width + pos.x()) as usize)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, pos: Pos, cell: Cell) {
|
||||
self.cells[(pos.y() * self.width + pos.x()) as usize] = cell;
|
||||
}
|
||||
|
||||
pub fn fill(&mut self, rect: Rect, cell: Cell) {
|
||||
for y in rect.pos.y()..rect.pos.y() + rect.height {
|
||||
for x in rect.pos.x()..rect.pos.x() + rect.width {
|
||||
self.set(Pos::new(x, y), cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_all(&mut self, cell: Cell) {
|
||||
self.cells.fill(cell);
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, pos: Pos, view: View) {
|
||||
for (idx, line) in view.cells.chunks(view.width as usize).enumerate() {
|
||||
let start = ((pos.y() + idx as u16) * self.width + pos.x()) as usize;
|
||||
let end = start + line.len();
|
||||
self.cells.splice(start..end, line.iter().cloned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, pos: Pos, output: &mut impl Write) -> Result<()> {
|
||||
queue!(output, MoveTo(pos.x(), pos.y()))?;
|
||||
for line in self.cells.chunks(self.width as usize) {
|
||||
for cell in line {
|
||||
cell.render(output)?;
|
||||
}
|
||||
queue!(output, MoveDown(1))?;
|
||||
queue!(output, MoveToColumn(pos.x()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue