diff --git a/README.md b/README.md index 91c33f6..0bb5d74 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ make ## Table of Contents * [Progress Bar](#progress-bar) +* [Indeterminate Progress Bar](#indeterminate-progress-bar) * [Block Progress Bar](#block-progress-bar) * [Multi Progress](#multiprogress) * [Dynamic Progress](#dynamicprogress) @@ -212,6 +213,61 @@ int main() { } ``` +## Indeterminate Progress Bar + +You might have a use-case for a progress bar where the maximum amount of progress is unknown, e.g., you're downloading from a remote server that isn't advertising the total bytes. + +Use an `indicators::IndeterminateProgressBar` for such cases. An `IndeterminateProgressBar` is similar to a regular progress bar except the total amount to progress towards is unknown. Ticking on this progress bar will happily run forever. + +When you know progress is complete, simply call `bar.mark_as_completed()`. + +

+ +

+ +```cpp +#include +#include +#include +#include +#include + +int main() { + indicators::IndeterminateProgressBar bar{ + indicators::option::BarWidth{40}, + indicators::option::Start{"["}, + indicators::option::Fill{"·"}, + indicators::option::Lead{"<==>"}, + indicators::option::End{"]"}, + indicators::option::PostfixText{"Checking for Updates"}, + indicators::option::ForegroundColor{indicators::Color::yellow}, + indicators::option::FontStyles{ + std::vector{indicators::FontStyle::bold}} + }; + + indicators::show_console_cursor(false); + + auto job = [&bar]() { + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + bar.mark_as_completed(); + std::cout << termcolor::bold << termcolor::green + << "System is up to date!\n" << termcolor::reset; + }; + std::thread job_completion_thread(job); + + // Update bar state + while (!bar.is_completed()) { + bar.tick(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + job_completion_thread.join(); + + indicators::show_console_cursor(true); + return 0; +} +``` + ## Block Progress Bar Are you in need of a smooth block progress bar using [unicode block elements](https://en.wikipedia.org/wiki/Block_Elements)? Use `BlockProgressBar` instead of `ProgressBar`. Thanks to [this blog post](https://mike42.me/blog/2018-06-make-better-cli-progress-bars-with-unicode-block-characters) for making `BlockProgressBar` an easy addition to the library. diff --git a/img/indeterminate_progress_bar.gif b/img/indeterminate_progress_bar.gif new file mode 100644 index 0000000..f992e6d Binary files /dev/null and b/img/indeterminate_progress_bar.gif differ diff --git a/include/indicators/details/stream_helper.hpp b/include/indicators/details/stream_helper.hpp index 5796225..5c499a9 100644 --- a/include/indicators/details/stream_helper.hpp +++ b/include/indicators/details/stream_helper.hpp @@ -155,5 +155,30 @@ private: std::string remainder; }; +class IndeterminateProgressScaleWriter { +public: + IndeterminateProgressScaleWriter(std::ostream &os, size_t bar_width, const std::string &fill, + const std::string &lead) + : os(os), bar_width(bar_width), fill(fill), lead(lead) {} + + std::ostream &write(size_t progress) { + for (size_t i = 0; i < bar_width; ++i) { + if (i < progress) + os << fill; + else if (i == progress) + os << lead; + else + os << fill; + } + return os; + } + +private: + std::ostream &os; + size_t bar_width = 0; + std::string fill; + std::string lead; +}; + } // namespace details } // namespace indicators diff --git a/include/indicators/indeterminate_progress_bar.hpp b/include/indicators/indeterminate_progress_bar.hpp new file mode 100644 index 0000000..4e7bc94 --- /dev/null +++ b/include/indicators/indeterminate_progress_bar.hpp @@ -0,0 +1,224 @@ +/* +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 +#include +#include +#include +#include +#include +#include +#include + +namespace indicators { + +class IndeterminateProgressBar { + using Settings = + std::tuple; + + enum class Direction { + forward, + backward + }; + + Direction direction_{Direction::forward}; + +public: + template ::type...>::value, + void *>::type = nullptr> + explicit IndeterminateProgressBar(Args &&... args) + : settings_(details::get(option::BarWidth{100}, + std::forward(args)...), + details::get( + option::PrefixText{}, std::forward(args)...), + details::get( + option::PostfixText{}, std::forward(args)...), + details::get(option::Start{"["}, + std::forward(args)...), + details::get(option::End{"]"}, + std::forward(args)...), + details::get(option::Fill{"."}, + std::forward(args)...), + details::get(option::Lead{"<==>"}, + std::forward(args)...), + details::get( + option::MaxPostfixTextLen{0}, std::forward(args)...), + details::get(option::Completed{false}, + std::forward(args)...), + details::get( + option::ForegroundColor{Color::unspecified}, std::forward(args)...), + details::get( + option::FontStyles{std::vector{}}, std::forward(args)...)) { + // starts with [<==>...........] + // progress_ = 0 + + // ends with [...........<==>] + // ^^^^^^^^^^^^^^^^^ bar_width + // ^^^^^^^^^^^^ (bar_width - len(lead)) + // progress_ = bar_width - len(lead) + progress_ = 0; + max_progress_ = get_value() + - get_value().size() + + get_value().size() + + get_value().size(); + } + + 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; + } + + void set_option( + const details::Setting &setting) { + std::lock_guard lock(mutex_); + get_value() = setting.value; + if (setting.value.length() > get_value()) { + get_value() = setting.value.length(); + } + } + + void + set_option(details::Setting &&setting) { + std::lock_guard lock(mutex_); + get_value() = std::move(setting).value; + auto &new_value = get_value(); + if (new_value.length() > get_value()) { + get_value() = new_value.length(); + } + } + + void tick() { + { + std::lock_guard lock{mutex_}; + if (get_value()) + return; + + progress_ += (direction_ == Direction::forward) ? 1 : -1; + if (direction_ == Direction::forward && progress_ == max_progress_) { + // time to go back + direction_ = Direction::backward; + } else if (direction_ == Direction::backward && progress_ == 0) { + direction_ = Direction::forward; + } + } + print_progress(); + } + + bool is_completed() { + return get_value(); + } + + void mark_as_completed() { + get_value() = true; + print_progress(); + } + +private: + 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; + } + + size_t progress_{0}; + size_t max_progress_; + Settings settings_; + std::chrono::nanoseconds elapsed_; + std::mutex mutex_; + + template friend class MultiProgress; + template friend class DynamicProgress; + std::atomic multi_progress_mode_{false}; + +public: + void print_progress(bool from_multi_progress = false) { + std::lock_guard lock{mutex_}; + if (multi_progress_mode_ && !from_multi_progress) { + return; + } + if (get_value() != Color::unspecified) + details::set_stream_color(std::cout, get_value()); + + for (auto &style : get_value()) + details::set_font_style(std::cout, style); + + std::cout << get_value(); + + std::cout << get_value(); + + details::IndeterminateProgressScaleWriter writer{std::cout, + get_value(), + get_value(), + get_value()}; + writer.write(progress_); + + std::cout << get_value(); + + if (get_value() == 0) + get_value() = 10; + std::cout << " " << get_value() + << std::string(get_value(), ' ') + << "\r"; + std::cout.flush(); + if (get_value() && + !from_multi_progress) // Don't std::endl if calling from MultiProgress + std::cout << termcolor::reset << std::endl; + } +}; + +} // namespace indicators diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 5cdb763..de33bee 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -29,3 +29,6 @@ target_link_libraries(dynamic_progress PRIVATE indicators::indicators) add_executable(max_progress max_progress.cpp) target_link_libraries(max_progress PRIVATE indicators::indicators) +add_executable(indeterminate_progress_bar indeterminate_progress_bar.cpp) +target_link_libraries(indeterminate_progress_bar PRIVATE indicators::indicators) + diff --git a/samples/indeterminate_progress_bar.cpp b/samples/indeterminate_progress_bar.cpp new file mode 100644 index 0000000..95cefa8 --- /dev/null +++ b/samples/indeterminate_progress_bar.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include + +int main() { + indicators::IndeterminateProgressBar bar{ + indicators::option::BarWidth{40}, + indicators::option::Start{"["}, + indicators::option::Fill{"·"}, + indicators::option::Lead{"<==>"}, + indicators::option::End{"]"}, + indicators::option::PostfixText{"Checking for Updates"}, + indicators::option::ForegroundColor{indicators::Color::yellow}, + indicators::option::FontStyles{ + std::vector{indicators::FontStyle::bold}} + }; + + indicators::show_console_cursor(false); + + auto job = [&bar]() { + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + bar.mark_as_completed(); + std::cout << termcolor::bold << termcolor::green + << "System is up to date!\n" << termcolor::reset; + }; + std::thread job_completion_thread(job); + + // Update bar state + while (!bar.is_completed()) { + bar.tick(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + job_completion_thread.join(); + + indicators::show_console_cursor(true); + return 0; +}