Search code examples
pythonqtpyqt

Transition animation of QComboBox item's background


My goal is to create a slow transition animation of QComboBox items when hovering over them - I want it to slowly transition from gray color (QColor(198, 198, 198)) to white (QColor(255, 255, 255)) in span of over 300ms, this is how my code looks so far:

from PySide6.QtCore import *
from PySide6.QtWidgets import *
from PySide6.QtGui import *
import sys

class ComboBoxDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        if option.state & QStyle.State_MouseOver:   # if hover
            background_color = QColor(255, 255, 255)  
        else:                                       # leaving hover
            background_color = QColor(198, 198, 198)  
        painter.fillRect(option.rect, background_color)
        painter.drawText(option.rect, index.data())


class MainWidget(QWidget):
    def __init__(self):
        super(MainWidget, self).__init__()
        self.combo_box = QComboBox(self)
        self.combo_box.addItems(['Item 1','Item 2', 'Item 3'])
        self.main_layout = QVBoxLayout()
        self.main_layout.addWidget(self.combo_box)
        self.setLayout(self.main_layout)

        delegate = ComboBoxDelegate(self.combo_box)
        self.combo_box.setItemDelegate(delegate)


app = QApplication(sys.argv)
window = MainWidget()
window.show()
sys.exit(app.exec())

For now this lets me hover over items in QComboBox and change their background color however without smooth transition I desire.

I've tried using .highlighted.connect() and grabbing the item with it then tried to animate it via QVariantAnimation however it doesn't let me animate QStandardItem with it, then I've tried using QStyledItemDelegate, and now I am stuck figuring out how can I deploy this transition because I believe it won't be possible to take item from QComboBox as QObject.


Solution

  • You can interpolate QColor values with QVariantAnimation and redraw an item of the popup with QAbstractItemView.update(), like the following example.

    ...
    
    class ComboBoxDelegate(QStyledItemDelegate):
        def __init__(self, combo_box):
            super().__init__(combo_box)
            self.selected_index = None
            self.anim = anim = QVariantAnimation()
            anim.setStartValue(QColor(Qt.gray))
            anim.setEndValue(QColor(Qt.red))
            anim.setDuration(1000)
            anim.valueChanged.connect(lambda _:
                combo_box.view().update(self.selected_index))
    
        def paint(self, painter, option, index):
            anim = self.anim
            if option.state & QStyle.State_Selected:
                if index != self.selected_index:
                    self.selected_index = index
                    anim.stop()
                    anim.start()
                background_color = anim.currentValue()
            else:
                background_color = anim.startValue()
            painter.fillRect(option.rect, background_color)
            painter.drawText(option.rect, index.data())
    
    ...