pythonpyqtpyqt5qpainterpathqpropertyanimation

how enable widget moved when window size changed with paint event?


I am try to create a widget to animate a line, the when the windows size is changed, the animation always play the size.

I know the PainterPath is always same, but i have no any simple idea to do?

from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class PathAnimation(QPropertyAnimation):
    def __init__(self, path=QPainterPath(), object=None, property=None):
        super().__init__(object, property)
        self.path = path

    def updateCurrentTime(self, currentTime):
        easedProgress = self.easingCurve().valueForProgress(currentTime/self.duration())
        pt = self.path.pointAtPercent(easedProgress)
        self.updateCurrentValue(pt)
        self.valueChanged.emit(pt)

class DemoB(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(400, 400)
        self.button = QPushButton('test', self)

    def mouseDoubleClickEvent(self, e):
        self.ani = PathAnimation(self.path, self.button, b'pos')
        self.ani.setDuration(2000)
        self.ani.start()

    def paintEvent(self, e):
        painter = QPainter(self)
        painter.begin(self)
        painter.setWindow(0, 0, 400, 400)
        self.path = QPainterPath()
        self.path.cubicTo(QPointF(0, 400), QPointF(200, 0), QPointF(400, 400))

        painter.drawPath( self.path )
        painter.end()

app = QApplication([])
demo = DemoB()
demo.show()
app.exec()

Solution

  • Sorry but your question is a bit confused. If I understand it correctly, you want to update the path everytime the window is resized.
    The problem is that you create a new self.path object whenever the window is painted, which will also happen as soon as the window is painted the first time, so the QPainterPath object you created for the property is not actually updated.

    You should update the path only when the window is resized, which within resizeEvent().

    Then keep in mind that you can update an existing path only from Qt 5.13 (which was released last June), otherwise you'll have to create a new one and ensure to update the path property of your animation too.

    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    
    # find if Qt version is >= 5.13
    QtVersion = [int(v) for v in QT_VERSION_STR.split('.')]
    CanClearPath = QtVersion[0] == 5 and QtVersion[1] >= 13
    
    class PathAnimation(QPropertyAnimation):
        def __init__(self, path=QPainterPath(), object=None, property=None):
            super().__init__(object, property)
            self.path = path
    
        def updateCurrentTime(self, currentTime):
            easedProgress = self.easingCurve().valueForProgress(currentTime/self.duration())
            pt = self.path.pointAtPercent(easedProgress)
            self.updateCurrentValue(pt)
            self.valueChanged.emit(pt)
    
    class DemoB(QWidget):
        def __init__(self):
            super().__init__()
            self.resize(400, 400)
            self.button = QPushButton('test', self)
            self.path = QPainterPath()
            self.ani = PathAnimation(self.path, self.button, b'pos')
            self.ani.setDuration(2000)
    
        def mouseDoubleClickEvent(self, e):
            if self.ani.state():
                return
            self.ani.setStartValue(QPointF(0, 0))
            self.ani.start()
    
        def resizeEvent(self, event):
            if CanClearPath:
                self.path.clear()
            else:
                self.path = QPainterPath()
            rect = self.rect()
            # use the current size to update the path positions
            self.path.cubicTo(rect.bottomLeft(), 
                QPointF(rect.center().x(), 0), rect.bottomRight())
            # update the final value!
            self.ani.setEndValue(rect.bottomRight())
            if not CanClearPath:
                self.ani.path = self.path
    
        def paintEvent(self, e):
            painter = QPainter(self)
            # no need to begin() a painter if you already gave it its argument
            painter.drawPath(self.path)
    
    if __name__ == '__main__':
        import sys
        app = QApplication(sys.argv)
        demo = DemoB()
        demo.show()
        sys.exit(app.exec())
    

    Another possibility is to avoid a QPropertyAnimation subclass at all, use a private property that will go from 0.0 to 1.0, create a QPropertyAnimation for that, then connect its valueChanged signal to a function that computes the position using pointAtPercent and then move the button.

    class DemoB(QWidget):
        def __init__(self):
            super().__init__()
            self.resize(400, 400)
            self.button = QPushButton('test', self)
            self.path = QPainterPath()
            # create an internal property
            self.setProperty('pathPercent', 0.0)
            self.ani = QPropertyAnimation(self, b'pathPercent')
            self.ani.setEndValue(1.0)
            self.ani.setDuration(2000)
            self.ani.valueChanged.connect(self.updatePosition)
    
        def mouseDoubleClickEvent(self, e):
            if self.ani.state():
                return
            # reset the property to 0 so that it is restarted from the beginning
            self.setProperty('pathPercent', 0.0)
            self.ani.start()
    
        def updatePosition(self, pos):
            self.button.move(self.path.pointAtPercent(pos).toPoint())
    
        def resizeEvent(self, event):
            # recreate/update the current path, based on the new window size.
            if CanClearPath:
                self.path.clear()
            else:
                self.path = QPainterPath()
            rect = self.rect()
            self.path.cubicTo(rect.bottomLeft(), 
                QPointF(rect.center().x(), 0), rect.bottomRight())
    
        def paintEvent(self, e):
            painter = QPainter(self)
            painter.drawPath(self.path)