281 lines
8.5 KiB
Rust
281 lines
8.5 KiB
Rust
use clap::Parser;
|
|
use std::fmt::Debug;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
use console::style;
|
|
use indicatif::{MultiProgress, MultiProgressAlignment, ProgressBar, ProgressStyle};
|
|
use once_cell::sync::Lazy;
|
|
use rand::rngs::ThreadRng;
|
|
use rand::{Rng, RngCore};
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Action {
|
|
ModifyTree(usize),
|
|
IncProgressBar(usize),
|
|
Stop,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum Elem {
|
|
AddItem(Item),
|
|
RemoveItem(Index),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct Item {
|
|
key: String,
|
|
index: usize,
|
|
indent: usize,
|
|
progress_bar: ProgressBar,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct Index(usize);
|
|
|
|
const PB_LEN: u64 = 32;
|
|
static ELEM_IDX: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
static ELEMENTS: Lazy<[Elem; 27]> = Lazy::new(|| {
|
|
[
|
|
Elem::AddItem(Item {
|
|
indent: 9,
|
|
index: 0,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "dog".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 0,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_1".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 8,
|
|
index: 1,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "lazy".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 1,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_2".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 1,
|
|
index: 0,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "the".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 0,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_3".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 7,
|
|
index: 3,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "a".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 3,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_4".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 6,
|
|
index: 2,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "over".to_string(),
|
|
}),
|
|
Elem::RemoveItem(Index(6)),
|
|
Elem::RemoveItem(Index(4)),
|
|
Elem::RemoveItem(Index(3)),
|
|
Elem::RemoveItem(Index(0)),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 2,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_5".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 4,
|
|
index: 1,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "fox".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 1,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_6".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 2,
|
|
index: 1,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "quick".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 1,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_7".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 5,
|
|
index: 5,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "jumps".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 5,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_8".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 3,
|
|
index: 4,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "brown".to_string(),
|
|
}),
|
|
Elem::AddItem(Item {
|
|
indent: 0,
|
|
index: 3,
|
|
progress_bar: ProgressBar::new(PB_LEN),
|
|
key: "temp_9".to_string(),
|
|
}),
|
|
Elem::RemoveItem(Index(10)),
|
|
Elem::RemoveItem(Index(7)),
|
|
Elem::RemoveItem(Index(4)),
|
|
Elem::RemoveItem(Index(3)),
|
|
Elem::RemoveItem(Index(1)),
|
|
]
|
|
});
|
|
|
|
#[derive(Debug, Parser)]
|
|
pub struct Config {
|
|
#[clap(long)]
|
|
bottom_alignment: bool,
|
|
}
|
|
|
|
/// The example demonstrates the usage of `MultiProgress` and further extends `multi-tree` example.
|
|
/// Now the example has 3 different actions implemented, and the item tree can be modified
|
|
/// by inserting or removing progress bars. The progress bars to be removed eventually
|
|
/// have messages with pattern `"temp_*"`.
|
|
///
|
|
/// Also the command option `--bottom-alignment` is used to control the vertical alignment of the
|
|
/// `MultiProgress`. To enable this run it with
|
|
/// ```ignore
|
|
/// cargo run --example multi-tree-ext -- --bottom-alignment
|
|
/// ```
|
|
pub fn main() {
|
|
let conf: Config = Config::parse();
|
|
let mp = Arc::new(MultiProgress::new());
|
|
let alignment = if conf.bottom_alignment {
|
|
MultiProgressAlignment::Bottom
|
|
} else {
|
|
MultiProgressAlignment::Top
|
|
};
|
|
mp.set_alignment(alignment);
|
|
let sty_main = ProgressStyle::with_template("{bar:40.green/yellow} {pos:>4}/{len:4}").unwrap();
|
|
let sty_aux =
|
|
ProgressStyle::with_template("[{pos:>2}/{len:2}] {prefix}{spinner:.green} {msg}").unwrap();
|
|
let sty_fin = ProgressStyle::with_template("[{pos:>2}/{len:2}] {prefix}{msg}").unwrap();
|
|
|
|
let pb_main = mp.add(ProgressBar::new(
|
|
ELEMENTS
|
|
.iter()
|
|
.map(|e| match e {
|
|
Elem::AddItem(item) => item.progress_bar.length().unwrap(),
|
|
Elem::RemoveItem(_) => 1,
|
|
})
|
|
.sum(),
|
|
));
|
|
|
|
pb_main.set_style(sty_main);
|
|
for e in ELEMENTS.iter() {
|
|
match e {
|
|
Elem::AddItem(item) => item.progress_bar.set_style(sty_aux.clone()),
|
|
Elem::RemoveItem(_) => {}
|
|
}
|
|
}
|
|
|
|
let mut items: Vec<&Item> = Vec::with_capacity(ELEMENTS.len());
|
|
|
|
let mp2 = Arc::clone(&mp);
|
|
let mut rng = ThreadRng::default();
|
|
pb_main.tick();
|
|
loop {
|
|
match get_action(&mut rng, &items) {
|
|
Action::Stop => {
|
|
// all elements were exhausted
|
|
pb_main.finish();
|
|
return;
|
|
}
|
|
Action::ModifyTree(elem_idx) => match &ELEMENTS[elem_idx] {
|
|
Elem::AddItem(item) => {
|
|
let pb = mp2.insert(item.index, item.progress_bar.clone());
|
|
pb.set_prefix(" ".repeat(item.indent));
|
|
pb.set_message(&item.key);
|
|
items.insert(item.index, item);
|
|
}
|
|
Elem::RemoveItem(Index(index)) => {
|
|
let item = items.remove(*index);
|
|
let pb = &item.progress_bar;
|
|
mp2.remove(pb);
|
|
pb_main.inc(pb.length().unwrap() - pb.position());
|
|
}
|
|
},
|
|
Action::IncProgressBar(item_idx) => {
|
|
let item = &items[item_idx];
|
|
item.progress_bar.inc(1);
|
|
let pos = item.progress_bar.position();
|
|
if pos >= item.progress_bar.length().unwrap() {
|
|
item.progress_bar.set_style(sty_fin.clone());
|
|
item.progress_bar.finish_with_message(format!(
|
|
"{} {}",
|
|
style("✔").green(),
|
|
item.key
|
|
));
|
|
}
|
|
pb_main.inc(1);
|
|
}
|
|
}
|
|
thread::sleep(Duration::from_millis(20));
|
|
}
|
|
}
|
|
|
|
/// The function guarantees to return the action, that is valid for the current tree.
|
|
fn get_action(rng: &mut dyn RngCore, items: &[&Item]) -> Action {
|
|
let elem_idx = ELEM_IDX.load(Ordering::SeqCst);
|
|
// the indices of those items, that not completed yet
|
|
let uncompleted = items
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|(_, item)| {
|
|
let pos = item.progress_bar.position();
|
|
pos < item.progress_bar.length().unwrap()
|
|
})
|
|
.map(|(idx, _)| idx)
|
|
.collect::<Vec<usize>>();
|
|
let k = rng.gen_range(0..16);
|
|
if (k > 0 || k == 0 && elem_idx == ELEMENTS.len()) && !uncompleted.is_empty() {
|
|
let idx = rng.gen_range(0..uncompleted.len() as u64) as usize;
|
|
Action::IncProgressBar(uncompleted[idx])
|
|
} else if elem_idx < ELEMENTS.len() {
|
|
ELEM_IDX.fetch_add(1, Ordering::SeqCst);
|
|
Action::ModifyTree(elem_idx)
|
|
} else {
|
|
// nothing to do more
|
|
Action::Stop
|
|
}
|
|
}
|