Reputation: 2363
I am trying to animate a timer progress on a QPushButton
border, using stylesheets and QLinearGradient
.
Here's how I'm doing it:
#include <QApplication>
#include <QPushButton>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton button("Animation Button");
float greenPosition = 0.99;
float whitePosition = 0.01;
QTimer timer;
enum Border {Top = 1, Left = 2, Bottom = 3, Right = 4};
int border = 1;
button.connect(&timer, &QTimer::timeout, [&button, &greenPosition, &whitePosition, &border]()
{
//Left or Bottom
if(border == 2 || border == 3)
{
if(whitePosition + 0.01 > 1)
{
whitePosition = 0;
border = border++ % 4 + 1;
}
whitePosition += 0.01;
}
else
{
if(greenPosition - 0.01 < 0.01)
{
greenPosition = 1;
border = border % 4 + 1;
}
greenPosition -= 0.01;
}
switch (border)
{
case Top:
button.setStyleSheet(QString("border: 2px solid white;"
//"border-radius: 5px;"
"border-top: 2px solid qlineargradient(x0:0, x2:1,"
"stop: 0 green,"
"stop: %1 green,"
"stop: %2 white,"
"stop: 1 white);").arg(greenPosition).arg(greenPosition + 0.01));
break;
case Left:
button.setStyleSheet(QString("border: 2px solid white;"
//"border-radius: 5px;"
"border-left: 2px solid qlineargradient(y0:0, y2:1,"
"stop: 0 white,"
"stop: %1 white,"
"stop: %2 green,"
"stop: 1 green);").arg(whitePosition).arg(whitePosition + 0.01));
break;
case Bottom:
button.setStyleSheet(QString("border: 2px solid white;"
//"border-radius: 5px;"
"border-bottom: 2px solid qlineargradient(x0:0, x2:1,"
"stop: 0 white,"
"stop: %1 white,"
"stop: %2 green,"
"stop: 1 green);").arg(whitePosition).arg(whitePosition + 0.01));
break;
case Right:
button.setStyleSheet(QString("border: 2px solid white;"
//"border-radius: 5px;"
"border-right: 2px solid qlineargradient(y0:0, y2:1,"
"stop: 0 green,"
"stop: %1 green,"
"stop: %2 white,"
"stop: 1 white);").arg(greenPosition).arg(greenPosition + 0.01));
break;
}
});
timer.start(50);
button.show();
return a.exec();
}
Here's the logic I came up with, to create this animation:
greenPosition
andwhitePosition
are values that control green and white color range inQLinearGradient
, green is the progress, and white is the actual border color.The basic idea, is that I'm making the white recede, and the green advance, or vice versa.
There is a small fraction between their values to avoid the gradient effect, hence why I'm adding
0.01
. IfgreenPosition
orwhitePosition
reach the limit of the [1.00 - 0.00] range, they reset to the default.
border
is a variable that allows me to iterate through the4
borders in my switch case, just a simple circular counter.All of this loops using a
QTimer
.
Here's the result:
I need round borders, but when I use them, it looks like this:
The borders seem to form another side of the button on their own, as if with round borders, the button now has 8 sides.
This is because the animation on the round borders seem to be synced with the border that's currently being animated, instead of being animated when the animation reaches them. It also looks like a mirror effect.
Note: This is just an observation to explain how it looks, not what actually happens.
Here's how it looks if I apply a green color without QLinearGradient
while using round borders:
How do I animate the round borders the same way as the not-round ones?
Upvotes: 0
Views: 316
Reputation: 2363
Since setting border radius using stylesheets has unwanted effects, you could make your widget borders round without them, and use paintEvent
instead; here's how:
You could draw a rounded rect with a color same as the widget parent's background, this acts as if it's hiding those sharp corners, and make them look round.
One downside of this method is that it limits how round the corners can get, because if the painter draws a too rounded rect, it will mask the widget itself or cause sharp edges.
It is possible to slightly get around that by increasing the border size, to get more space, thus, making it possible to increase the border radius when using a painter without ruining the widget edges.
There is an upside for using this method. It prevents the widget background from poking out the border.
Here's an example of subclass derived from QPushButton
with a custom paintEvent
:
class button : public QPushButton
{
Q_OBJECT
public:
button (QWidget *parent = nullptr) : QPushButton(parent) {}
protected:
void paintEvent(QPaintEvent *event) override
{
QPushButton::paintEvent(event);
QRectF r = rect();
r.adjust(-1,-1,+1,+1);
QPainter p(this);
QColor color = parentWidget()->palette().brush(QPalette::Window).color();
p.setPen(QPen(color,2));
p.setRenderHint(QPainter::Antialiasing);
p.drawRoundedRect(r, 6, 6);
}
};
Here's the result, where the bottom button is a normal QPushButton
with no border radius, just to increase the difference visibility:
Upvotes: 0
Reputation: 39
For these types of features, QML provides a very flexible tool set.
I've achieved what I think you are looking for in QML:
Canvas {
id: border_animation
property real progress: 0
onProgressChanged: {
border_animation.requestPaint()
}
anchors.centerIn: parent
height: parent.height*0.10
width: parent.width*0.65
z: 2
Text {
text: "start timer"
color: "white"
font.bold: true
font.pointSize: 15
anchors.centerIn: parent
}
NumberAnimation on progress {
id: startTimer
from: 0
to: 1
running: false
duration: 1000
onFinished: {
startTimer.start()
}
}
onPaint: {
var ctx = border_animation.getContext('2d')
ctx.strokeStyle = "green"
ctx.fillStyle = "black"
ctx.lineWidth = 2
ctx.beginPath()
ctx.rect(0,0,width,height)
ctx.fill()
var Lcenterx = width*0.25
var Lcentery = height*0.50
var Rcenterx = width*0.75
var Rcentery = height*0.50
var radius = height*0.40
//each section is 25% of the border so we break it up that way
if(progress<=0.25){
var topProgress = progress/0.25
var rightProgress = 0
var bottomProgress = 0
var leftProgress = 0
}else if(progress<=0.50){
topProgress = 1
rightProgress = (progress-0.25)/0.25
bottomProgress = 0
leftProgress = 0
}else if(progress<=0.75){
topProgress = 1
rightProgress = 1
bottomProgress = (progress-0.50)/0.25
leftProgress = 0
}else{
topProgress = 1
rightProgress = 1
bottomProgress = 1
leftProgress = (progress-0.75)/0.25
}
//top line
ctx.strokeStyle = "orange"
ctx.beginPath();
ctx.moveTo(Lcenterx, Lcentery-radius);
ctx.lineTo(Lcenterx + (Rcenterx - Lcenterx)*topProgress, Rcentery-radius);
ctx.stroke();
ctx.strokeStyle = "yellow"
ctx.beginPath();
ctx.arc(Rcenterx, Rcentery, radius, 1.5*Math.PI, 1.5*Math.PI + Math.PI * rightProgress);
ctx.stroke();
//bottom line
ctx.strokeStyle = "cyan"
ctx.beginPath();
ctx.moveTo(Rcenterx, Lcentery+radius);
ctx.lineTo(Rcenterx - (Rcenterx - Lcenterx)*bottomProgress, Rcentery+radius);
ctx.stroke();
ctx.strokeStyle = "green"
ctx.beginPath();
ctx.arc(Lcenterx, Lcentery, radius, 0.5*Math.PI, 0.5*Math.PI + Math.PI * leftProgress);
ctx.stroke();
}
MouseArea {
anchors.fill: parent
onClicked: {
startTimer.start()
}
}
}
Upvotes: -1