foxygit / Torlinkc Log in
commits tags

/src/ui/progress_bar.cpp · 2.44 KB

raw
#include "torlinkc/ui/progress_bar.hpp"

#include <algorithm>
#include <cmath>

#include "torlinkc/ui/theme.hpp"

using namespace ftxui;

namespace torlinkc::ui {

namespace {

constexpr float kSheenRadius = 4.5f;   // bell half-width, in bar cells
constexpr float kSheenGap = 8.0f;      // dark cells between sweeps
constexpr float kSheenSpeed = 11.25f;  // cells per second
constexpr float kSheenMax = 0.9f;      // peak blend toward the highlight

const Color kSheenPeak = Color::RGB(244, 239, 255);   // #f4efff
const Color kAnimatedDeep = Color::RGB(124, 92, 214);  // #7c5cd6, ProgressBar.tsx's DEEP

float sheenPeriod(int width) { return static_cast<float>(width) + kSheenRadius * 2.0f + kSheenGap; }

float sheenIntensity(float cell, float center) {
  const float d = std::fabs(cell - center);
  if (d >= kSheenRadius) return 0.0f;
  return 0.5f * (1.0f + std::cos(static_cast<float>(M_PI) * d / kSheenRadius)) * kSheenMax;
}

Color ramp(float t, Color deep, Color mid, Color bright) {
  return t <= 0.5f ? Color::Interpolate(t / 0.5f, deep, mid) : Color::Interpolate((t - 0.5f) / 0.5f, mid, bright);
}

}  // namespace

float progressSheenCenter(float clockSeconds, int width) {
  return std::fmod(clockSeconds * kSheenSpeed, sheenPeriod(width)) - kSheenRadius;
}

Element renderProgressBar(int pct, int width, Color base, std::optional<float> sweepCenter) {
  const int clamped = std::clamp(pct, 0, 100);
  const int filled = static_cast<int>(std::lround((clamped / 100.0) * width));
  const int empty = std::max(0, width - filled);
  const int denom = std::max(1, width - 1);

  Color deep;
  Color mid;
  Color bright;
  if (sweepCenter) {
    deep = kAnimatedDeep;
    mid = palette::accent;
    bright = palette::bright;
  } else {
    deep = Color::Interpolate(0.3f, base, Color::RGB(0, 0, 0));
    mid = base;
    bright = Color::Interpolate(0.35f, base, palette::text);
  }

  Elements cells;
  cells.reserve(static_cast<std::size_t>(width));
  for (int i = 0; i < filled; ++i) {
    const float t = static_cast<float>(i) / static_cast<float>(denom);
    Color c = ramp(t, deep, mid, bright);
    if (sweepCenter) {
      const float intensity = sheenIntensity(static_cast<float>(i), *sweepCenter);
      if (intensity > 0.0f) c = Color::Interpolate(intensity, c, kSheenPeak);
    }
    cells.push_back(text("█") | color(c));
  }
  for (int i = 0; i < empty; ++i) cells.push_back(text("░") | color(palette::rule));
  return hbox(std::move(cells));
}

}  // namespace torlinkc::ui