Как реализовать надежную очередь анимации с Qt для Python (PySide2) - PullRequest
0 голосов
/ 26 января 2019

Я занимаюсь разработкой приложения на Python с использованием Qt для Python (PySide2). Я компетентный Java-разработчик среднего уровня, однако Python и GUI для меня в основном новы.

Графический интерфейс пользователя состоит из одного главного окна с рядом значков (кнопок) внутри QScrollArea, который прокручивает влево и вправо по центру экрана, когда пользователь меняет свой выбор с помощью клавиатуры или джойстика. Это упрощенная версия интерфейса Sony XMB . Я включаю только горизонтальное меню, а не дополнительное вертикальное меню, и пока оно работает довольно хорошо. Однако теперь я хотел бы улучшить производительность анимации меню, внедрив выделенную очередь анимации FIFO, но каждый подход, который я пробовал до сих пор, приводил меня к препятствиям.

Основная проблема заключается в том, что делать, когда пользователь перемещается влево или вправо два или более раз подряд, чтобы предыдущая анимация не была завершена до запуска следующей. Наилучший подход, который я нашел до сих пор, заключается в том, чтобы по существу игнорировать события изменения выбора, пока анимация еще выполняется, но это может заставить интерфейс пользователя чувствовать себя немного безразличным.

Я собрал базовый пример ниже с более медленной анимацией, чтобы было проще воспроизвести эффект игнорируемых нажатий клавиш.

import sys

from PySide2 import QtWidgets
from PySide2.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QSizePolicy, \
    QSpacerItem, QApplication
from PySide2.QtCore import Qt, QSize, QPropertyAnimation, QPointF, Slot


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = "Test"
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle(self.title)
        self.setGeometry(400, 400, 1600, 900)

        self.widget = QWidget()
        self.setCentralWidget(self.widget)

        self.parent = QVBoxLayout()
        self.tool_parent = QHBoxLayout()

        self.exit_button = QPushButton("Exit")
        self.exit_button.setFixedSize(QSize(60, 60))
        self.exit_button.clicked.connect(self.on_quit)

        self.tool_parent.addWidget(self.exit_button)
        self.tool_parent.addSpacerItem(QSpacerItem(40, 40, QSizePolicy.Expanding, QSizePolicy.Minimum))

        self.parent.addLayout(self.tool_parent)
        self.parent.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding))
        self.widget.setLayout(self.parent)

        self.animation = QPropertyAnimation(self.exit_button, b'pos')
        self.animation.setDuration(300)

        self.show()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_L:
            self.shift_left()
        elif event.key() == Qt.Key_R:
            self.shift_right()
        elif event.key() == Qt.Key_Escape:
            self.on_quit()

    def shift_left(self):
        # Only shift left if there's space on the screen and
        # no animations are currently running
        if self.exit_button.geometry().topLeft().x() > (self.exit_button.width()) and self.animation.state() == 0:
            end = QPointF(self.exit_button.geometry().topLeft().x() - 100, self.exit_button.geometry().topLeft().y())
            self.animation.setEndValue(end)
            self.animation.start()

    def shift_right(self):
        # Only shift right if there's space on the screen and
        # no animations are currently running
        if self.exit_button.geometry().topRight().x() < (1600 - self.exit_button.width()) and self.animation.state() == 0:
            end = QPointF(self.exit_button.geometry().topLeft().x() + 100, self.exit_button.geometry().topLeft().y())
            self.animation.setEndValue(end)
            self.animation.start()

    def on_quit(self):
        QtWidgets.qApp.exit(0)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())

Нажатие клавиш «L» или «R» сместит кнопку влево или вправо на экране, используя анимацию, но любой ввод во время работы анимации игнорируется. Это поведение, которое я хотел бы улучшить, внедрив очередь анимации, которая удовлетворяет следующим требованиям:

  • Пользовательский интерфейс продолжает принимать входные данные во время работы анимации
  • Анимации не перекрываются, все они запускаются последовательно

Я пробовал несколько способов, в том числе:

  • Помещение анимации в очередь, которую отдельный рабочий поток опрашивает и обрабатывает, однако отдельные потоки не могут изменять элементы пользовательского интерфейса с помощью QT, поэтому это не работает.
  • Использование QSequentialAnimationGroup, но они не предназначены и не безопасны для повторного использования и добавления при запуске анимации.
  • Ввод цикла while () сразу после запуска анимации, чтобы дождаться его завершения, но это блокирует пользовательский интерфейс во время ожидания.

На данный момент у меня в основном нет идей, поэтому мы будем благодарны за любые советы.

...