Reputation: 3595
I want to create a endless loop with QStateMachine in which I also need animations.
QColor leastTransparent, mostTransparent = color();
leastTransparent.setAlpha(250);
mostTransparent.setAlpha(150);
QState *s1 = new QState();
s1->assignProperty(this, "color", leastTransparent);
QState *s2 = new QState();
s2->assignProperty(this, "color", mostTransparent);
QSignalTransition *transition = s1->addTransition( this, SIGNAL(triggerSignal()),s2);
QSignalTransition *transition2 = s2->addTransition(s2, SIGNAL(entered),s1);
QPropertyAnimation* animation = new QPropertyAnimation( this, "color");
animation->setDuration( 5000 );
transition->addAnimation(animation);
QPropertyAnimation* animation2 = new QPropertyAnimation( this, "color");
animation2->setDuration(10000);
transition2->addAnimation(animation2);
m_stateMachineAnimation->addState(s1);
m_stateMachineAnimation->addState(s2);
m_stateMachineAnimation->setInitialState(s1);
m_stateMachineAnimation->setGlobalRestorePolicy(QStateMachine::RestoreProperties);
m_stateMachineAnimation->start();
What I expect here is the color will get more opaque for first 5 seconds after "triggerSignal". And state will be "s2". And than "s2" 's enter signal be triggered and it will get more and more transparent for 10 seconds.
But instead I am having s2 trigger immediately without waiting 5 seconds right after the "triggerSignal" and than immediately s1 is being triggered again without waiting 10 seconds.
Why my duration is not taken into account by QStateMachine. How can I achieve such a animation with QStateMachine
Upvotes: 1
Views: 490
Reputation: 98425
You seem to expect the animation to create some sort of an in-between state. It does no such thing. The transition merely triggers the animation. You transition immediately from s2
to s1
, giving no time for animations to finish. Instead, you need to explicitly trigger the subsequent transition when the final values of the properties are set. The QState::propertiesAssigned
signal is most useful for this purpose. Alternatively, you could use the animation's finished()
signal.
In the example below, click within the window to start the animation loop:
// https://github.com/KubaO/stackoverflown/tree/master/questions/statemachine-animation-42682462
#include <QtWidgets>
const char kColor[] = "color";
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
QColor m_color{Qt::blue};
QStateMachine m_machine{this};
QState s0{&m_machine}, s1{&m_machine}, s2{&m_machine};
QEventTransition t01{this, QEvent::MouseButtonPress};
QPropertyAnimation anim_s1{this, kColor}, anim_s2{this, kColor};
void paintEvent(QPaintEvent *) override {
QPainter{this}.fillRect(rect(), m_color);
}
Q_SIGNAL void colorChanged(const QColor &);
public:
Widget() {
connect(this, &Widget::colorChanged, [this]{ update(); });
s1.assignProperty(this, kColor, QColor{Qt::red});
s2.assignProperty(this, kColor, QColor{Qt::green});
t01.setTargetState(&s1);
s0.addTransition(&t01); t01.addAnimation(&anim_s1);
s1.addTransition(&s1, &QState::propertiesAssigned, &s2)->addAnimation(&anim_s2);
s2.addTransition(&s2, &QState::propertiesAssigned, &s1)->addAnimation(&anim_s1);
anim_s1.setDuration(1000);
anim_s2.setDuration(2000);
m_machine.setInitialState(&s0);
m_machine.start();
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
Widget w;
w.setFixedSize(300, 200);
w.show();
return app.exec();
}
#include "main.moc"
As an aside, this demonstrates that the animation interpolates RGB values, causing the color to go dark between red
and blue
as the values go from (1,0,0)
through (.5,.5,0)
onto (0,1,0)
. For human consumption, it'd make more sense to interpolate HSV, so that the value (brightness) stays the same, and only the hue (what we humans really call "color") changes.
Upvotes: 2