1 | use std::fmt::{Debug, Formatter}; |
---|---|
2 | use std::io; |
3 | use std::sync::{Arc, RwLock}; |
4 | use std::thread::panicking; |
5 | #[cfg(not(target_arch = "wasm32"))] |
6 | use std::time::Instant; |
7 | |
8 | use crate::draw_target::{ |
9 | visual_line_count, DrawState, DrawStateWrapper, LineAdjust, ProgressDrawTarget, VisualLines, |
10 | }; |
11 | use crate::progress_bar::ProgressBar; |
12 | #[cfg(target_arch = "wasm32")] |
13 | use web_time::Instant; |
14 | |
15 | /// Manages multiple progress bars from different threads |
16 | #[derive(Debug, Clone)] |
17 | pub struct MultiProgress { |
18 | pub(crate) state: Arc<RwLock<MultiState>>, |
19 | } |
20 | |
21 | impl Default for MultiProgress { |
22 | fn default() -> Self { |
23 | Self::with_draw_target(ProgressDrawTarget::stderr()) |
24 | } |
25 | } |
26 | |
27 | impl MultiProgress { |
28 | /// Creates a new multi progress object. |
29 | /// |
30 | /// Progress bars added to this object by default draw directly to stderr, and refresh |
31 | /// a maximum of 15 times a second. To change the refresh rate [set] the [draw target] to |
32 | /// one with a different refresh rate. |
33 | /// |
34 | /// [set]: MultiProgress::set_draw_target |
35 | /// [draw target]: ProgressDrawTarget |
36 | pub fn new() -> Self { |
37 | Self::default() |
38 | } |
39 | |
40 | /// Creates a new multi progress object with the given draw target. |
41 | pub fn with_draw_target(draw_target: ProgressDrawTarget) -> Self { |
42 | Self { |
43 | state: Arc::new(RwLock::new(MultiState::new(draw_target))), |
44 | } |
45 | } |
46 | |
47 | /// Sets a different draw target for the multiprogress bar. |
48 | /// |
49 | /// Use [`MultiProgress::with_draw_target`] to set the draw target during creation. |
50 | pub fn set_draw_target(&self, target: ProgressDrawTarget) { |
51 | let mut state = self.state.write().unwrap(); |
52 | state.draw_target.disconnect(Instant::now()); |
53 | state.draw_target = target; |
54 | } |
55 | |
56 | /// Set whether we should try to move the cursor when possible instead of clearing lines. |
57 | /// |
58 | /// This can reduce flickering, but do not enable it if you intend to change the number of |
59 | /// progress bars. |
60 | pub fn set_move_cursor(&self, move_cursor: bool) { |
61 | self.state |
62 | .write() |
63 | .unwrap() |
64 | .draw_target |
65 | .set_move_cursor(move_cursor); |
66 | } |
67 | |
68 | /// Set alignment flag |
69 | pub fn set_alignment(&self, alignment: MultiProgressAlignment) { |
70 | self.state.write().unwrap().alignment = alignment; |
71 | } |
72 | |
73 | /// Adds a progress bar. |
74 | /// |
75 | /// The progress bar added will have the draw target changed to a |
76 | /// remote draw target that is intercepted by the multi progress |
77 | /// object overriding custom [`ProgressDrawTarget`] settings. |
78 | /// |
79 | /// The progress bar will be positioned below all other bars currently |
80 | /// in the [`MultiProgress`]. |
81 | /// |
82 | /// Adding a progress bar that is already a member of the [`MultiProgress`] |
83 | /// will have no effect. |
84 | pub fn add(&self, pb: ProgressBar) -> ProgressBar { |
85 | self.internalize(InsertLocation::End, pb) |
86 | } |
87 | |
88 | /// Inserts a progress bar. |
89 | /// |
90 | /// The progress bar inserted at position `index` will have the draw |
91 | /// target changed to a remote draw target that is intercepted by the |
92 | /// multi progress object overriding custom [`ProgressDrawTarget`] settings. |
93 | /// |
94 | /// If `index >= MultiProgressState::objects.len()`, the progress bar |
95 | /// is added to the end of the list. |
96 | /// |
97 | /// Inserting a progress bar that is already a member of the [`MultiProgress`] |
98 | /// will have no effect. |
99 | pub fn insert(&self, index: usize, pb: ProgressBar) -> ProgressBar { |
100 | self.internalize(InsertLocation::Index(index), pb) |
101 | } |
102 | |
103 | /// Inserts a progress bar from the back. |
104 | /// |
105 | /// The progress bar inserted at position `MultiProgressState::objects.len() - index` |
106 | /// will have the draw target changed to a remote draw target that is |
107 | /// intercepted by the multi progress object overriding custom |
108 | /// [`ProgressDrawTarget`] settings. |
109 | /// |
110 | /// If `index >= MultiProgressState::objects.len()`, the progress bar |
111 | /// is added to the start of the list. |
112 | /// |
113 | /// Inserting a progress bar that is already a member of the [`MultiProgress`] |
114 | /// will have no effect. |
115 | pub fn insert_from_back(&self, index: usize, pb: ProgressBar) -> ProgressBar { |
116 | self.internalize(InsertLocation::IndexFromBack(index), pb) |
117 | } |
118 | |
119 | /// Inserts a progress bar before an existing one. |
120 | /// |
121 | /// The progress bar added will have the draw target changed to a |
122 | /// remote draw target that is intercepted by the multi progress |
123 | /// object overriding custom [`ProgressDrawTarget`] settings. |
124 | /// |
125 | /// Inserting a progress bar that is already a member of the [`MultiProgress`] |
126 | /// will have no effect. |
127 | pub fn insert_before(&self, before: &ProgressBar, pb: ProgressBar) -> ProgressBar { |
128 | self.internalize(InsertLocation::Before(before.index().unwrap()), pb) |
129 | } |
130 | |
131 | /// Inserts a progress bar after an existing one. |
132 | /// |
133 | /// The progress bar added will have the draw target changed to a |
134 | /// remote draw target that is intercepted by the multi progress |
135 | /// object overriding custom [`ProgressDrawTarget`] settings. |
136 | /// |
137 | /// Inserting a progress bar that is already a member of the [`MultiProgress`] |
138 | /// will have no effect. |
139 | pub fn insert_after(&self, after: &ProgressBar, pb: ProgressBar) -> ProgressBar { |
140 | self.internalize(InsertLocation::After(after.index().unwrap()), pb) |
141 | } |
142 | |
143 | /// Removes a progress bar. |
144 | /// |
145 | /// The progress bar is removed only if it was previously inserted or added |
146 | /// by the methods [`MultiProgress::insert`] or [`MultiProgress::add`]. |
147 | /// If the passed progress bar does not satisfy the condition above, |
148 | /// the `remove` method does nothing. |
149 | pub fn remove(&self, pb: &ProgressBar) { |
150 | let mut state = pb.state(); |
151 | let idx = match &state.draw_target.remote() { |
152 | Some((state, idx)) => { |
153 | // Check that this progress bar is owned by the current MultiProgress. |
154 | assert!(Arc::ptr_eq(&self.state, state)); |
155 | *idx |
156 | } |
157 | _ => return, |
158 | }; |
159 | |
160 | state.draw_target = ProgressDrawTarget::hidden(); |
161 | self.state.write().unwrap().remove_idx(idx); |
162 | } |
163 | |
164 | fn internalize(&self, location: InsertLocation, pb: ProgressBar) -> ProgressBar { |
165 | let mut state = self.state.write().unwrap(); |
166 | let idx = state.insert(location); |
167 | drop(state); |
168 | |
169 | pb.set_draw_target(ProgressDrawTarget::new_remote(self.state.clone(), idx)); |
170 | pb |
171 | } |
172 | |
173 | /// Print a log line above all progress bars in the [`MultiProgress`] |
174 | /// |
175 | /// If the draw target is hidden (e.g. when standard output is not a terminal), `println()` |
176 | /// will not do anything. |
177 | pub fn println<I: AsRef<str>>(&self, msg: I) -> io::Result<()> { |
178 | let mut state = self.state.write().unwrap(); |
179 | state.println(msg, Instant::now()) |
180 | } |
181 | |
182 | /// Hide all progress bars temporarily, execute `f`, then redraw the [`MultiProgress`] |
183 | /// |
184 | /// Executes 'f' even if the draw target is hidden. |
185 | /// |
186 | /// Useful for external code that writes to the standard output. |
187 | /// |
188 | /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print |
189 | /// anything on the progress bar will be blocked until `f` finishes. |
190 | /// Therefore, it is recommended to avoid long-running operations in `f`. |
191 | pub fn suspend<F: FnOnce() -> R, R>(&self, f: F) -> R { |
192 | let mut state = self.state.write().unwrap(); |
193 | state.suspend(f, Instant::now()) |
194 | } |
195 | |
196 | pub fn clear(&self) -> io::Result<()> { |
197 | self.state.write().unwrap().clear(Instant::now()) |
198 | } |
199 | |
200 | pub fn is_hidden(&self) -> bool { |
201 | self.state.read().unwrap().is_hidden() |
202 | } |
203 | } |
204 | |
205 | #[derive(Debug)] |
206 | pub(crate) struct MultiState { |
207 | /// The collection of states corresponding to progress bars |
208 | members: Vec<MultiStateMember>, |
209 | /// Set of removed bars, should have corresponding members in the `members` vector with a |
210 | /// `draw_state` of `None`. |
211 | free_set: Vec<usize>, |
212 | /// Indices to the `draw_states` to maintain correct visual order |
213 | ordering: Vec<usize>, |
214 | /// Target for draw operation for MultiProgress |
215 | draw_target: ProgressDrawTarget, |
216 | /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top` |
217 | alignment: MultiProgressAlignment, |
218 | /// Lines to be drawn above everything else in the MultiProgress. These specifically come from |
219 | /// calling `ProgressBar::println` on a pb that is connected to a `MultiProgress`. |
220 | orphan_lines: Vec<String>, |
221 | /// The count of currently visible zombie lines. |
222 | zombie_lines_count: VisualLines, |
223 | } |
224 | |
225 | impl MultiState { |
226 | fn new(draw_target: ProgressDrawTarget) -> Self { |
227 | Self { |
228 | members: vec![], |
229 | free_set: vec![], |
230 | ordering: vec![], |
231 | draw_target, |
232 | alignment: MultiProgressAlignment::default(), |
233 | orphan_lines: Vec::new(), |
234 | zombie_lines_count: VisualLines::default(), |
235 | } |
236 | } |
237 | |
238 | pub(crate) fn mark_zombie(&mut self, index: usize) { |
239 | let width = self.width().map(usize::from); |
240 | |
241 | let member = &mut self.members[index]; |
242 | |
243 | // If the zombie is the first visual bar then we can reap it right now instead of |
244 | // deferring it to the next draw. |
245 | if index != self.ordering.first().copied().unwrap() { |
246 | member.is_zombie = true; |
247 | return; |
248 | } |
249 | |
250 | let line_count = member |
251 | .draw_state |
252 | .as_ref() |
253 | .zip(width) |
254 | .map(|(d, width)| d.visual_line_count(.., width)) |
255 | .unwrap_or_default(); |
256 | |
257 | // Track the total number of zombie lines on the screen |
258 | self.zombie_lines_count = self.zombie_lines_count.saturating_add(line_count); |
259 | |
260 | // Make `DrawTarget` forget about the zombie lines so that they aren't cleared on next draw. |
261 | self.draw_target |
262 | .adjust_last_line_count(LineAdjust::Keep(line_count)); |
263 | |
264 | self.remove_idx(index); |
265 | } |
266 | |
267 | pub(crate) fn draw( |
268 | &mut self, |
269 | mut force_draw: bool, |
270 | extra_lines: Option<Vec<String>>, |
271 | now: Instant, |
272 | ) -> io::Result<()> { |
273 | if panicking() { |
274 | return Ok(()); |
275 | } |
276 | |
277 | let width = match self.width() { |
278 | Some(width) => width as usize, |
279 | None => return Ok(()), |
280 | }; |
281 | |
282 | // Assumption: if extra_lines is not None, then it has at least one line |
283 | debug_assert_eq!( |
284 | extra_lines.is_some(), |
285 | extra_lines.as_ref().map(Vec::len).unwrap_or_default() > 0 |
286 | ); |
287 | |
288 | let mut reap_indices = vec![]; |
289 | |
290 | // Reap all consecutive 'zombie' progress bars from head of the list. |
291 | let mut adjust = VisualLines::default(); |
292 | for &index in &self.ordering { |
293 | let member = &self.members[index]; |
294 | if !member.is_zombie { |
295 | break; |
296 | } |
297 | |
298 | let line_count = member |
299 | .draw_state |
300 | .as_ref() |
301 | .map(|d| d.visual_line_count(.., width)) |
302 | .unwrap_or_default(); |
303 | // Track the total number of zombie lines on the screen. |
304 | self.zombie_lines_count += line_count; |
305 | |
306 | // Track the number of zombie lines that will be drawn by this call to draw. |
307 | adjust += line_count; |
308 | |
309 | reap_indices.push(index); |
310 | } |
311 | |
312 | // If this draw is due to a `println`, then we need to erase all the zombie lines. |
313 | // This is because `println` is supposed to appear above all other elements in the |
314 | // `MultiProgress`. |
315 | if extra_lines.is_some() { |
316 | self.draw_target |
317 | .adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count)); |
318 | self.zombie_lines_count = VisualLines::default(); |
319 | } |
320 | |
321 | let orphan_visual_line_count = visual_line_count(&self.orphan_lines, width); |
322 | force_draw |= orphan_visual_line_count > VisualLines::default(); |
323 | let mut drawable = match self.draw_target.drawable(force_draw, now) { |
324 | Some(drawable) => drawable, |
325 | None => return Ok(()), |
326 | }; |
327 | |
328 | let mut draw_state = drawable.state(); |
329 | draw_state.orphan_lines_count = self.orphan_lines.len(); |
330 | draw_state.alignment = self.alignment; |
331 | |
332 | if let Some(extra_lines) = &extra_lines { |
333 | draw_state.lines.extend_from_slice(extra_lines.as_slice()); |
334 | draw_state.orphan_lines_count += extra_lines.len(); |
335 | } |
336 | |
337 | // Add lines from `ProgressBar::println` call. |
338 | draw_state.lines.append(&mut self.orphan_lines); |
339 | |
340 | for index in &self.ordering { |
341 | let member = &self.members[*index]; |
342 | if let Some(state) = &member.draw_state { |
343 | draw_state.lines.extend_from_slice(&state.lines[..]); |
344 | } |
345 | } |
346 | |
347 | drop(draw_state); |
348 | let drawable = drawable.draw(); |
349 | |
350 | for index in reap_indices { |
351 | self.remove_idx(index); |
352 | } |
353 | |
354 | // The zombie lines were drawn for the last time, so make `DrawTarget` forget about them |
355 | // so they aren't cleared on next draw. |
356 | if extra_lines.is_none() { |
357 | self.draw_target |
358 | .adjust_last_line_count(LineAdjust::Keep(adjust)); |
359 | } |
360 | |
361 | drawable |
362 | } |
363 | |
364 | pub(crate) fn println<I: AsRef<str>>(&mut self, msg: I, now: Instant) -> io::Result<()> { |
365 | let msg = msg.as_ref(); |
366 | |
367 | // If msg is "", make sure a line is still printed |
368 | let lines: Vec<String> = match msg.is_empty() { |
369 | false => msg.lines().map(Into::into).collect(), |
370 | true => vec![String::new()], |
371 | }; |
372 | |
373 | self.draw(true, Some(lines), now) |
374 | } |
375 | |
376 | pub(crate) fn draw_state(&mut self, idx: usize) -> DrawStateWrapper<'_> { |
377 | let member = self.members.get_mut(idx).unwrap(); |
378 | // alignment is handled by the `MultiProgress`'s underlying draw target, so there is no |
379 | // point in propagating it here. |
380 | let state = member.draw_state.get_or_insert(DrawState::default()); |
381 | |
382 | DrawStateWrapper::for_multi(state, &mut self.orphan_lines) |
383 | } |
384 | |
385 | pub(crate) fn is_hidden(&self) -> bool { |
386 | self.draw_target.is_hidden() |
387 | } |
388 | |
389 | pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, f: F, now: Instant) -> R { |
390 | self.clear(now).unwrap(); |
391 | let ret = f(); |
392 | self.draw(true, None, Instant::now()).unwrap(); |
393 | ret |
394 | } |
395 | |
396 | pub(crate) fn width(&self) -> Option<u16> { |
397 | self.draw_target.width() |
398 | } |
399 | |
400 | fn insert(&mut self, location: InsertLocation) -> usize { |
401 | let idx = if let Some(idx) = self.free_set.pop() { |
402 | self.members[idx] = MultiStateMember::default(); |
403 | idx |
404 | } else { |
405 | self.members.push(MultiStateMember::default()); |
406 | self.members.len() - 1 |
407 | }; |
408 | |
409 | match location { |
410 | InsertLocation::End => self.ordering.push(idx), |
411 | InsertLocation::Index(pos) => { |
412 | let pos = Ord::min(pos, self.ordering.len()); |
413 | self.ordering.insert(pos, idx); |
414 | } |
415 | InsertLocation::IndexFromBack(pos) => { |
416 | let pos = self.ordering.len().saturating_sub(pos); |
417 | self.ordering.insert(pos, idx); |
418 | } |
419 | InsertLocation::After(after_idx) => { |
420 | let pos = self.ordering.iter().position(|i| *i == after_idx).unwrap(); |
421 | self.ordering.insert(pos + 1, idx); |
422 | } |
423 | InsertLocation::Before(before_idx) => { |
424 | let pos = self.ordering.iter().position(|i| *i == before_idx).unwrap(); |
425 | self.ordering.insert(pos, idx); |
426 | } |
427 | } |
428 | |
429 | assert_eq!( |
430 | self.len(), |
431 | self.ordering.len(), |
432 | "Draw state is inconsistent" |
433 | ); |
434 | |
435 | idx |
436 | } |
437 | |
438 | fn clear(&mut self, now: Instant) -> io::Result<()> { |
439 | match self.draw_target.drawable(true, now) { |
440 | Some(mut drawable) => { |
441 | // Make the clear operation also wipe out zombie lines |
442 | drawable.adjust_last_line_count(LineAdjust::Clear(self.zombie_lines_count)); |
443 | self.zombie_lines_count = VisualLines::default(); |
444 | drawable.clear() |
445 | } |
446 | None => Ok(()), |
447 | } |
448 | } |
449 | |
450 | fn remove_idx(&mut self, idx: usize) { |
451 | if self.free_set.contains(&idx) { |
452 | return; |
453 | } |
454 | |
455 | self.members[idx] = MultiStateMember::default(); |
456 | self.free_set.push(idx); |
457 | self.ordering.retain(|&x| x != idx); |
458 | |
459 | assert_eq!( |
460 | self.len(), |
461 | self.ordering.len(), |
462 | "Draw state is inconsistent" |
463 | ); |
464 | } |
465 | |
466 | fn len(&self) -> usize { |
467 | self.members.len() - self.free_set.len() |
468 | } |
469 | } |
470 | |
471 | #[derive(Default)] |
472 | struct MultiStateMember { |
473 | /// Draw state will be `None` for members that haven't been drawn before, or for entries that |
474 | /// correspond to something in the free set. |
475 | draw_state: Option<DrawState>, |
476 | /// Whether the corresponding progress bar (more precisely, `BarState`) has been dropped. |
477 | is_zombie: bool, |
478 | } |
479 | |
480 | impl Debug for MultiStateMember { |
481 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
482 | f&mut DebugStruct<'_, '_>.debug_struct("MultiStateElement") |
483 | .field("draw_state", &self.draw_state) |
484 | .field(name:"is_zombie", &self.is_zombie) |
485 | .finish_non_exhaustive() |
486 | } |
487 | } |
488 | |
489 | /// Vertical alignment of a multi progress. |
490 | /// |
491 | /// The alignment controls how the multi progress is aligned if some of its progress bars get removed. |
492 | /// E.g. [`Top`](MultiProgressAlignment::Top) alignment (default), when _progress bar 2_ is removed: |
493 | /// ```ignore |
494 | /// [0/100] progress bar 1 [0/100] progress bar 1 |
495 | /// [0/100] progress bar 2 => [0/100] progress bar 3 |
496 | /// [0/100] progress bar 3 |
497 | /// ``` |
498 | /// |
499 | /// [`Bottom`](MultiProgressAlignment::Bottom) alignment |
500 | /// ```ignore |
501 | /// [0/100] progress bar 1 |
502 | /// [0/100] progress bar 2 => [0/100] progress bar 1 |
503 | /// [0/100] progress bar 3 [0/100] progress bar 3 |
504 | /// ``` |
505 | #[derive(Debug, Copy, Clone)] |
506 | pub enum MultiProgressAlignment { |
507 | Top, |
508 | Bottom, |
509 | } |
510 | |
511 | impl Default for MultiProgressAlignment { |
512 | fn default() -> Self { |
513 | Self::Top |
514 | } |
515 | } |
516 | |
517 | enum InsertLocation { |
518 | End, |
519 | Index(usize), |
520 | IndexFromBack(usize), |
521 | After(usize), |
522 | Before(usize), |
523 | } |
524 | |
525 | #[cfg(test)] |
526 | mod tests { |
527 | use crate::{MultiProgress, ProgressBar, ProgressDrawTarget}; |
528 | |
529 | #[test] |
530 | fn late_pb_drop() { |
531 | let pb = ProgressBar::new(10); |
532 | let mpb = MultiProgress::new(); |
533 | // This clone call is required to trigger a now fixed bug. |
534 | // See <https://github.com/console-rs/indicatif/pull/141> for context |
535 | #[allow(clippy::redundant_clone)] |
536 | mpb.add(pb.clone()); |
537 | } |
538 | |
539 | #[test] |
540 | fn progress_bar_sync_send() { |
541 | let _: Box<dyn Sync> = Box::new(ProgressBar::new(1)); |
542 | let _: Box<dyn Send> = Box::new(ProgressBar::new(1)); |
543 | let _: Box<dyn Sync> = Box::new(MultiProgress::new()); |
544 | let _: Box<dyn Send> = Box::new(MultiProgress::new()); |
545 | } |
546 | |
547 | #[test] |
548 | fn multi_progress_hidden() { |
549 | let mpb = MultiProgress::with_draw_target(ProgressDrawTarget::hidden()); |
550 | let pb = mpb.add(ProgressBar::new(123)); |
551 | pb.finish(); |
552 | } |
553 | |
554 | #[test] |
555 | fn multi_progress_modifications() { |
556 | let mp = MultiProgress::new(); |
557 | let p0 = mp.add(ProgressBar::new(1)); |
558 | let p1 = mp.add(ProgressBar::new(1)); |
559 | let p2 = mp.add(ProgressBar::new(1)); |
560 | let p3 = mp.add(ProgressBar::new(1)); |
561 | mp.remove(&p2); |
562 | mp.remove(&p1); |
563 | let p4 = mp.insert(1, ProgressBar::new(1)); |
564 | |
565 | let state = mp.state.read().unwrap(); |
566 | // the removed place for p1 is reused |
567 | assert_eq!(state.members.len(), 4); |
568 | assert_eq!(state.len(), 3); |
569 | |
570 | // free_set may contain 1 or 2 |
571 | match state.free_set.last() { |
572 | Some(1) => { |
573 | assert_eq!(state.ordering, vec![0, 2, 3]); |
574 | assert!(state.members[1].draw_state.is_none()); |
575 | assert_eq!(p4.index().unwrap(), 2); |
576 | } |
577 | Some(2) => { |
578 | assert_eq!(state.ordering, vec![0, 1, 3]); |
579 | assert!(state.members[2].draw_state.is_none()); |
580 | assert_eq!(p4.index().unwrap(), 1); |
581 | } |
582 | _ => unreachable!(), |
583 | } |
584 | |
585 | assert_eq!(p0.index().unwrap(), 0); |
586 | assert_eq!(p1.index(), None); |
587 | assert_eq!(p2.index(), None); |
588 | assert_eq!(p3.index().unwrap(), 3); |
589 | } |
590 | |
591 | #[test] |
592 | fn multi_progress_insert_from_back() { |
593 | let mp = MultiProgress::new(); |
594 | let p0 = mp.add(ProgressBar::new(1)); |
595 | let p1 = mp.add(ProgressBar::new(1)); |
596 | let p2 = mp.add(ProgressBar::new(1)); |
597 | let p3 = mp.insert_from_back(1, ProgressBar::new(1)); |
598 | let p4 = mp.insert_from_back(10, ProgressBar::new(1)); |
599 | |
600 | let state = mp.state.read().unwrap(); |
601 | assert_eq!(state.ordering, vec![4, 0, 1, 3, 2]); |
602 | assert_eq!(p0.index().unwrap(), 0); |
603 | assert_eq!(p1.index().unwrap(), 1); |
604 | assert_eq!(p2.index().unwrap(), 2); |
605 | assert_eq!(p3.index().unwrap(), 3); |
606 | assert_eq!(p4.index().unwrap(), 4); |
607 | } |
608 | |
609 | #[test] |
610 | fn multi_progress_insert_after() { |
611 | let mp = MultiProgress::new(); |
612 | let p0 = mp.add(ProgressBar::new(1)); |
613 | let p1 = mp.add(ProgressBar::new(1)); |
614 | let p2 = mp.add(ProgressBar::new(1)); |
615 | let p3 = mp.insert_after(&p2, ProgressBar::new(1)); |
616 | let p4 = mp.insert_after(&p0, ProgressBar::new(1)); |
617 | |
618 | let state = mp.state.read().unwrap(); |
619 | assert_eq!(state.ordering, vec![0, 4, 1, 2, 3]); |
620 | assert_eq!(p0.index().unwrap(), 0); |
621 | assert_eq!(p1.index().unwrap(), 1); |
622 | assert_eq!(p2.index().unwrap(), 2); |
623 | assert_eq!(p3.index().unwrap(), 3); |
624 | assert_eq!(p4.index().unwrap(), 4); |
625 | } |
626 | |
627 | #[test] |
628 | fn multi_progress_insert_before() { |
629 | let mp = MultiProgress::new(); |
630 | let p0 = mp.add(ProgressBar::new(1)); |
631 | let p1 = mp.add(ProgressBar::new(1)); |
632 | let p2 = mp.add(ProgressBar::new(1)); |
633 | let p3 = mp.insert_before(&p0, ProgressBar::new(1)); |
634 | let p4 = mp.insert_before(&p2, ProgressBar::new(1)); |
635 | |
636 | let state = mp.state.read().unwrap(); |
637 | assert_eq!(state.ordering, vec![3, 0, 1, 4, 2]); |
638 | assert_eq!(p0.index().unwrap(), 0); |
639 | assert_eq!(p1.index().unwrap(), 1); |
640 | assert_eq!(p2.index().unwrap(), 2); |
641 | assert_eq!(p3.index().unwrap(), 3); |
642 | assert_eq!(p4.index().unwrap(), 4); |
643 | } |
644 | |
645 | #[test] |
646 | fn multi_progress_insert_before_and_after() { |
647 | let mp = MultiProgress::new(); |
648 | let p0 = mp.add(ProgressBar::new(1)); |
649 | let p1 = mp.add(ProgressBar::new(1)); |
650 | let p2 = mp.add(ProgressBar::new(1)); |
651 | let p3 = mp.insert_before(&p0, ProgressBar::new(1)); |
652 | let p4 = mp.insert_after(&p3, ProgressBar::new(1)); |
653 | let p5 = mp.insert_after(&p3, ProgressBar::new(1)); |
654 | let p6 = mp.insert_before(&p1, ProgressBar::new(1)); |
655 | |
656 | let state = mp.state.read().unwrap(); |
657 | assert_eq!(state.ordering, vec![3, 5, 4, 0, 6, 1, 2]); |
658 | assert_eq!(p0.index().unwrap(), 0); |
659 | assert_eq!(p1.index().unwrap(), 1); |
660 | assert_eq!(p2.index().unwrap(), 2); |
661 | assert_eq!(p3.index().unwrap(), 3); |
662 | assert_eq!(p4.index().unwrap(), 4); |
663 | assert_eq!(p5.index().unwrap(), 5); |
664 | assert_eq!(p6.index().unwrap(), 6); |
665 | } |
666 | |
667 | #[test] |
668 | fn multi_progress_multiple_remove() { |
669 | let mp = MultiProgress::new(); |
670 | let p0 = mp.add(ProgressBar::new(1)); |
671 | let p1 = mp.add(ProgressBar::new(1)); |
672 | // double remove beyond the first one have no effect |
673 | mp.remove(&p0); |
674 | mp.remove(&p0); |
675 | mp.remove(&p0); |
676 | |
677 | let state = mp.state.read().unwrap(); |
678 | // the removed place for p1 is reused |
679 | assert_eq!(state.members.len(), 2); |
680 | assert_eq!(state.free_set.len(), 1); |
681 | assert_eq!(state.len(), 1); |
682 | assert!(state.members[0].draw_state.is_none()); |
683 | assert_eq!(state.free_set.last(), Some(&0)); |
684 | |
685 | assert_eq!(state.ordering, vec![1]); |
686 | assert_eq!(p0.index(), None); |
687 | assert_eq!(p1.index().unwrap(), 1); |
688 | } |
689 | |
690 | #[test] |
691 | fn mp_no_crash_double_add() { |
692 | let mp = MultiProgress::new(); |
693 | let pb = mp.add(ProgressBar::new(10)); |
694 | mp.add(pb); |
695 | } |
696 | } |
697 |
Definitions
- MultiProgress
- state
- default
- new
- with_draw_target
- set_draw_target
- set_move_cursor
- set_alignment
- add
- insert
- insert_from_back
- insert_before
- insert_after
- remove
- internalize
- println
- suspend
- clear
- is_hidden
- MultiState
- members
- free_set
- ordering
- draw_target
- alignment
- orphan_lines
- zombie_lines_count
- new
- mark_zombie
- draw
- println
- draw_state
- is_hidden
- suspend
- width
- insert
- clear
- remove_idx
- len
- MultiStateMember
- draw_state
- is_zombie
- fmt
- MultiProgressAlignment
- Top
- Bottom
- default
- InsertLocation
- End
- Index
- IndexFromBack
- After
Learn Rust with the experts
Find out more