1

My idea in this project is to perform swap animation on items. Problem is however that when I perform swap on items for the first time they keep their position still, but when the other animation starts that involves already swapped items, those items fall back to their initial positions. Please tell me what am I doing wrong. Animation as follows:

enter image description here

    #include <QtCore>
    #include <QtWidgets>


    /**
     * Element to be displayed in QGraphicsView
     */
    class QGraphicsRectWidget : public QGraphicsWidget
    {
        Q_OBJECT
        int m_number;

    public:

        void changePosition(QGraphicsRectWidget *other)
        {
            setPos(mapToParent(other->x() < x() ? -abs(x() - other->x())
                                                      : abs(x() - other->x()) ,0));
        }

        static int NUMBER;

        QGraphicsRectWidget(QGraphicsItem *parent = 0) : QGraphicsWidget(parent), m_number(NUMBER)
        { NUMBER++;}
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
                   QWidget *) Q_DECL_OVERRIDE
        {
            painter->fillRect(rect(), QColor(127, 63, 63));
            painter->drawText(rect(), QString("%1").arg(m_number), QTextOption(Qt::AlignCenter));
        }

    };

    int QGraphicsRectWidget::NUMBER = 1;


    class MyAnim : public QPropertyAnimation
    {
        Q_OBJECT

        QGraphicsView &pview;   // View in which elements must be swapped   
        int i1, i2;         // Indices for elements to be swapped

    public:
        MyAnim(QGraphicsView &view, int index1 = 0, int index2 = 1, QObject *par = 0)
            : QPropertyAnimation(par), pview(view), i1(index1), i2(index2)
        {
            QObject::connect(this, SIGNAL(finished()), SLOT(slotOnFinish()));
        }

    public slots:
        /* !!!!!!!!!!!!!!!!!!!!!!! HERE IS THE PROBLEM (brobably)   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
        // Triggered when animation is over and sets position of target element to position of its end value
        void slotOnFinish()
        {
            auto list = pview.items();

            static_cast<QGraphicsRectWidget*>(list.at(i1))
                    ->changePosition(static_cast<QGraphicsRectWidget*>(list.at(i2)));
        }
    };

    class GraphicsView : public QGraphicsView
    {
        Q_OBJECT
    public:
        GraphicsView(QGraphicsScene *scene, QWidget *parent = NULL) : QGraphicsView(scene, parent)
        {
        }

    protected:
        virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE
        {
            fitInView(scene()->sceneRect());
            QGraphicsView::resizeEvent(event);
        }
    };


    #define SWAP_HEIGHT 75

    /**
     * Creates swap animation for items in QGraphicsView 
     */
    QParallelAnimationGroup* getSwapAnimation(QGraphicsView &view, int noItem1, int noItem2)
    {
        auto list = view.items();
        QGraphicsRectWidget *wgt1 = static_cast<QGraphicsRectWidget*>(list.at(noItem1));
        QGraphicsRectWidget *wgt2 = static_cast<QGraphicsRectWidget*>(list.at(noItem2));

        MyAnim *pupperAnim, *plowerAnim;
        QParallelAnimationGroup *par = new QParallelAnimationGroup;

        plowerAnim = new MyAnim(view, noItem1, noItem2);
        plowerAnim->setTargetObject(wgt2);
        plowerAnim->setPropertyName("pos");
        plowerAnim->setDuration(5000);
        plowerAnim->setKeyValueAt(1.0/3.0, QPoint(wgt2->x(), wgt1->y() - SWAP_HEIGHT));
        plowerAnim->setKeyValueAt(2.0/3.0, QPoint(wgt1->x(), wgt1->y() - SWAP_HEIGHT));
        plowerAnim->setEndValue(wgt1->pos());


        pupperAnim = new MyAnim(view, noItem2, noItem1);
        pupperAnim->setTargetObject(wgt1);
        pupperAnim->setPropertyName("pos");
        pupperAnim->setDuration(5000);
        pupperAnim->setKeyValueAt(1.0/3.0, QPoint(wgt1->x(), wgt2->y() + SWAP_HEIGHT));
        pupperAnim->setKeyValueAt(2.0/3.0, QPoint(wgt2->x(), wgt2->y() + SWAP_HEIGHT));
        pupperAnim->setEndValue(wgt2->pos());

        par->addAnimation(pupperAnim);
        par->addAnimation(plowerAnim);
        return par;
    }

    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);

        QGraphicsRectWidget *button1 = new QGraphicsRectWidget;
        QGraphicsRectWidget *button2 = new QGraphicsRectWidget;
        QGraphicsRectWidget *button3 = new QGraphicsRectWidget;
        QGraphicsRectWidget *button4 = new QGraphicsRectWidget;
        button2->setZValue(1);
        button3->setZValue(2);
        button4->setZValue(3);
        QGraphicsScene scene(0, 0, 300, 300);
        scene.setBackgroundBrush(QColor(23, 0, 0));
        scene.addItem(button1);
        scene.addItem(button2);
        scene.addItem(button3);
        scene.addItem(button4);
        GraphicsView window(&scene);
        window.setFrameStyle(0);
        window.setAlignment(Qt::AlignLeft | Qt::AlignTop);
        window.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        window.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);


        QList<QGraphicsItem*> items = window.items();
        QPoint start(20, 125);

        for (auto item : items) // Set items in initial position
        {
            QGraphicsWidget *wgt = static_cast<QGraphicsWidget*>(item);
            wgt->resize(50,50);
            wgt->moveBy(start.x(), start.y());
            start.setX(start.x() + 70);
        }


        QSequentialAnimationGroup gr;

        gr.addAnimation(getSwapAnimation(window, 0, 1));
        gr.addAnimation(getSwapAnimation(window, 1, 2));
        gr.addAnimation(getSwapAnimation(window, 2, 3));
        gr.addAnimation(getSwapAnimation(window, 3, 1));
        gr.start();

        window.resize(300, 300);
        window.show();

        return app.exec();
    }

    #include "main.moc"

UPD: Don't use animation with that purpose

UPD*: Forget previous UPD

Vadixem
  • 99
  • 9
  • 1
    "UPD: Don't use animation with that purpose" Of course you can and should use an animation, just do it right :) – Kuba hasn't forgotten Monica Nov 28 '16 at 16:05
  • @KubaOber Yeah, but considering fact that I spoke to Max Schlee, who is author of "Qt 5.3 Professional programming with C++" and was given advise not to use one, I decided not to use one. I am not stating u cannot do it with animation, but it's a huge pain in buttocks. And I have already found a solution – Vadixem Nov 28 '16 at 16:56
  • 1
    Use what works and what results in the most readable code. I doubt that the performance will play much role unless you're animating lots of items. If you do, then you could start with a `QAbstractAnimation` and implement a `QGraphicsItemPositionAnimation` that would avoid the overhead of having `QObject` items etc. :) – Kuba hasn't forgotten Monica Nov 28 '16 at 18:52
  • @KubaOber cannot disagree! Thanks for your effort:D – Vadixem Nov 29 '16 at 08:39

1 Answers1

1

Your animation retains the position of the items involved at the time the animation is created. By the time the second animation runs, this information is invalid.

You need to redesign the animation to update its keypoint values at the time it starts. You might also wish to ensure that the animated items run at a constant speed - or at least at a speed you have full control over.

For example:

// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-anim-swap-40787655
#include <QtWidgets>
#include <cmath>

class QGraphicsRectWidget : public QGraphicsWidget
{
public:
   void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
   {
      painter->fillRect(rect(), Qt::blue);
      painter->setPen(Qt::yellow);
      painter->drawText(rect(), QString::number(zValue()), QTextOption(Qt::AlignCenter));
   }
};

class SwapAnimation : public QPropertyAnimation
{
   QPointer<QObject> other;
   qreal offset;
   QPoint propertyOf(QObject *obj) {
      return obj->property(propertyName().constData()).toPoint();
   }
   void updateState(State newState, State oldState) override {
      if (newState == Running && oldState == Stopped) {
         auto start = propertyOf(targetObject());
         auto end = propertyOf(other);
         auto step1 = fabs(offset);
         auto step2 = QLineF(start,end).length();
         auto steps = 2.0*step1 + step2;
         setStartValue(start);
         setKeyValueAt(step1/steps, QPoint(start.x(), start.y() + offset));
         setKeyValueAt((step1+step2)/steps, QPoint(end.x(), end.y() + offset));
         setEndValue(end);
         setDuration(10.0 * steps);
      }
      QPropertyAnimation::updateState(newState, oldState);
   }
public:
   SwapAnimation(QObject *first, QObject *second, qreal offset) : other(second), offset(offset) {
      setTargetObject(first);
      setPropertyName("pos");
   }
};

QParallelAnimationGroup* getSwapAnimation(QObject *obj1, QObject *obj2)
{
   auto const swapHeight = 75.0;
   auto par = new QParallelAnimationGroup;
   par->addAnimation(new SwapAnimation(obj2, obj1, -swapHeight));
   par->addAnimation(new SwapAnimation(obj1, obj2, swapHeight));
   return par;
}

int main(int argc, char **argv)
{
   QApplication app(argc, argv);

   QGraphicsScene scene(0, 0, 300, 300);
   QGraphicsRectWidget buttons[4];
   int i = 0;
   QPointF start(20, 125);
   for (auto & button : buttons) {
      button.setZValue(i++);
      button.resize(50,50);
      button.setPos(start);
      start.setX(start.x() + 70);
      scene.addItem(&button);
   }

   QSequentialAnimationGroup gr;
   gr.addAnimation(getSwapAnimation(&buttons[0], &buttons[1]));
   gr.addAnimation(getSwapAnimation(&buttons[1], &buttons[2]));
   gr.addAnimation(getSwapAnimation(&buttons[2], &buttons[3]));
   gr.addAnimation(getSwapAnimation(&buttons[3], &buttons[1]));
   gr.start();

   QGraphicsView view(&scene);
   view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
   view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
   view.resize(300, 300);
   view.show();
   return app.exec();
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thanks a lot for your solution! Though, I have already found one without using animation. Hope one finds it useful! – Vadixem Nov 28 '16 at 17:00