Search code examples
qtswapqgraphicsviewqgraphicswidget

How to perform swap of QGraphicsItem(s) in QGraphicsView?


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


Solution

  • 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();
    }