Satyam Niranjan
Satyam Niranjan

Reputation: 5

Adding animation to QPushbutton enterEvent and exitEvent

I'm trying to add custom animation to QPushbutton without making a custom QPushbutton and overriding its enterEvent() and leaveEvent().

So far I've tried this,

@staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
    '''
    Method to:
    => Add hover animation for provided button
    '''
    enterShift = QPropertyAnimation(button,b'pos',button)
    exitShift = QPropertyAnimation(button,b'pos',button)

    def enterEvent(e):
        pos=button.pos()            
        enterShift.setStartValue(pos)
        enterShift.setEndValue(QPoint(pos.x()+3,pos.y()+3))
        enterShift.setDuration(100)
        enterShift.start()
        Effects.dropShadow(button,1,2)
        

    def leaveEvent(e):
        pos=button.pos()            
        exitShift.setStartValue(pos)
        exitShift.setEndValue(QPoint(pos.x()-3,pos.y()-3))
        exitShift.setDuration(100)
        exitShift.start()
        Effects.dropShadow(button)
    

    button.enterEvent=enterEvent
    button.leaveEvent=leaveEvent

But when I move the mouse very quickly in and out of the button before the animation finishes, The button starts to move wierdly towards the North-West direction.

Button Animation Using Dynamic Positions

I figured out this was due to the leaveEvent() being triggered before enterEvent() even finishes and also because the start and end values are dynamic. So, I tried providing currentPos as a static position and using it instead,

@staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
    '''
    Method to:
    => Add hover animation for provided button
    '''
    enterShift = QPropertyAnimation(button,b'pos',button)
    enterShift.setStartValue(currentPos)
    enterShift.setEndValue(QPoint(currentPos.x()+3,currentPos.y()+3))
    enterShift.setDuration(100) 
    exitShift = QPropertyAnimation(button,b'pos',button)
    exitShift.setStartValue(QPoint(currentPos.x()-3,currentPos.y()-3))
    exitShift.setEndValue(currentPos)
    exitShift.setDuration(100)  


    def enterEvent(e):  
        button.setProperty(b'pos',exitShift.endValue())
        enterShift.start()
        Effects.dropShadow(button,1,2)
        

    def leaveEvent(e):
        exitShift.start()
        Effects.dropShadow(button)
    

    button.enterEvent=enterEvent
    button.leaveEvent=leaveEvent

On running, as soon as the mouse enters the QPushbutton, it moves to the top-left of its parent widget and the animation starts working fine. I can't figure out why this is happening. But I was able to get that, it only happened when I used any static value in the animation.

Button Animation with Static Position:

Button Animation with Static Position

Here is an example:

import sys
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
# This is the same method mentioned above
from styling import addButtonHoverAnimation


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout=QVBoxLayout()

        button1 = QPushButton("Proceed1", self)
        layout.addWidget(button1)

        button2 = QPushButton("Proceed2", self)
        layout.addWidget(button2)
        self.setLayout(layout)

        self.resize(640, 480)
        addButtonHoverAnimation(button1)
        addButtonHoverAnimation(button2)


def main():
    app = QApplication(sys.argv)

    view = Widget()
    view.show()

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main()

Upvotes: 0

Views: 687

Answers (1)

eyllanesc
eyllanesc

Reputation: 243897

The problem is that probably when the state is changed from enter to leave (or vice versa) the previous animation still does not end so the position of the widget is not the initial or final position, so when starting the new animation there is a deviation that accumulates. One possible solution is to initialize the position and keep it as a reference.

On the other hand you should not do x.fooMethod = foo_callable since many can fail, in this case it is better to use an eventfilter.

import sys
from dataclasses import dataclass
from functools import cached_property

from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget


@dataclass
class AnimationManager(QObject):
    widget: QWidget
    delta: QPoint = QPoint(3, 3)
    duration: int = 100

    def __post_init__(self):
        super().__init__(self.widget)
        self._start_value = QPoint()
        self._end_value = QPoint()
        self.widget.installEventFilter(self)
        self.animation.setTargetObject(self.widget)
        self.animation.setPropertyName(b"pos")
        self.reset()

    def reset(self):
        self._start_value = self.widget.pos()
        self._end_value = self._start_value + self.delta
        self.animation.setDuration(self.duration)

    @cached_property
    def animation(self):
        return QPropertyAnimation(self)

    def eventFilter(self, obj, event):
        if obj is self.widget:
            if event.type() == QEvent.Enter:
                self.start_enter_animation()
            elif event.type() == QEvent.Leave:
                self.start_leave_animation()
        return super().eventFilter(obj, event)

    def start_enter_animation(self):
        self.animation.stop()
        self.animation.setStartValue(self.widget.pos())
        self.animation.setEndValue(self._end_value)
        self.animation.start()

    def start_leave_animation(self):
        self.animation.stop()
        self.animation.setStartValue(self.widget.pos())
        self.animation.setEndValue(self._start_value)
        self.animation.start()


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        button1 = QPushButton("Proceed1", self)
        button1.move(100, 100)

        button2 = QPushButton("Proceed2", self)
        button2.move(200, 200)

        self.resize(640, 480)

        animation_manager1 = AnimationManager(widget=button1)
        animation_manager2 = AnimationManager(widget=button2)


def main():
    app = QApplication(sys.argv)

    view = Widget()
    view.show()

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main()

184 / 5000 Resultados de traducción If you are using a layout then you must reset the position since the layout does not apply the position change immediately but only when the parent widget applies the changes.

class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        button1 = QPushButton("Proceed1")
        button2 = QPushButton("Proceed2")

        lay = QVBoxLayout(self)
        lay.addWidget(button1)
        lay.addWidget(button2)

        self.resize(640, 480)

        self.animation_manager1 = AnimationManager(widget=button1)
        self.animation_manager2 = AnimationManager(widget=button2)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.animation_manager1.reset()
        self.animation_manager2.reset()

Upvotes: 1

Related Questions