Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add insert_after / insert_before to MultiProgress #331

Merged
merged 7 commits into from Dec 15, 2021
75 changes: 75 additions & 0 deletions examples/multi-insert-relative.rs
@@ -0,0 +1,75 @@
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
chris-laplante marked this conversation as resolved.
Show resolved Hide resolved
use std::sync::Arc;
use std::time::Duration;

struct Item {
name: &'static str,
steps: Vec<&'static str>,
}

fn main() {
let mp = Arc::new(MultiProgress::new());
let item_spinner_style = ProgressStyle::default_spinner().on_finish(ProgressFinish::AndLeave);

let sty_main = ProgressStyle::default_bar().on_finish(ProgressFinish::AndLeave);
let step_progress_style = ProgressStyle::default_bar()
.template("{msg}: {bar:40.green/yellow} {pos:>4}/{len:4}")
.on_finish(ProgressFinish::AndLeave);

let items_to_process = vec![
Item {
name: "apples",
steps: vec!["removing stem", "washing", "peeling", "coring", "slicing"],
},
Item {
name: "pears",
steps: vec!["removing stem", "washing", "coring", "dicing"],
},
Item {
name: "oranges",
steps: vec!["peeling", "segmenting", "removing pith"],
},
Item {
name: "cherries",
steps: vec!["washing", "pitting", "removing stems"],
},
Item {
name: "bananas",
steps: vec!["peeling", "slicing"],
},
];

let overall_progress =
mp.add(ProgressBar::new(items_to_process.len() as u64).with_style(sty_main));
overall_progress.tick();

for item in items_to_process {
let item_spinner = mp.insert_before(
&overall_progress,
ProgressBar::new_spinner()
.with_message(item.name)
.with_style(item_spinner_style.clone()),
);
item_spinner.enable_steady_tick(100);

std::thread::sleep(Duration::from_secs(3));

let mut next_step_target = item_spinner.clone();

for step in item.steps {
let step_progress = mp.insert_after(
&next_step_target,
ProgressBar::new(10)
.with_message(step)
.with_style(step_progress_style.clone()),
);
next_step_target = step_progress.clone();
for _ in 0..9 {
step_progress.inc(1);
std::thread::sleep(Duration::from_millis(100));
}
}

overall_progress.inc(1);
}
}
120 changes: 109 additions & 11 deletions src/progress_bar.rs
Expand Up @@ -558,6 +558,14 @@ impl Default for MultiProgress {
}
}

enum InsertLocation<'a> {
chris-laplante marked this conversation as resolved.
Show resolved Hide resolved
End,
Index(usize),
IndexFromBack(usize),
After(&'a ProgressBar),
Before(&'a ProgressBar),
}

impl MultiProgress {
/// Creates a new multi progress object.
///
Expand Down Expand Up @@ -608,7 +616,7 @@ impl MultiProgress {
/// remote draw target that is intercepted by the multi progress
/// object overriding custom `ProgressDrawTarget` settings.
pub fn add(&self, pb: ProgressBar) -> ProgressBar {
self.push(None, false, pb)
self.push(InsertLocation::End, pb)
}

/// Inserts a progress bar.
Expand All @@ -620,7 +628,7 @@ impl MultiProgress {
/// If `index >= MultiProgressState::objects.len()`, the progress bar
/// is added to the end of the list.
pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar {
self.push(Some(index), false, pb)
self.push(InsertLocation::Index(index), pb)
}

/// Inserts a progress bar from the back.
Expand All @@ -633,10 +641,28 @@ impl MultiProgress {
/// If `index >= MultiProgressState::objects.len()`, the progress bar
/// is added to the start of the list.
pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar {
self.push(Some(index), true, pb)
self.push(InsertLocation::IndexFromBack(index), pb)
}

/// Inserts a progress bar before an existing one.
///
/// The progress bar added will have the draw target changed to a
/// remote draw target that is intercepted by the multi progress
/// object overriding custom `ProgressDrawTarget` settings.
pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar {
self.push(InsertLocation::Before(before), pb)
}

fn push(&self, pos: Option<usize>, from_back: bool, pb: ProgressBar) -> ProgressBar {
/// Inserts a progress bar after an existing one.
///
/// The progress bar added will have the draw target changed to a
/// remote draw target that is intercepted by the multi progress
/// object overriding custom `ProgressDrawTarget` settings.
pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar {
self.push(InsertLocation::After(after), pb)
}

fn push(&self, pos: InsertLocation, pb: ProgressBar) -> ProgressBar {
chris-laplante marked this conversation as resolved.
Show resolved Hide resolved
let mut state = self.state.write().unwrap();
let idx = match state.free_set.pop() {
Some(idx) => {
Expand All @@ -650,15 +676,29 @@ impl MultiProgress {
};

match pos {
Some(pos) => {
let pos = match from_back {
true => state.ordering.len().saturating_sub(pos),
false => Ord::min(pos, state.ordering.len()),
};

InsertLocation::End => state.ordering.push(idx),
InsertLocation::Index(pos) => {
let pos = Ord::min(pos, state.ordering.len());
state.ordering.insert(pos, idx);
}
InsertLocation::IndexFromBack(pos) => {
let pos = state.ordering.len().saturating_sub(pos);
state.ordering.insert(pos, idx);
}
InsertLocation::After(after) => {
let after_idx = after.state.lock().unwrap().draw_target.remote().unwrap().1;
let pos = state.ordering.iter().position(|i| *i == after_idx).unwrap();
state.ordering.insert(pos + 1, idx);
}
InsertLocation::Before(before) => {
let before_idx = before.state.lock().unwrap().draw_target.remote().unwrap().1;
let pos = state
.ordering
.iter()
.position(|i| *i == before_idx)
.unwrap();
state.ordering.insert(pos, idx);
}
_ => state.ordering.push(idx),
}

assert!(
Expand Down Expand Up @@ -880,6 +920,64 @@ mod tests {
assert_eq!(extract_index(&p4), 4);
}

#[test]
fn multi_progress_insert_after() {
let mp = MultiProgress::new();
let p0 = mp.add(ProgressBar::new(1));
let p1 = mp.add(ProgressBar::new(1));
let p2 = mp.add(ProgressBar::new(1));
let p3 = mp.insert_after(&p2, ProgressBar::new(1));
let p4 = mp.insert_after(&p0, ProgressBar::new(1));

let state = mp.state.read().unwrap();
assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]);
assert_eq!(extract_index(&p0), 0);
assert_eq!(extract_index(&p1), 1);
assert_eq!(extract_index(&p2), 2);
assert_eq!(extract_index(&p3), 3);
assert_eq!(extract_index(&p4), 4);
}

#[test]
fn multi_progress_insert_before() {
let mp = MultiProgress::new();
let p0 = mp.add(ProgressBar::new(1));
let p1 = mp.add(ProgressBar::new(1));
let p2 = mp.add(ProgressBar::new(1));
let p3 = mp.insert_before(&p0, ProgressBar::new(1));
let p4 = mp.insert_before(&p2, ProgressBar::new(1));

let state = mp.state.read().unwrap();
assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]);
assert_eq!(extract_index(&p0), 0);
assert_eq!(extract_index(&p1), 1);
assert_eq!(extract_index(&p2), 2);
assert_eq!(extract_index(&p3), 3);
assert_eq!(extract_index(&p4), 4);
}

#[test]
fn multi_progress_insert_before_and_after() {
let mp = MultiProgress::new();
let p0 = mp.add(ProgressBar::new(1));
let p1 = mp.add(ProgressBar::new(1));
let p2 = mp.add(ProgressBar::new(1));
let p3 = mp.insert_before(&p0, ProgressBar::new(1));
let p4 = mp.insert_after(&p3, ProgressBar::new(1));
let p5 = mp.insert_after(&p3, ProgressBar::new(1));
let p6 = mp.insert_before(&p1, ProgressBar::new(1));

let state = mp.state.read().unwrap();
assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]);
assert_eq!(extract_index(&p0), 0);
assert_eq!(extract_index(&p1), 1);
assert_eq!(extract_index(&p2), 2);
assert_eq!(extract_index(&p3), 3);
assert_eq!(extract_index(&p4), 4);
assert_eq!(extract_index(&p5), 5);
assert_eq!(extract_index(&p6), 6);
}

#[test]
fn multi_progress_multiple_remove() {
let mp = MultiProgress::new();
Expand Down