Skip to content

Commit ec19736

Browse files
authored
Merge pull request #32 from p-ranav/feature/dynamic-progress
Feature/dynamic progress
2 parents 7298c71 + 5049e70 commit ec19736

File tree

9 files changed

+433
-2
lines changed

9 files changed

+433
-2
lines changed

README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ make
3939
* [Progress Bar](#progress-bar)
4040
* [Block Progress Bar](#block-progress-bar)
4141
* [Multi Progress](#multiprogress)
42+
* [Dynamic Progress](#dynamicprogress)
4243
* [Progress Spinner](#progress-spinner)
4344
* [Contributing](#contributing)
4445
* [License](#license)
@@ -350,6 +351,159 @@ int main() {
350351
}
351352
```
352353

354+
# DynamicProgress
355+
356+
`DynamicProgress` is a container class, similar to `MultiProgress`, for managing multiple progress bars. As the name suggests, with `DynamicProgress`, you can dynamically add new progress bars. Simply call `bars.push_back`.
357+
358+
Below is an example `DynamicProgress` object that manages six `ProgressBar` objects. Three of these bars are added dynamically.
359+
360+
<p align="center">
361+
<img src="img/dynamic_progress_bar.gif"/>
362+
</p>
363+
364+
```cpp
365+
#include <indicators/dynamic_progress.hpp>
366+
#include <indicators/progress_bar.hpp>
367+
using namespace indicators;
368+
369+
int main() {
370+
371+
ProgressBar bar1{option::BarWidth{50}, option::ForegroundColor{Color::red},
372+
option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
373+
option::PrefixText{"5c90d4a2d1a8: Downloading "}};
374+
375+
ProgressBar bar2{option::BarWidth{50}, option::ForegroundColor{Color::yellow},
376+
option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
377+
option::PrefixText{"22337bfd13a9: Downloading "}};
378+
379+
ProgressBar bar3{option::BarWidth{50}, option::ForegroundColor{Color::green},
380+
option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
381+
option::PrefixText{"10f26c680a34: Downloading "}};
382+
383+
ProgressBar bar4{option::BarWidth{50}, option::ForegroundColor{Color::white},
384+
option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
385+
option::PrefixText{"6364e0d7a283: Downloading "}};
386+
387+
ProgressBar bar5{option::BarWidth{50}, option::ForegroundColor{Color::blue},
388+
option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
389+
option::PrefixText{"ff1356ba118b: Downloading "}};
390+
391+
ProgressBar bar6{option::BarWidth{50}, option::ForegroundColor{Color::cyan},
392+
option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
393+
option::PrefixText{"5a17453338b4: Downloading "}};
394+
395+
std::cout << termcolor::bold << termcolor::white << "Pulling image foo:bar/baz\n";
396+
397+
// Construct with 3 progress bars. We'll add 3 more at a later point
398+
DynamicProgress<ProgressBar> bars(bar1, bar2, bar3);
399+
400+
// Do not hide bars when completed
401+
bars.set_option(option::HideBarWhenComplete{false});
402+
403+
std::thread fourth_job, fifth_job, sixth_job;
404+
405+
auto job4 = [&bars](size_t i) {
406+
while (true) {
407+
bars[i].tick();
408+
if (bars[i].is_completed()) {
409+
bars[i].set_option(option::PrefixText{"6364e0d7a283: Pull complete "});
410+
bars[i].mark_as_completed();
411+
break;
412+
}
413+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
414+
}
415+
};
416+
417+
auto job5 = [&bars](size_t i) {
418+
while (true) {
419+
bars[i].tick();
420+
if (bars[i].is_completed()) {
421+
bars[i].set_option(option::PrefixText{"ff1356ba118b: Pull complete "});
422+
bars[i].mark_as_completed();
423+
break;
424+
}
425+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
426+
}
427+
};
428+
429+
auto job6 = [&bars](size_t i) {
430+
while (true) {
431+
bars[i].tick();
432+
if (bars[i].is_completed()) {
433+
bars[i].set_option(option::PrefixText{"5a17453338b4: Pull complete "});
434+
bars[i].mark_as_completed();
435+
break;
436+
}
437+
std::this_thread::sleep_for(std::chrono::milliseconds(40));
438+
}
439+
};
440+
441+
auto job1 = [&bars, &bar6, &sixth_job, &job6]() {
442+
while (true) {
443+
bars[0].tick();
444+
if (bars[0].is_completed()) {
445+
bars[0].set_option(option::PrefixText{"5c90d4a2d1a8: Pull complete "});
446+
// bar1 is completed, adding bar6
447+
auto i = bars.push_back(bar6);
448+
sixth_job = std::thread(job6, i);
449+
sixth_job.join();
450+
break;
451+
}
452+
std::this_thread::sleep_for(std::chrono::milliseconds(140));
453+
}
454+
};
455+
456+
auto job2 = [&bars, &bar5, &fifth_job, &job5]() {
457+
while (true) {
458+
bars[1].tick();
459+
if (bars[1].is_completed()) {
460+
bars[1].set_option(option::PrefixText{"22337bfd13a9: Pull complete "});
461+
// bar2 is completed, adding bar5
462+
auto i = bars.push_back(bar5);
463+
fifth_job = std::thread(job5, i);
464+
fifth_job.join();
465+
break;
466+
}
467+
std::this_thread::sleep_for(std::chrono::milliseconds(25));
468+
}
469+
};
470+
471+
auto job3 = [&bars, &bar4, &fourth_job, &job4]() {
472+
while (true) {
473+
bars[2].tick();
474+
if (bars[2].is_completed()) {
475+
bars[2].set_option(option::PrefixText{"10f26c680a34: Pull complete "});
476+
// bar3 is completed, adding bar4
477+
auto i = bars.push_back(bar4);
478+
fourth_job = std::thread(job4, i);
479+
fourth_job.join();
480+
break;
481+
}
482+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
483+
}
484+
};
485+
486+
std::thread first_job(job1);
487+
std::thread second_job(job2);
488+
std::thread third_job(job3);
489+
490+
third_job.join();
491+
second_job.join();
492+
first_job.join();
493+
494+
std::cout << termcolor::bold << termcolor::green << "✔ Downloaded image foo/bar:baz" << std::endl;
495+
std::cout << termcolor::reset;
496+
497+
return 0;
498+
}
499+
```
500+
501+
In the above code, notice the option `bars.set_option(option::HideBarWhenComplete{true});`. Yes, you can hide progress bars as and when they complete by setting this option to `true`. If you do so, the above example will look like this:
502+
503+
<p align="center">
504+
<img src="img/dynamic_progress_bar_hide_completed.gif"/>
505+
</p>
506+
353507
# Progress Spinner
354508

355509
To introduce a progress spinner in your application, include `indicators/progress_spinner.hpp` and create a `ProgressSpinner` object. Here's the general structure of a progress spinner:

img/dynamic_progress_bar.gif

16.1 MB
Loading
9.12 MB
Loading

include/indicators/block_progress_bar.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class BlockProgressBar {
164164
std::mutex mutex_;
165165

166166
template <typename Indicator, size_t count> friend class MultiProgress;
167+
template <typename Indicator> friend class DynamicProgress;
167168
std::atomic<bool> multi_progress_mode_{false};
168169

169170
void save_start_time() {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Activity Indicators for Modern C++
3+
https://github.com/p-ranav/indicators
4+
5+
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
6+
SPDX-License-Identifier: MIT
7+
Copyright (c) 2019 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>.
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in all
17+
copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
SOFTWARE.
26+
*/
27+
#pragma once
28+
#include <atomic>
29+
#include <functional>
30+
#include <indicators/color.hpp>
31+
#include <indicators/setting.hpp>
32+
#include <iostream>
33+
#include <mutex>
34+
#include <vector>
35+
36+
namespace indicators {
37+
38+
template <typename Indicator> class DynamicProgress {
39+
using Settings = std::tuple<option::HideBarWhenComplete>;
40+
41+
public:
42+
template <typename... Indicators> explicit DynamicProgress(Indicators &... bars) {
43+
bars_ = {bars...};
44+
for (auto &bar : bars_) {
45+
bar.get().multi_progress_mode_ = true;
46+
++total_count_;
47+
++incomplete_count_;
48+
}
49+
}
50+
51+
Indicator &operator[](size_t index) {
52+
print_progress();
53+
std::lock_guard<std::mutex> lock{mutex_};
54+
return bars_[index].get();
55+
}
56+
57+
size_t push_back(Indicator &bar) {
58+
std::lock_guard<std::mutex> lock{mutex_};
59+
bar.multi_progress_mode_ = true;
60+
bars_.push_back(bar);
61+
return bars_.size() - 1;
62+
}
63+
64+
template <typename T, details::ProgressBarOption id>
65+
void set_option(details::Setting<T, id> &&setting) {
66+
static_assert(!std::is_same<T, typename std::decay<decltype(details::get_value<id>(
67+
std::declval<Settings>()))>::type>::value,
68+
"Setting has wrong type!");
69+
std::lock_guard<std::mutex> lock(mutex_);
70+
get_value<id>() = std::move(setting).value;
71+
}
72+
73+
template <typename T, details::ProgressBarOption id>
74+
void set_option(const details::Setting<T, id> &setting) {
75+
static_assert(!std::is_same<T, typename std::decay<decltype(details::get_value<id>(
76+
std::declval<Settings>()))>::type>::value,
77+
"Setting has wrong type!");
78+
std::lock_guard<std::mutex> lock(mutex_);
79+
get_value<id>() = setting.value;
80+
}
81+
82+
private:
83+
Settings settings_;
84+
std::atomic<bool> started_{false};
85+
std::mutex mutex_;
86+
std::vector<std::reference_wrapper<Indicator>> bars_;
87+
std::atomic<size_t> total_count_{0};
88+
std::atomic<size_t> incomplete_count_{0};
89+
90+
template <details::ProgressBarOption id>
91+
auto get_value() -> decltype((details::get_value<id>(std::declval<Settings &>()).value)) {
92+
return details::get_value<id>(settings_).value;
93+
}
94+
95+
template <details::ProgressBarOption id>
96+
auto get_value() const
97+
-> decltype((details::get_value<id>(std::declval<const Settings &>()).value)) {
98+
return details::get_value<id>(settings_).value;
99+
}
100+
101+
void print_progress() {
102+
std::lock_guard<std::mutex> lock{mutex_};
103+
auto &hide_bar_when_complete = get_value<details::ProgressBarOption::hide_bar_when_complete>();
104+
if (hide_bar_when_complete) {
105+
// Hide completed bars
106+
if (started_) {
107+
for (size_t i = 0; i < incomplete_count_; ++i)
108+
std::cout << "\033[A\r\033[K" << std::flush;
109+
}
110+
incomplete_count_ = 0;
111+
for (auto &bar : bars_) {
112+
if (!bar.get().is_completed()) {
113+
bar.get().print_progress(true);
114+
std::cout << "\n";
115+
++incomplete_count_;
116+
}
117+
}
118+
if (!started_)
119+
started_ = true;
120+
} else {
121+
// Don't hide any bars
122+
if (started_) {
123+
for (size_t i = 0; i < total_count_; ++i)
124+
std::cout << "\x1b[A";
125+
}
126+
for (auto &bar: bars_) {
127+
bar.get().print_progress(true);
128+
std::cout << "\n";
129+
}
130+
if (!started_)
131+
started_ = true;
132+
}
133+
total_count_ = bars_.size();
134+
std::cout << termcolor::reset;
135+
}
136+
};
137+
138+
} // namespace indicators

include/indicators/progress_bar.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class ProgressBar {
176176
std::mutex mutex_;
177177

178178
template <typename Indicator, size_t count> friend class MultiProgress;
179+
template <typename Indicator> friend class DynamicProgress;
179180
std::atomic<bool> multi_progress_mode_{false};
180181

181182
void save_start_time() {
@@ -189,13 +190,13 @@ class ProgressBar {
189190
}
190191

191192
void print_progress(bool from_multi_progress = false) {
193+
std::lock_guard<std::mutex> lock{mutex_};
192194
if (multi_progress_mode_ && !from_multi_progress) {
193195
if (progress_ > 100.0) {
194196
get_value<details::ProgressBarOption::completed>() = true;
195197
}
196198
return;
197199
}
198-
std::lock_guard<std::mutex> lock{mutex_};
199200
auto now = std::chrono::high_resolution_clock::now();
200201
if (!get_value<details::ProgressBarOption::completed>())
201202
elapsed_ = std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time_point_);

include/indicators/setting.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ enum class ProgressBarOption {
8585
saved_start_time,
8686
foreground_color,
8787
spinner_show,
88-
spinner_states
88+
spinner_states,
89+
hide_bar_when_complete
8990
};
9091

9192
template <typename T, ProgressBarOption Id> struct Setting {
@@ -196,5 +197,7 @@ using ForegroundColor = details::Setting<Color, details::ProgressBarOption::fore
196197
using ShowSpinner = details::BooleanSetting<details::ProgressBarOption::spinner_show>;
197198
using SpinnerStates =
198199
details::Setting<std::vector<std::string>, details::ProgressBarOption::spinner_states>;
200+
using HideBarWhenComplete =
201+
details::BooleanSetting<details::ProgressBarOption::hide_bar_when_complete>;
199202
} // namespace option
200203
} // namespace indicators

samples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ target_link_libraries(multi_progress_bar PRIVATE indicators::indicators)
2222

2323
add_executable(multi_block_progress_bar multi_block_progress_bar.cpp)
2424
target_link_libraries(multi_block_progress_bar PRIVATE indicators::indicators)
25+
26+
add_executable(dynamic_progress dynamic_progress.cpp)
27+
target_link_libraries(dynamic_progress PRIVATE indicators::indicators)

0 commit comments

Comments
 (0)