diff --git a/README.md b/README.md index 46d748f..fddada5 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ int main() { } ``` -## Multi-threaded Example +## Multi-threaded Update ```cpp #include @@ -297,6 +297,110 @@ int main() { } ``` +# MultiProgress + +`indicators` supports management of multiple progress bars with the `MultiProgress` class. + +`template class MultiProgress` is a class template that holds references to multiple progress bars and provides a safe interface to update the state of each bar. `MultiProgress` works with both `ProgressBar` and `BlockProgressBar` classes. + +Below is an example `MultiProgress` object that manages three `ProgressBar` objects. + +

+ +

+ +```cpp +#include +#include + +int main() { + + // Configure first progress bar + indicators::ProgressBar bar1; + bar1.set_bar_width(50); + bar1.start_bar_with("["); + bar1.fill_bar_progress_with("■"); + bar1.lead_bar_progress_with("■"); + bar1.fill_bar_remainder_with(" "); + bar1.end_bar_with(" ]"); + bar1.set_foreground_color(indicators::Color::YELLOW); + bar1.show_elapsed_time(); + bar1.show_remaining_time(); + bar1.set_prefix_text("Progress Bar #1 "); + + // Configure second progress bar + indicators::ProgressBar bar2; + bar2.set_bar_width(50); + bar2.start_bar_with("["); + bar2.fill_bar_progress_with("="); + bar2.lead_bar_progress_with(">"); + bar2.fill_bar_remainder_with(" "); + bar2.end_bar_with(" ]"); + bar2.set_foreground_color(indicators::Color::CYAN); + bar2.show_elapsed_time(); + bar2.show_remaining_time(); + bar2.set_prefix_text("Progress Bar #2 "); + + // Configure third progress bar + indicators::ProgressBar bar3; + bar3.set_bar_width(50); + bar3.start_bar_with("["); + bar3.fill_bar_progress_with("#"); + bar3.lead_bar_progress_with("#"); + bar3.fill_bar_remainder_with(" "); + bar3.end_bar_with(" ]"); + bar3.set_foreground_color(indicators::Color::RED); + bar3.show_elapsed_time(); + bar3.show_remaining_time(); + bar3.set_prefix_text("Progress Bar #3 "); + + // Construct MultiProgress object + indicators::MultiProgress bars; + bars.insert<0>(bar1); + bars.insert<1>(bar2); + bars.insert<2>(bar3); + + std::cout << "Multiple Progress Bars:\n"; + + auto job1 = [&bars]() { + while (true) { + bars.tick<0>(); + if (bars.is_completed<0>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }; + + auto job2 = [&bars]() { + while (true) { + bars.tick<1>(); + if (bars.is_completed<1>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + }; + + auto job3 = [&bars]() { + while (true) { + bars.tick<2>(); + if (bars.is_completed<2>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + } + }; + + std::thread first_job(job1); + std::thread second_job(job2); + std::thread third_job(job3); + + first_job.join(); + second_job.join(); + third_job.join(); + + return 0; +} +``` + # Progress Spinner To introduce a progress spinner in your application, include `indicators/progress_spinner.hpp` and create a `ProgressSpinner` object. diff --git a/img/multi_progress.gif b/img/multi_progress.gif new file mode 100644 index 0000000..d27398c Binary files /dev/null and b/img/multi_progress.gif differ diff --git a/img/time_meter.gif b/img/time_meter.gif index c32b79e..bc192af 100644 Binary files a/img/time_meter.gif and b/img/time_meter.gif differ diff --git a/include/indicators/block_progress_bar.hpp b/include/indicators/block_progress_bar.hpp index a7fdf75..4c8f34b 100644 --- a/include/indicators/block_progress_bar.hpp +++ b/include/indicators/block_progress_bar.hpp @@ -131,7 +131,10 @@ private: std::atomic _saved_start_time{false}; std::chrono::time_point _start_time_point; std::mutex _mutex; - Color _foreground_color; + Color _foreground_color{indicators::Color::WHITE}; + + template friend class MultiProgress; + std::atomic _multi_progress_mode{false}; std::ostream &_print_duration(std::ostream &os, std::chrono::nanoseconds ns) { using namespace std; @@ -162,7 +165,13 @@ private: } } - void _print_progress() { + void _print_progress(bool from_multi_progress = false) { + if (_multi_progress_mode && !from_multi_progress) { + if (_progress > 100.0) { + _completed = true; + } + return; + } std::unique_lock lock{_mutex}; auto now = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration_cast(now - _start_time_point); @@ -231,6 +240,9 @@ private: auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); _print_duration(std::cout, remaining); std::cout << "]"; + } else { + if (_show_elapsed_time) + std::cout << "]"; } if (_max_postfix_text_length == 0) @@ -240,7 +252,7 @@ private: if (_progress > 100.0) { _completed = true; } - if (_completed) + if (_completed && !from_multi_progress) // Don't std::endl if calling from MultiProgress std::cout << termcolor::reset << std::endl; } }; diff --git a/include/indicators/multi_progress.hpp b/include/indicators/multi_progress.hpp new file mode 100644 index 0000000..2dbb9c9 --- /dev/null +++ b/include/indicators/multi_progress.hpp @@ -0,0 +1,92 @@ +/* +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 + +namespace indicators { + +template class MultiProgress { +public: + MultiProgress() { _bars.reserve(count); } + + template + typename std::enable_if<(index < count), void>::type insert(Indicator &bar) { + _bars.insert(_bars.begin() + index, 1, bar); + bar._multi_progress_mode = true; + } + + template + typename std::enable_if<(index < count), void>::type set_progress(float value) { + if (!_bars[index].get().is_completed()) + _bars[index].get().set_progress(value); + _print_progress(); + } + + template typename std::enable_if<(index < count), void>::type tick() { + if (!_bars[index].get().is_completed()) + _bars[index].get().tick(); + _print_progress(); + } + + template + typename std::enable_if<(index < count), bool>::type is_completed() const { + return _bars[index].get().is_completed(); + } + +private: + std::atomic _started{false}; + std::mutex _mutex; + std::vector> _bars; + + bool _all_completed() { + bool result{true}; + for (size_t i = 0; i < count; ++i) + result &= _bars[i].get().is_completed(); + return result; + } + + void _print_progress() { + std::unique_lock lock{_mutex}; + if (_started) + for (size_t i = 0; i < count; ++i) + std::cout << "\x1b[A"; + for (auto &bar : _bars) { + bar.get()._print_progress(true); + std::cout << "\n"; + } + std::cout << termcolor::reset; + if (!_started) + _started = true; + } +}; + +} // namespace indicators diff --git a/include/indicators/progress_bar.hpp b/include/indicators/progress_bar.hpp index 43eb2e5..2ee9c2d 100644 --- a/include/indicators/progress_bar.hpp +++ b/include/indicators/progress_bar.hpp @@ -145,7 +145,10 @@ private: std::atomic _saved_start_time{false}; std::chrono::time_point _start_time_point; std::mutex _mutex; - Color _foreground_color; + Color _foreground_color{indicators::Color::WHITE}; + + template friend class MultiProgress; + std::atomic _multi_progress_mode{false}; std::ostream &_print_duration(std::ostream &os, std::chrono::nanoseconds ns) { using namespace std; @@ -176,7 +179,13 @@ private: } } - void _print_progress() { + void _print_progress(bool from_multi_progress = false) { + if (_multi_progress_mode && !from_multi_progress) { + if (_progress > 100.0) { + _completed = true; + } + return; + } std::unique_lock lock{_mutex}; auto now = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration_cast(now - _start_time_point); @@ -239,6 +248,9 @@ private: auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); _print_duration(std::cout, remaining); std::cout << "]"; + } else { + if (_show_elapsed_time) + std::cout << "]"; } if (_max_postfix_text_length == 0) @@ -248,7 +260,7 @@ private: if (_progress > 100.0) { _completed = true; } - if (_completed) + if (_completed && !from_multi_progress) // Don't std::endl if calling from MultiProgress std::cout << termcolor::reset << std::endl; } }; diff --git a/include/indicators/progress_spinner.hpp b/include/indicators/progress_spinner.hpp index dbf631c..6b62d13 100644 --- a/include/indicators/progress_spinner.hpp +++ b/include/indicators/progress_spinner.hpp @@ -207,6 +207,9 @@ private: auto remaining = eta > elapsed ? (eta - elapsed) : (elapsed - eta); _print_duration(std::cout, remaining); std::cout << "]"; + } else { + if (_show_elapsed_time) + std::cout << "]"; } if (_max_postfix_text_length == 0) diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 983290e..99f6e5c 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -16,3 +16,9 @@ target_link_libraries(progress_spinner PRIVATE indica::indica) add_executable(time_meter time_meter.cpp) target_link_libraries(time_meter PRIVATE indica::indica) + +add_executable(multi_progress_bar multi_progress_bar.cpp) +target_link_libraries(multi_progress_bar PRIVATE indica::indica) + +add_executable(multi_block_progress_bar multi_block_progress_bar.cpp) +target_link_libraries(multi_block_progress_bar PRIVATE indica::indica) diff --git a/samples/multi_block_progress_bar.cpp b/samples/multi_block_progress_bar.cpp new file mode 100644 index 0000000..d43aabb --- /dev/null +++ b/samples/multi_block_progress_bar.cpp @@ -0,0 +1,70 @@ +#include +#include + +int main() { + + indicators::BlockProgressBar bar1; + bar1.set_bar_width(50); + bar1.set_foreground_color(indicators::Color::YELLOW); + bar1.show_elapsed_time(); + bar1.show_remaining_time(); + bar1.set_prefix_text("Progress Bar #1 "); + + indicators::BlockProgressBar bar2; + bar2.set_bar_width(50); + bar2.set_foreground_color(indicators::Color::CYAN); + bar2.show_elapsed_time(); + bar2.show_remaining_time(); + bar2.set_prefix_text("Progress Bar #2 "); + + indicators::BlockProgressBar bar3; + bar3.set_bar_width(50); + bar3.set_foreground_color(indicators::Color::RED); + bar3.show_elapsed_time(); + bar3.show_remaining_time(); + bar3.set_prefix_text("Progress Bar #3 "); + + indicators::MultiProgress bars; + bars.insert<0>(bar1); + bars.insert<1>(bar2); + bars.insert<2>(bar3); + + std::cout << "Multiple Progress Bars:\n"; + + auto job1 = [&bars]() { + while (true) { + bars.tick<0>(); + if (bars.is_completed<0>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }; + + auto job2 = [&bars]() { + while (true) { + bars.tick<1>(); + if (bars.is_completed<1>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + }; + + auto job3 = [&bars]() { + while (true) { + bars.tick<2>(); + if (bars.is_completed<2>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + } + }; + + std::thread first_job(job1); + std::thread second_job(job2); + std::thread third_job(job3); + + first_job.join(); + second_job.join(); + third_job.join(); + + return 0; +} diff --git a/samples/multi_progress_bar.cpp b/samples/multi_progress_bar.cpp new file mode 100644 index 0000000..dc71c41 --- /dev/null +++ b/samples/multi_progress_bar.cpp @@ -0,0 +1,85 @@ +#include +#include + +int main() { + + indicators::ProgressBar bar1; + bar1.set_bar_width(50); + bar1.start_bar_with("["); + bar1.fill_bar_progress_with("■"); + bar1.lead_bar_progress_with("■"); + bar1.fill_bar_remainder_with(" "); + bar1.end_bar_with(" ]"); + bar1.set_foreground_color(indicators::Color::YELLOW); + bar1.show_elapsed_time(); + bar1.show_remaining_time(); + bar1.set_prefix_text("Progress Bar #1 "); + + indicators::ProgressBar bar2; + bar2.set_bar_width(50); + bar2.start_bar_with("["); + bar2.fill_bar_progress_with("="); + bar2.lead_bar_progress_with(">"); + bar2.fill_bar_remainder_with(" "); + bar2.end_bar_with(" ]"); + bar2.set_foreground_color(indicators::Color::CYAN); + bar2.show_elapsed_time(); + bar2.show_remaining_time(); + bar2.set_prefix_text("Progress Bar #2 "); + + indicators::ProgressBar bar3; + bar3.set_bar_width(50); + bar3.start_bar_with("["); + bar3.fill_bar_progress_with("#"); + bar3.lead_bar_progress_with("#"); + bar3.fill_bar_remainder_with(" "); + bar3.end_bar_with(" ]"); + bar3.set_foreground_color(indicators::Color::RED); + bar3.show_elapsed_time(); + bar3.show_remaining_time(); + bar3.set_prefix_text("Progress Bar #3 "); + + indicators::MultiProgress bars; + bars.insert<0>(bar1); + bars.insert<1>(bar2); + bars.insert<2>(bar3); + + std::cout << "Multiple Progress Bars:\n"; + + auto job1 = [&bars]() { + while (true) { + bars.tick<0>(); + if (bars.is_completed<0>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }; + + auto job2 = [&bars]() { + while (true) { + bars.tick<1>(); + if (bars.is_completed<1>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + }; + + auto job3 = [&bars]() { + while (true) { + bars.tick<2>(); + if (bars.is_completed<2>()) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(60)); + } + }; + + std::thread first_job(job1); + std::thread second_job(job2); + std::thread third_job(job3); + + first_job.join(); + second_job.join(); + third_job.join(); + + return 0; +}