#ifndef INDICATORS_BLOCK_PROGRESS_BAR #define INDICATORS_BLOCK_PROGRESS_BAR #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace indicators { class BlockProgressBar { using Settings = std::tuple; public: template ::type...>::value, void *>::type = nullptr> explicit BlockProgressBar(Args &&... args) : settings_(details::get( option::ForegroundColor{Color::unspecified}, std::forward(args)...), details::get(option::BarWidth{100}, std::forward(args)...), details::get(option::Start{"["}, std::forward(args)...), details::get(option::End{"]"}, std::forward(args)...), details::get( option::PrefixText{""}, std::forward(args)...), details::get( option::PostfixText{""}, std::forward(args)...), details::get( option::ShowPercentage{true}, std::forward(args)...), details::get( option::ShowElapsedTime{false}, std::forward(args)...), details::get( option::ShowRemainingTime{false}, std::forward(args)...), details::get(option::Completed{false}, std::forward(args)...), details::get( option::SavedStartTime{false}, std::forward(args)...), details::get( option::MaxPostfixTextLen{0}, std::forward(args)...), details::get( option::FontStyles{std::vector{}}, std::forward(args)...), details::get( option::MaxProgress{100}, std::forward(args)...), details::get(option::Stream{std::cout}, std::forward(args)...)) {} 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 set_progress(size_t value) { { std::lock_guard lock{mutex_}; tick_ = value; } save_start_time(); print_progress(); } void tick() { { std::lock_guard lock{mutex_}; tick_++; } save_start_time(); print_progress(); } size_t current() { std::lock_guard lock{mutex_}; return (std::min)(tick_, 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; } Settings settings_; float progress_{0.0}; size_t tick_{0}; 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(); progress_ = static_cast(tick_)/max_progress; auto now = std::chrono::high_resolution_clock::now(); auto elapsed = std::chrono::duration_cast(now - start_time_point_); if (get_value()) { os << " " << (std::min)(static_cast(progress_ * 100.0), 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( tick_ > 0 ? static_cast(std::ceil(float(elapsed.count()) / 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}; } public: void print_progress(bool from_multi_progress = false) { std::lock_guard lock{mutex_}; auto &os = get_value(); const auto max_progress = get_value(); if (multi_progress_mode_ && !from_multi_progress) { if (tick_ > max_progress) { get_value() = true; } return; } 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::BlockProgressScaleWriter writer{os, get_value()}; writer.write(progress_ * 100); 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().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 (tick_ > max_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