Search code examples
pythonpyqt5resizeqpropertyanimation

Resize widget from center when animating the size property


I'm trying to code a widget that slightly increases in size on mouse-over and decreases when the mouse leaves again.
This is what I have come up with so far:

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

from random import randrange


class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setFixedSize(500, 500)

        cent_widget = QWidget()
        self.setCentralWidget(cent_widget)

        layout = QVBoxLayout()
        cent_widget.setLayout(layout)

        layout.addWidget(MyItem(), Qt.AlignCenter,
                         alignment=Qt.AlignCenter)


class MyItem(QLabel):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setBaseSize(200, 250)
        self.setMinimumSize(self.baseSize())
        self.resize(self.baseSize())

        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        self.setStyleSheet('background: #{:02x}{:02x}{:02x}'.format(
            randrange(255), randrange(255), randrange(255)
        ))

        # Animation
        self._enlarged = False
        self.zoom_factor = 1.2

        self.anim = QPropertyAnimation(self, b'size')
        self.anim.setEasingCurve(QEasingCurve.InOutSine)
        self.anim.setDuration(250)

    def enterEvent(self, event: QEvent) -> None:
        self.resize_anim()
        self._enlarged = True

    def leaveEvent(self, event: QEvent) -> None:
        self.resize_anim()
        self._enlarged = False

    def resize_anim(self):
        if self._enlarged:
            new_size = self.baseSize()
        else:
            new_size = QSize(
                int(self.baseSize().width() * self.zoom_factor),
                int(self.baseSize().height() * self.zoom_factor)
            )
        self.anim.setEndValue(new_size)
        self.anim.start()


if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

Green rectangle that gets resized from the top left corner on mouse-over

It's almost working the way I want, my only problem is that the widget gets resized from the top-left corner instead of from the center.
How can I change that?


Solution

  • Instead of animating using the size property you should use the geometry property as it is relative to the parent widget, so you can animate its geometry making the center remain invariant.

    from PyQt5.QtCore import (
        QAbstractAnimation,
        QEasingCurve,
        QEvent,
        QPropertyAnimation,
        QRect,
        Qt,
    )
    from PyQt5.QtGui import QColor
    from PyQt5.QtWidgets import (
        QApplication,
        QLabel,
        QMainWindow,
        QSizePolicy,
        QVBoxLayout,
        QWidget,
    )
    
    import random
    
    
    class MainWindow(QMainWindow):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            self.setFixedSize(500, 500)
    
            cent_widget = QWidget()
            self.setCentralWidget(cent_widget)
    
            layout = QVBoxLayout(cent_widget)
            layout.addWidget(MyItem(), alignment=Qt.AlignCenter)
    
    
    class MyItem(QLabel):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            self.setBaseSize(200, 250)
            self.setMinimumSize(self.baseSize())
            self.resize(self.baseSize())
    
            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    
            self.setStyleSheet(
                "background: {}".format(QColor(*random.sample(range(255), 3)).name())
            )
    
            # Animation
            self.zoom_factor = 1.2
    
            self.anim = QPropertyAnimation(self, b"geometry")
            self.anim.setEasingCurve(QEasingCurve.InOutSine)
            self.anim.setDuration(250)
    
        def enterEvent(self, event: QEvent) -> None:
    
            initial_rect = self.geometry()
            final_rect = QRect(
                0,
                0,
                int(initial_rect.width() * self.zoom_factor),
                int(initial_rect.height() * self.zoom_factor),
            )
            final_rect.moveCenter(initial_rect.center())
    
            self.anim.setStartValue(initial_rect)
            self.anim.setEndValue(final_rect)
            self.anim.setDirection(QAbstractAnimation.Forward)
            self.anim.start()
    
        def leaveEvent(self, event: QEvent) -> None:
            self.anim.setDirection(QAbstractAnimation.Backward)
            self.anim.start()
    
    
    if __name__ == "__main__":
        app = QApplication([])
        window = MainWindow()
        window.show()
        app.exec()