diff --git a/src/multi.rs b/src/multi.rs index 195c9f9d..02f97f8e 100644 --- a/src/multi.rs +++ b/src/multi.rs @@ -416,12 +416,10 @@ impl MultiState { self.ordering.insert(pos, idx); } InsertLocation::After(after_idx) => { - let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap(); - self.ordering.insert(pos + 1, idx); + self.ordering.insert(self.visual_index(after_idx) + 1, idx); } InsertLocation::Before(before_idx) => { - let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap(); - self.ordering.insert(pos, idx); + self.ordering.insert(self.visual_index(before_idx), idx); } } @@ -465,6 +463,14 @@ impl MultiState { fn len(&self) -> usize { self.members.len() - self.free_set.len() } + + /// Map from opaque index to position on screen. + pub(crate) fn visual_index(&self, opaque_index: usize) -> usize { + self.ordering + .iter() + .position(|v| *v == opaque_index) + .expect("no such member") + } } #[derive(Default)] @@ -552,9 +558,28 @@ mod tests { let p1 = mp.add(ProgressBar::new(1)); let p2 = mp.add(ProgressBar::new(1)); let p3 = mp.add(ProgressBar::new(1)); + + // Check position of bars on screen + assert_eq!( + &[ + p0.visual_index().unwrap(), + p1.visual_index().unwrap(), + p2.visual_index().unwrap(), + p3.visual_index().unwrap() + ], + &[0, 1, 2, 3] + ); + mp.remove(&p2); mp.remove(&p1); + + assert_eq!( + &[p0.visual_index().unwrap(), p3.visual_index().unwrap()], + &[0, 1] + ); + let p4 = mp.insert(1, ProgressBar::new(1)); + assert_eq!(p4.visual_index().unwrap(), 1); let state = mp.state.read().unwrap(); // the removed place for p1 is reused @@ -580,6 +605,18 @@ mod tests { assert_eq!(p1.index(), None); assert_eq!(p2.index(), None); assert_eq!(p3.index().unwrap(), 3); + + // Check position of bars on screen + assert_eq!( + &[ + p0.visual_index(), + p1.visual_index(), + p2.visual_index(), + p3.visual_index(), + p4.visual_index() + ], + &[Some(0), None, None, Some(2), Some(1)] + ); } #[test] @@ -598,6 +635,18 @@ mod tests { assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); + + // Check position of bars on screen + assert_eq!( + &[ + p0.visual_index().unwrap(), + p1.visual_index().unwrap(), + p2.visual_index().unwrap(), + p3.visual_index().unwrap(), + p4.visual_index().unwrap() + ], + &[1, 2, 4, 3, 0] + ); } #[test] @@ -616,6 +665,18 @@ mod tests { assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); + + // Check position of bars on screen + assert_eq!( + &[ + p0.visual_index().unwrap(), + p1.visual_index().unwrap(), + p2.visual_index().unwrap(), + p3.visual_index().unwrap(), + p4.visual_index().unwrap() + ], + &[0, 2, 3, 4, 1] + ); } #[test] @@ -634,6 +695,18 @@ mod tests { assert_eq!(p2.index().unwrap(), 2); assert_eq!(p3.index().unwrap(), 3); assert_eq!(p4.index().unwrap(), 4); + + // Check position of bars on screen + assert_eq!( + &[ + p0.visual_index().unwrap(), + p1.visual_index().unwrap(), + p2.visual_index().unwrap(), + p3.visual_index().unwrap(), + p4.visual_index().unwrap() + ], + &[1, 2, 4, 0, 3] + ); } #[test] @@ -656,6 +729,20 @@ mod tests { assert_eq!(p4.index().unwrap(), 4); assert_eq!(p5.index().unwrap(), 5); assert_eq!(p6.index().unwrap(), 6); + + // Check position of bars on screen + assert_eq!( + &[ + p0.visual_index().unwrap(), + p1.visual_index().unwrap(), + p2.visual_index().unwrap(), + p3.visual_index().unwrap(), + p4.visual_index().unwrap(), + p5.visual_index().unwrap(), + p6.visual_index().unwrap() + ], + &[3, 5, 6, 0, 2, 1, 4] + ); } #[test] diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 61a5990e..ebced06d 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -633,11 +633,26 @@ impl ProgressBar { self.state().tab_width } - /// Index in the `MultiState` + /// Opaque index in the `MultiState` + /// + /// This value is an implementation detail and does not necessarily correspond to visual + /// position on the screen. + /// + /// It is an index into `MultiState::members`, which is not in any particular order (due to + /// reclaiming of indices from the `MultiState::free_set`). pub(crate) fn index(&self) -> Option { self.state().draw_target.remote().map(|(_, idx)| idx) } + /// If this [`ProgressBar`] is a member of a [`MultiProgress`](crate::MultiProgress), then return visual position + /// on screen. Otherwise, `None`. + pub fn visual_index(&self) -> Option { + self.state() + .draw_target + .remote() + .map(|(remote, idx)| remote.read().unwrap().visual_index(idx)) + } + /// Current message pub fn message(&self) -> String { self.state().state.message.expanded().to_string()