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;
+}