Search code examples
qtgridqgraphicsviewqt5qgraphicsscene

Aligning QGraphicsItems to a grid when dragging and dropping


For example, if I wanted to display the player's inventory in a game using a QGraphicsView, how could I enforce a grid-based view where dragging and dropping items always results in them being aligned to a grid?


Solution

  • You can handle the appropriate mouse events in a QGraphicsScene subclass:

    #include <QApplication>
    #include <QGraphicsItem>
    #include <QGraphicsScene>
    #include <QGraphicsSceneMouseEvent>
    #include <QGraphicsView>
    #include <QMainWindow>
    
    #include <math.h>
    
    class GridScene : public QGraphicsScene
    {
    public:
        GridScene() :
            mCellSize(25, 25)
        {
        }
    protected:
        // Efficiently draws a grid in the background.
        // For more information: http://www.qtcentre.org/threads/5609-Drawing-grids-efficiently-in-QGraphicsScene?p=28905#post28905
        void drawBackground(QPainter *painter, const QRectF &rect)
        {
            qreal left = int(rect.left()) - (int(rect.left()) % mCellSize.width());
            qreal top = int(rect.top()) - (int(rect.top()) % mCellSize.height());
    
            QVarLengthArray<QLineF, 100> lines;
    
            for (qreal x = left; x < rect.right(); x += mCellSize.width())
                lines.append(QLineF(x, rect.top(), x, rect.bottom()));
            for (qreal y = top; y < rect.bottom(); y += mCellSize.height())
                lines.append(QLineF(rect.left(), y, rect.right(), y));
    
            painter->drawLines(lines.data(), lines.size());
        }
    
        void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
        {
            mDragged = qgraphicsitem_cast<QGraphicsItem*>(itemAt(mouseEvent->scenePos(), QTransform()));
            if (mDragged) {
                mDragOffset = mouseEvent->scenePos() - mDragged->pos();
            } else
                QGraphicsScene::mousePressEvent(mouseEvent);
        }
    
        void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
        {
            if (mDragged) {
                // Ensure that the item's offset from the mouse cursor stays the same.
                mDragged->setPos(mouseEvent->scenePos() - mDragOffset);
            } else
                QGraphicsScene::mouseMoveEvent(mouseEvent);
        }
    
        void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
        {
            if (mDragged) {
                int x = floor(mouseEvent->scenePos().x() / mCellSize.width()) * mCellSize.width();
                int y = floor(mouseEvent->scenePos().y() / mCellSize.height()) * mCellSize.height();
                mDragged->setPos(x, y);
                mDragged = 0;
            } else
                QGraphicsScene::mouseReleaseEvent(mouseEvent);
        }
    private:
        // The size of the cells in the grid.
        const QSize mCellSize;
        // The item being dragged.
        QGraphicsItem *mDragged;
        // The distance from the top left of the item to the mouse position.
        QPointF mDragOffset;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QMainWindow w;
        w.resize(400, 400);
        w.show();
    
        QGraphicsView view(&w);
        view.setScene(new GridScene());
        view.resize(w.size());
        view.setSceneRect(0, 0, view.size().width(), view.size().height());
        view.show();
    
        view.scene()->addRect(0, 0, 25, 25)->setBrush(QBrush(Qt::blue));
    
        return a.exec();
    }
    
    #include "main.moc"