diff --git a/README.md b/README.md
index 2779c65..5c4d580 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ make
* [Progress Bar](#progress-bar)
* [Block Progress Bar](#block-progress-bar)
* [Multi Progress](#multiprogress)
+* [Dynamic Progress](#dynamicprogress)
* [Progress Spinner](#progress-spinner)
* [Contributing](#contributing)
* [License](#license)
@@ -350,6 +351,159 @@ int main() {
}
```
+# DynamicProgress
+
+`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`.
+
+Below is an example `DynamicProgress` object that manages six `ProgressBar` objects. Three of these bars are added dynamically.
+
+
+
+
+
+```cpp
+#include
+#include
+using namespace indicators;
+
+int main() {
+
+ ProgressBar bar1{option::BarWidth{50}, option::ForegroundColor{Color::red},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"5c90d4a2d1a8: Downloading "}};
+
+ ProgressBar bar2{option::BarWidth{50}, option::ForegroundColor{Color::yellow},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"22337bfd13a9: Downloading "}};
+
+ ProgressBar bar3{option::BarWidth{50}, option::ForegroundColor{Color::green},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"10f26c680a34: Downloading "}};
+
+ ProgressBar bar4{option::BarWidth{50}, option::ForegroundColor{Color::white},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"6364e0d7a283: Downloading "}};
+
+ ProgressBar bar5{option::BarWidth{50}, option::ForegroundColor{Color::blue},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"ff1356ba118b: Downloading "}};
+
+ ProgressBar bar6{option::BarWidth{50}, option::ForegroundColor{Color::cyan},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"5a17453338b4: Downloading "}};
+
+ std::cout << termcolor::bold << termcolor::white << "Pulling image foo:bar/baz\n";
+
+ // Construct with 3 progress bars. We'll add 3 more at a later point
+ DynamicProgress bars(bar1, bar2, bar3);
+
+ // Do not hide bars when completed
+ bars.set_option(option::HideBarWhenComplete{false});
+
+ std::thread fourth_job, fifth_job, sixth_job;
+
+ auto job4 = [&bars](size_t i) {
+ while (true) {
+ bars[i].tick();
+ if (bars[i].is_completed()) {
+ bars[i].set_option(option::PrefixText{"6364e0d7a283: Pull complete "});
+ bars[i].mark_as_completed();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+ };
+
+ auto job5 = [&bars](size_t i) {
+ while (true) {
+ bars[i].tick();
+ if (bars[i].is_completed()) {
+ bars[i].set_option(option::PrefixText{"ff1356ba118b: Pull complete "});
+ bars[i].mark_as_completed();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ };
+
+ auto job6 = [&bars](size_t i) {
+ while (true) {
+ bars[i].tick();
+ if (bars[i].is_completed()) {
+ bars[i].set_option(option::PrefixText{"5a17453338b4: Pull complete "});
+ bars[i].mark_as_completed();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(40));
+ }
+ };
+
+ auto job1 = [&bars, &bar6, &sixth_job, &job6]() {
+ while (true) {
+ bars[0].tick();
+ if (bars[0].is_completed()) {
+ bars[0].set_option(option::PrefixText{"5c90d4a2d1a8: Pull complete "});
+ // bar1 is completed, adding bar6
+ auto i = bars.push_back(bar6);
+ sixth_job = std::thread(job6, i);
+ sixth_job.join();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(140));
+ }
+ };
+
+ auto job2 = [&bars, &bar5, &fifth_job, &job5]() {
+ while (true) {
+ bars[1].tick();
+ if (bars[1].is_completed()) {
+ bars[1].set_option(option::PrefixText{"22337bfd13a9: Pull complete "});
+ // bar2 is completed, adding bar5
+ auto i = bars.push_back(bar5);
+ fifth_job = std::thread(job5, i);
+ fifth_job.join();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(25));
+ }
+ };
+
+ auto job3 = [&bars, &bar4, &fourth_job, &job4]() {
+ while (true) {
+ bars[2].tick();
+ if (bars[2].is_completed()) {
+ bars[2].set_option(option::PrefixText{"10f26c680a34: Pull complete "});
+ // bar3 is completed, adding bar4
+ auto i = bars.push_back(bar4);
+ fourth_job = std::thread(job4, i);
+ fourth_job.join();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+ };
+
+ std::thread first_job(job1);
+ std::thread second_job(job2);
+ std::thread third_job(job3);
+
+ third_job.join();
+ second_job.join();
+ first_job.join();
+
+ std::cout << termcolor::bold << termcolor::green << "✔ Downloaded image foo/bar:baz" << std::endl;
+ std::cout << termcolor::reset;
+
+ return 0;
+}
+```
+
+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:
+
+
+
+
+
# Progress Spinner
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:
diff --git a/img/dynamic_progress_bar.gif b/img/dynamic_progress_bar.gif
new file mode 100644
index 0000000..2a9381f
Binary files /dev/null and b/img/dynamic_progress_bar.gif differ
diff --git a/img/dynamic_progress_bar_hide_completed.gif b/img/dynamic_progress_bar_hide_completed.gif
new file mode 100644
index 0000000..e1c73cc
Binary files /dev/null and b/img/dynamic_progress_bar_hide_completed.gif differ
diff --git a/include/indicators/block_progress_bar.hpp b/include/indicators/block_progress_bar.hpp
index 3857320..0ce42fa 100644
--- a/include/indicators/block_progress_bar.hpp
+++ b/include/indicators/block_progress_bar.hpp
@@ -164,6 +164,7 @@ class BlockProgressBar {
std::mutex mutex_;
template friend class MultiProgress;
+ template friend class DynamicProgress;
std::atomic multi_progress_mode_{false};
void save_start_time() {
diff --git a/include/indicators/dynamic_progress.hpp b/include/indicators/dynamic_progress.hpp
new file mode 100644
index 0000000..bc8f8a7
--- /dev/null
+++ b/include/indicators/dynamic_progress.hpp
@@ -0,0 +1,138 @@
+/*
+Activity Indicators for Modern C++
+https://github.com/p-ranav/indicators
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2019 Pranav Srinivas Kumar .
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace indicators {
+
+template class DynamicProgress {
+ using Settings = std::tuple;
+
+public:
+ template explicit DynamicProgress(Indicators &... bars) {
+ bars_ = {bars...};
+ for (auto &bar : bars_) {
+ bar.get().multi_progress_mode_ = true;
+ ++total_count_;
+ ++incomplete_count_;
+ }
+ }
+
+ Indicator &operator[](size_t index) {
+ print_progress();
+ std::lock_guard lock{mutex_};
+ return bars_[index].get();
+ }
+
+ size_t push_back(Indicator &bar) {
+ std::lock_guard lock{mutex_};
+ bar.multi_progress_mode_ = true;
+ bars_.push_back(bar);
+ return bars_.size() - 1;
+ }
+
+ template
+ void set_option(details::Setting &&setting) {
+ static_assert(!std::is_same(
+ std::declval()))>::type>::value,
+ "Setting has wrong type!");
+ std::lock_guard lock(mutex_);
+ get_value() = std::move(setting).value;
+ }
+
+ template
+ void set_option(const details::Setting &setting) {
+ static_assert(!std::is_same(
+ std::declval()))>::type>::value,
+ "Setting has wrong type!");
+ std::lock_guard lock(mutex_);
+ get_value() = setting.value;
+ }
+
+private:
+ Settings settings_;
+ std::atomic started_{false};
+ std::mutex mutex_;
+ std::vector> bars_;
+ std::atomic total_count_{0};
+ std::atomic incomplete_count_{0};
+
+ template
+ auto get_value() -> decltype((details::get_value(std::declval()).value)) {
+ return details::get_value(settings_).value;
+ }
+
+ template
+ auto get_value() const
+ -> decltype((details::get_value(std::declval()).value)) {
+ return details::get_value(settings_).value;
+ }
+
+ void print_progress() {
+ std::lock_guard lock{mutex_};
+ auto &hide_bar_when_complete = get_value();
+ if (hide_bar_when_complete) {
+ // Hide completed bars
+ if (started_) {
+ for (size_t i = 0; i < incomplete_count_; ++i)
+ std::cout << "\033[A\r\033[K" << std::flush;
+ }
+ incomplete_count_ = 0;
+ for (auto &bar : bars_) {
+ if (!bar.get().is_completed()) {
+ bar.get().print_progress(true);
+ std::cout << "\n";
+ ++incomplete_count_;
+ }
+ }
+ if (!started_)
+ started_ = true;
+ } else {
+ // Don't hide any bars
+ if (started_) {
+ for (size_t i = 0; i < total_count_; ++i)
+ std::cout << "\x1b[A";
+ }
+ for (auto &bar: bars_) {
+ bar.get().print_progress(true);
+ std::cout << "\n";
+ }
+ if (!started_)
+ started_ = true;
+ }
+ total_count_ = bars_.size();
+ std::cout << termcolor::reset;
+ }
+};
+
+} // namespace indicators
diff --git a/include/indicators/progress_bar.hpp b/include/indicators/progress_bar.hpp
index 7235f54..200eac5 100644
--- a/include/indicators/progress_bar.hpp
+++ b/include/indicators/progress_bar.hpp
@@ -176,6 +176,7 @@ class ProgressBar {
std::mutex mutex_;
template friend class MultiProgress;
+ template friend class DynamicProgress;
std::atomic multi_progress_mode_{false};
void save_start_time() {
@@ -189,13 +190,13 @@ class ProgressBar {
}
void print_progress(bool from_multi_progress = false) {
+ std::lock_guard lock{mutex_};
if (multi_progress_mode_ && !from_multi_progress) {
if (progress_ > 100.0) {
get_value() = true;
}
return;
}
- std::lock_guard lock{mutex_};
auto now = std::chrono::high_resolution_clock::now();
if (!get_value())
elapsed_ = std::chrono::duration_cast(now - start_time_point_);
diff --git a/include/indicators/setting.hpp b/include/indicators/setting.hpp
index 8bc25ad..c444589 100644
--- a/include/indicators/setting.hpp
+++ b/include/indicators/setting.hpp
@@ -85,7 +85,8 @@ enum class ProgressBarOption {
saved_start_time,
foreground_color,
spinner_show,
- spinner_states
+ spinner_states,
+ hide_bar_when_complete
};
template struct Setting {
@@ -196,5 +197,7 @@ using ForegroundColor = details::Setting;
using SpinnerStates =
details::Setting, details::ProgressBarOption::spinner_states>;
+using HideBarWhenComplete =
+ details::BooleanSetting;
} // namespace option
} // namespace indicators
\ No newline at end of file
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
index 01b3212..9aa8218 100644
--- a/samples/CMakeLists.txt
+++ b/samples/CMakeLists.txt
@@ -22,3 +22,6 @@ target_link_libraries(multi_progress_bar PRIVATE indicators::indicators)
add_executable(multi_block_progress_bar multi_block_progress_bar.cpp)
target_link_libraries(multi_block_progress_bar PRIVATE indicators::indicators)
+
+add_executable(dynamic_progress dynamic_progress.cpp)
+target_link_libraries(dynamic_progress PRIVATE indicators::indicators)
diff --git a/samples/dynamic_progress.cpp b/samples/dynamic_progress.cpp
new file mode 100644
index 0000000..d16b04c
--- /dev/null
+++ b/samples/dynamic_progress.cpp
@@ -0,0 +1,131 @@
+#include
+#include
+using namespace indicators;
+
+int main() {
+
+ ProgressBar bar1{option::BarWidth{50}, option::ForegroundColor{Color::red},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"5c90d4a2d1a8: Downloading "}};
+
+ ProgressBar bar2{option::BarWidth{50}, option::ForegroundColor{Color::yellow},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"22337bfd13a9: Downloading "}};
+
+ ProgressBar bar3{option::BarWidth{50}, option::ForegroundColor{Color::green},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"10f26c680a34: Downloading "}};
+
+ ProgressBar bar4{option::BarWidth{50}, option::ForegroundColor{Color::white},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"6364e0d7a283: Downloading "}};
+
+ ProgressBar bar5{option::BarWidth{50}, option::ForegroundColor{Color::blue},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"ff1356ba118b: Downloading "}};
+
+ ProgressBar bar6{option::BarWidth{50}, option::ForegroundColor{Color::cyan},
+ option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
+ option::PrefixText{"5a17453338b4: Downloading "}};
+
+ std::cout << termcolor::bold << termcolor::white << "Pulling image foo:bar/baz\n";
+
+ DynamicProgress bars(bar1, bar2, bar3);
+ bars.set_option(option::HideBarWhenComplete{false});
+
+ std::thread fourth_job, fifth_job, sixth_job;
+
+ auto job4 = [&bars](size_t i) {
+ while (true) {
+ bars[i].tick();
+ if (bars[i].is_completed()) {
+ bars[i].set_option(option::PrefixText{"6364e0d7a283: Pull complete "});
+ bars[i].mark_as_completed();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+ };
+
+ auto job5 = [&bars](size_t i) {
+ while (true) {
+ bars[i].tick();
+ if (bars[i].is_completed()) {
+ bars[i].set_option(option::PrefixText{"ff1356ba118b: Pull complete "});
+ bars[i].mark_as_completed();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ }
+ };
+
+ auto job6 = [&bars](size_t i) {
+ while (true) {
+ bars[i].tick();
+ if (bars[i].is_completed()) {
+ bars[i].set_option(option::PrefixText{"5a17453338b4: Pull complete "});
+ bars[i].mark_as_completed();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(40));
+ }
+ };
+
+ auto job1 = [&bars, &bar6, &sixth_job, &job6]() {
+ while (true) {
+ bars[0].tick();
+ if (bars[0].is_completed()) {
+ bars[0].set_option(option::PrefixText{"5c90d4a2d1a8: Pull complete "});
+ // bar1 is completed, adding bar6
+ auto i = bars.push_back(bar6);
+ sixth_job = std::thread(job6, i);
+ sixth_job.join();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(140));
+ }
+ };
+
+ auto job2 = [&bars, &bar5, &fifth_job, &job5]() {
+ while (true) {
+ bars[1].tick();
+ if (bars[1].is_completed()) {
+ bars[1].set_option(option::PrefixText{"22337bfd13a9: Pull complete "});
+ // bar2 is completed, adding bar5
+ auto i = bars.push_back(bar5);
+ fifth_job = std::thread(job5, i);
+ fifth_job.join();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(25));
+ }
+ };
+
+ auto job3 = [&bars, &bar4, &fourth_job, &job4]() {
+ while (true) {
+ bars[2].tick();
+ if (bars[2].is_completed()) {
+ bars[2].set_option(option::PrefixText{"10f26c680a34: Pull complete "});
+ // bar3 is completed, adding bar4
+ auto i = bars.push_back(bar4);
+ fourth_job = std::thread(job4, i);
+ fourth_job.join();
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+ };
+
+ std::thread first_job(job1);
+ std::thread second_job(job2);
+ std::thread third_job(job3);
+
+ third_job.join();
+ second_job.join();
+ first_job.join();
+
+ std::cout << termcolor::bold << termcolor::green << "✔ Downloaded image foo/bar:baz" << std::endl;
+ std::cout << termcolor::reset;
+
+ return 0;
+}