I'm doing the same thing. Keeping the white flowing chunk while changing the chunk color is the difficult part. The white flowing chunk has its own condition as well. Partly using Volodymyr's example, this is what I came up
First we subclass the QProgressBar
ProgressBar.h
class AnimProgressBar : public QProgressBar
{
Q_OBJECT
private:
const double c_overtime = 0.25;
const double c_speed = 0.025;
const double c_gsize = 0.02;
const double c_gap = 0.1;
const double opq = 180;
double overtime = c_overtime;
double speed = c_speed;
double gsize = c_gsize;
double gap = c_gap;
double aRunner = -0.2;
QString hue = "120";
QString val = "170";
const QString sat = "255";
std::mutex pbmtx;
std::condition_variable pbcv;
std::atomic<int> trueValue = 0;
std::atomic_flag setFlag{};
public:
QString font = "";
AnimProgressBar(QWidget* parent = nullptr);
private:
void valueUpdate();
void commitValue(int value);
public slots:
void ForeverLoop();
void newValue(int value);
};
ProgressBar.cpp
AnimProgressBar::AnimProgressBar(QWidget * parent)
{
QTimer *mTimer = new QTimer(this);
connect(mTimer, SIGNAL(timeout()), this, SLOT(ForeverLoop()));
mTimer->start(50);
}
Then we set the color changes. In this case I'm using hsv. Each clock cycle 1 + overtime
is each cycle for the white flowing chunk. The clock speed is speed
. Below are all the scenarios that can happen in the a progress bar flow
void AnimProgressBar::ForeverLoop()
{
QString color = hue + ", " + sat + ", " + val;
QString style
= font + "QProgressBar::chunk {background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, ";
QString trueopq = QString::number(255 - opq);
double bRunner = aRunner + gsize;
double aGap = aRunner - gap;
double bGap = bRunner + gap;
const auto BigOrEqual = [](double a, double b) { return a > b || AreDoubleSame(a, b); };
const auto SmallOrEqual = [](double a, double b) { return a < b || AreDoubleSame(a, b); };
// Symbols below are used to illustrate the progress bar coordination as visual representation
// [xx] : this is the opaque block
// [ : left side of the opaque block (aRunner)
// ] : right side of the opaque block (bRunner)
// xx : size of the opaque block (gap)
// ---- : this is the solid color section
// |S| : this is the start point
// |E| : this is the end point
// <== : this is the gradient gap
// |S|----------<==[xx]==>--------|E|
// Illustration above is visual for progress bar where the opaque block is flowing within the visible section
// Note: Visible section only exists between start point and end point, meaning in between |S| and |E|
if (SmallOrEqual(bGap, 0) || BigOrEqual(aGap, 1))
{
// <==[xx]==>--|S|---
// --|E|---<==[xx]==>
// block and gradient gap are outside of visible section
style.append("stop:0 hsv(%1)), stop:1 hsv(%1)");
}
else if (BigOrEqual(aRunner, 1))
{
if (SmallOrEqual(aGap, 0))
{
// <=|S|=|E|[xx]
// whole chunk only contains the gradient gap area
double num = 255 - std::fmin(255, std::fabs(aGap) / gap * opq);
style.append("stop:0 hsv(%1, " + QString::number(num) + ")");
}
else
{
// --|S|----------<=|E|=[xx]==>
// small gradient gap still within visible section
style.append("stop:" + QString::number(1 - gap) + " hsv(%1)");
}
double num = 255 - std::fmin(255, (1 + gap - aRunner) / gap * opq);
style.append(", stop:1 hsv(%1, " + QString::number(num) + ")");
}
else if (SmallOrEqual(bRunner, 0))
{
// <==[xx]=|S|=>----------
// only the gradient part of the opaque block is within the visible section at the start
double num = 255 - std::fmax(0, bGap / gap * opq);
style.append("stop:0 hsv(%1, " + QString::number(num) + "), stop:" + QString::number(bGap)
+ " hsv(%1)");
}
else if (SmallOrEqual(aRunner, 0))
{
// <==[x|S|x]==>
// only part of the opaque block is within the visible section at the start
style.append("stop:0 hsv(%1, " + trueopq + ")");
if (SmallOrEqual(bRunner, 1))
{
style.append(", stop:" + QString::number(bRunner) + " hsv(%1, " + trueopq + ")");
if (BigOrEqual(bGap, 1))
{
// <==[x|S|x]==|E|>
// the progress bar's chunk is small that right end of the gradient gap is hitting the end of the progress bar chunk
double num = 255 - std::fmin(255, bGap - 1 / gap * opq);
style.append(", stop:1 hsv(%1, " + QString::number(num) + ")");
}
else
{
// <==[x|S|x]==>---------|E|
// ^
// bRunner
// the progress bar's chunk gradient gap not hitting the end
style.append(", stop:" + QString::number(bGap) + " hsv(%1)");
}
}
else
{
// <==[x|S|x|E|]==>
// the progress bar's chunk is so small that fits within the whole block
}
}
else if (SmallOrEqual(aGap, 0))
{
// <=|S|=[xx]
// part of the left gradient is outside of the start point
double anum = 255 - std::fmin(255, std::fabs(aGap) / gap * opq);
style.append("stop:0 hsv(%1, " + QString::number(anum) + "), stop:" + QString::number(aRunner) + " hsv(%1, " + trueopq + ")");
if (SmallOrEqual(bRunner, 1))
{
style.append(", stop:" + QString::number(bRunner) + " hsv(%1, " + trueopq + ")");
if (BigOrEqual(bGap, 1))
{
// <=|S|=[xx]=|E|=>
// the part of the gradient area at left and right are outside of the start point and end point respectively
double bextra = bGap - 1;
double num = 255 - std::fmin(255, gap + bextra / gap * opq);
style.append(", stop:1 hsv(%1, " + QString::number(num) + ")");
}
else
{
// <=|S|=[xx]==>-------|E|
// part of the left gradient is outside of the start point
style.append(", stop:" + QString::number(bGap) + " hsv(%1)");
}
}
else
{
// <=|S|=[x|E|x]==>
// the part of the gradient area at left is outside of the start point while the block is outside of the end point
}
}
else if (BigOrEqual(bRunner, 1))
{
// ------<==[x|E|x]==>
// only part of the opaque block is within the end point
style.append("stop:" + QString::number(aGap) + " hsv(%1), stop:" + QString::number(aRunner) + " hsv(%1, "
+ trueopq + "), stop:0.99995 hsv(%1, " + trueopq + "), stop:1 hsv(%1)");
}
else if (BigOrEqual(bGap, 1))
{
// ------<==[xx]=|E|=>
// part of the right gradient is outside of the end point
double bextra = bGap - 1;
double bnum = 255 - std::fmin(255, gap + bextra / gap * opq);
style.append("stop:" + QString::number(aGap) + " hsv(%1), stop:" + QString::number(aRunner)
+ " hsv(%1, " + trueopq + "), stop:" + QString::number(bRunner) + " hsv(%1, " + trueopq
+ "), stop:1 hsv(%1, " + QString::number(bnum) + ")");
}
else
{
// |S|----------<==[xx]==>--------|E|
// the opaque block is flowing within the visible section
style.append("stop:" + QString::number(aGap) + " hsv(%1), stop:" + QString::number(aRunner)
+ " hsv(%1, " + trueopq + "), stop:" + QString::number(bRunner) + " hsv(%1, " + trueopq
+ "), stop:" + QString::number(bGap) + " hsv(%1)");
}
style.append(");}");
setStyleSheet(style.arg(color));
aRunner += speed;
if (aRunner < 0 - overtime || aGap > 1 + overtime) aRunner = 0 - overtime;
}
For every change in the value, we modify the hsv, gap and the clock speed
void AnimProgressBar::newValue(int value)
{
int max = maximum();
if (0 < value && value <= max)
{
double decivalue = value;
double power = decivalue / max;
double mult = max / std::min(max, value);
overtime = c_overtime * mult;
speed = c_spped * mult;
gsize = c_gsize * mult;
gap = c_gap * mult;
hue = QString::number(static_cast<int>(120 - (decivalue / max * 120)));
val = QString::number(static_cast<int>(170 + (decivalue / max* 55)));
}
if (value == max * 7 / 10) font = "QProgressBar { color: rgb(255, 255, 255); } ";
setValue(value);
}
EDIT: Rewrote the update loop