Search code examples
pythonanimationpyqt5qgraphicssceneqpropertyanimation

How do I animate the scene Rectangle?


I am trying to move my QGraphicsScene rectangle using a animation to get the impression that it moves smoothly. But I don't know how to do it works. Could some one help me? I would like to konow if its possible to animate a Qtransform instance. if it is, how can I do that?

Issues:

1 - How animate the translate function, which moves my scene rectangle. I want to animate it, because I want that it looks smooth.

2 - It's possible to animate a Qtransform instance?

here is my code:

from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore


class Example(QWidget):

    def __init__(self, parent=None):
        super(Example, self).__init__(parent) # this widget has no parent
        hbox = QHBoxLayout(self)
        self.setMinimumHeight(500)
        self.setMinimumWidth(500)
        self.button = QtWidgets.QPushButton("move scene", self)  # this button is responsible to emit a signal to animate the
        # scene translation
        self.button.clicked.connect(self.do)  # connect to the animation
        self.scene = QtWidgets.QGraphicsScene()  # instantiate the scene
        self.view = QtWidgets.QGraphicsView(self)  # instantiate the view
        self.view.setScene(self.scene)
        hbox.addWidget(self.view)  # insert into the layout
        hbox.addWidget(self.button)  # insert into the layout
        self.r = self.view.mapToScene(self.view.viewport().rect()).boundingRect()# take the viewport bounding rectangle
        self.view.setSceneRect(self.r)  # define the viewport bounding rectangle as the initial scene rectangle
        self.scene.addEllipse(self.r, QtGui.QPen(QtGui.QBrush(QtCore.Qt.darkRed), 10, join=QtCore.Qt.RoundJoin))
        # draw an ellipse in our scene
        self.scene.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.CrossPattern))  # set a grid patter to turn easier to
        # see the displacements of objects

    def translateSceneSmooth(self, ds: tuple) -> None:
        """
        ds = (dx, dy)
        :param ds: is the differential in x and y.
        :return: None
        """
        self.view.setSceneRect(self.view.sceneRect().translated(*ds)) # I want  that the animation pass a interpolation
        # of a tuple here: starting at (0, 0) and ending at (100, 100)
        # I don't know if this approach is correct.
        # Because maybe it will not  move 100 px.if I have a list of numbers in the form passing through the function:
        # [0, 10, 20, 50, 70, 100] maybe it'll move 0+10+20+50+70+100 = 250 px

    def do(self, duration=100):
        """
        I want the  scene rectangle to move smoothly
        """
        print('Starting animation')
        self._animation = QtCore.QVariantAnimation()
        self._animation.setStartValue(0)
        self._animation.setEndValue(1000)
        self._animation.setDuration(duration)
        self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
        self._animation.valueChanged.connect(self.translateSceneSmooth)
        print('Ending animation')


if __name__ == "__main__":
    app = QApplication([])
    ex = Example()
    ex.show()
    app.exec_()

Solution

  • If you want to move the sceneRect, generate an animation of the rectangle by calculating the starting and ending rectangles. On the other hand, by default, the pressed signal passes a boolean that overrides the default value of "duration", a possible solution is to use the pyqtSlot decorator to make the connection signature explicit:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Example(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(Example, self).__init__(parent)
    
            self.setMinimumSize(500, 500)
            self.button = QtWidgets.QPushButton("move scene")
            # scene translation
            self.button.clicked.connect(self.do)
            self.scene = QtWidgets.QGraphicsScene()
            self.view = QtWidgets.QGraphicsView()
            self.view.setScene(self.scene)
    
            hbox = QtWidgets.QHBoxLayout(self)
            hbox.addWidget(self.view)
            hbox.addWidget(self.button)
    
            self.r = self.view.mapToScene(self.view.viewport().rect()).boundingRect()
            self.view.setSceneRect(self.r)
            self.scene.addEllipse(
                self.r,
                QtGui.QPen(QtGui.QBrush(QtCore.Qt.darkRed), 10, join=QtCore.Qt.RoundJoin),
            )
            self.scene.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.CrossPattern))
    
        @QtCore.pyqtSlot()
        def do(self, duration=100):
            """
            I want the  scene rectangle to move smoothly
            """
            ds = QtCore.QPointF(100, 100)
    
            current_rect = self.view.sceneRect()
            next_rect = current_rect.translated(ds)
            self._animation = QtCore.QVariantAnimation()
            self._animation.setStartValue(current_rect)
            self._animation.setEndValue(next_rect)
            self._animation.setDuration(duration)
            self._animation.valueChanged.connect(self.view.setSceneRect)
            self._animation.finished.connect(lambda: print("Ending animation"))
            self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
            print("Starting animation")
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication([])
        ex = Example()
        ex.show()
        app.exec_()