#ifndef INDICATORS_PROGRESS_BAR #define INDICATORS_PROGRESS_BAR #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace indicators { class ProgressBar { using Settings = std::tuple; public: template ::type...>::value, void *>::type = nullptr> explicit ProgressBar(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::Remainder{" "}, std::forward(args)...), details::get( option::MaxPostfixTextLen{0}, std::forward(args)...), details::get( option::Completed{false}, std::forward(args)...), details::get( option::ShowPercentage{false}, std::forward(args)...), details::get( option::ShowElapsedTime{false}, std::forward(args)...), details::get( option::ShowRemainingTime{false}, std::forward(args)...), details::get( option::SavedStartTime{false}, std::forward(args)...), details::get( option::ForegroundColor{Color::unspecified}, std::forward(args)...), details::get( option::FontStyles{std::vector{}}, std::forward(args)...), details::get( option::MinProgress{0}, std::forward(args)...), details::get( option::MaxProgress{100}, std::forward(args)...), details::get( option::ProgressType{ProgressType::incremental}, std::forward(args)...), details::get( option::Stream{TerminalHandle::StdOut}, std::forward(args)...)) { // if progress is incremental, start from min_progress // else start from max_progress const auto type = get_value(); if (type == ProgressType::incremental) progress_ = get_value(); else progress_ = get_value(); } 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< std::string, details::ProgressBarOption::postfix_text> &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 set_progress(size_t new_progress) { { std::lock_guard lock(mutex_); progress_ = new_progress; } save_start_time(); print_progress(); } void tick() { { std::lock_guard lock{mutex_}; const auto type = get_value(); if (type == ProgressType::incremental) progress_ += 1; else progress_ -= 1; } save_start_time(); print_progress(); } size_t current() { std::lock_guard lock{mutex_}; return (std::min)( progress_, size_t(get_value())); } bool is_completed() const { 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}; Settings settings_; std::chrono::nanoseconds elapsed_; std::chrono::time_point start_time_point_; std::mutex mutex_; template friend class MultiProgress; template friend class DynamicProgress; std::atomic multi_progress_mode_{false}; void save_start_time() { auto &show_elapsed_time = get_value(); auto &saved_start_time = get_value(); auto &show_remaining_time = get_value(); if ((show_elapsed_time || show_remaining_time) && !saved_start_time) { start_time_point_ = std::chrono::high_resolution_clock::now(); saved_start_time = true; } } std::pair get_prefix_text() { std::stringstream os; os << get_value(); const auto result = os.str(); const auto result_size = unicode::display_width(result); return {result, result_size}; } std::pair get_postfix_text() { std::stringstream os; const auto max_progress = get_value(); if (get_value()) { os << " " << (std::min)(static_cast(static_cast(progress_) / max_progress * 100), size_t(100)) << "%"; } auto &saved_start_time = get_value(); if (get_value()) { os << " ["; if (saved_start_time) details::write_duration(os, elapsed_); else os << "00:00s"; } if (get_value()) { if (get_value()) os << "<"; else os << " ["; if (saved_start_time) { auto eta = std::chrono::nanoseconds( progress_ > 0 ? static_cast(std::ceil(float(elapsed_.count()) * max_progress / progress_)) : 0); auto remaining = eta > elapsed_ ? (eta - elapsed_) : (elapsed_ - eta); details::write_duration(os, remaining); } else { os << "00:00s"; } os << "]"; } else { if (get_value()) os << "]"; } os << " " << get_value(); const auto result = os.str(); const auto result_size = unicode::display_width(result); return {result, result_size}; } private: std::ostream& as_stream(TerminalHandle hndl) { if (hndl == TerminalHandle::StdOut) { return std::cout; } return std::cerr; } public: void print_progress(bool from_multi_progress = false) { std::lock_guard lock{mutex_}; auto stream_type = get_value(); auto &os = as_stream(stream_type); const auto type = get_value(); const auto min_progress = get_value(); const auto max_progress = get_value(); if (multi_progress_mode_ && !from_multi_progress) { if ((type == ProgressType::incremental && progress_ >= max_progress) || (type == ProgressType::decremental && progress_ <= min_progress)) { get_value() = true; } return; } auto now = std::chrono::high_resolution_clock::now(); if (!get_value()) elapsed_ = std::chrono::duration_cast( now - start_time_point_); if (get_value() != Color::unspecified) details::set_stream_color( os, get_value()); for (auto &style : get_value()) details::set_font_style(os, style); const auto prefix_pair = get_prefix_text(); const auto prefix_text = prefix_pair.first; const auto prefix_length = prefix_pair.second; os << "\r" << prefix_text; os << get_value(); details::ProgressScaleWriter writer{ os, get_value(), get_value(), get_value(), get_value()}; writer.write(double(progress_) / double(max_progress) * 100.0f); os << get_value(); const auto postfix_pair = get_postfix_text(); const auto postfix_text = postfix_pair.first; const auto postfix_length = postfix_pair.second; os << postfix_text; // Get length of prefix text and postfix text const auto start_length = get_value().size(); const auto bar_width = get_value(); const auto end_length = get_value().size(); const auto terminal_width = terminal_size(stream_type).second; // prefix + bar_width + postfix should be <= terminal_width const int remaining = terminal_width - (prefix_length + start_length + bar_width + end_length + postfix_length); if (prefix_length == -1 || postfix_length == -1) { os << "\r"; } else if (remaining > 0) { os << std::string(remaining, ' ') << "\r"; } else if (remaining < 0) { // Do nothing. Maybe in the future truncate postfix with ... } os.flush(); if ((type == ProgressType::incremental && progress_ >= max_progress) || (type == ProgressType::decremental && progress_ <= min_progress)) { get_value() = true; } if (get_value() && !from_multi_progress) // Don't std::endl if calling from MultiProgress os << termcolor::reset << std::endl; } }; } // namespace indicators #endif