From a779b950655be8621417fbe59d6fe184827b0fa4 Mon Sep 17 00:00:00 2001 From: En Yi Date: Thu, 19 Jul 2018 14:32:45 +0800 Subject: [PATCH] Add animation to difficulty selector --- graphic_components/menu_graphics.py | 7 +- graphic_components/scoreboard.py | 208 +++++++++++++++++++++++++++- 2 files changed, 204 insertions(+), 11 deletions(-) diff --git a/graphic_components/menu_graphics.py b/graphic_components/menu_graphics.py index d37d03e..8db04f9 100644 --- a/graphic_components/menu_graphics.py +++ b/graphic_components/menu_graphics.py @@ -197,18 +197,17 @@ class HighScoreDisplayer(QGraphicsObject): def paint(self, painter, style, widget=None): painter.setPen(self.box_pen) painter.drawRect(self.boundingRect()) - #if self.selected: - # painter.drawRect(self.btn1) - # painter.drawRect(self.btn2) def hoverEnterEvent(self, ev): if not self.selected: self.scoreboard_widget.setVisible(True) + self.scoreboard_widget.show_scores(True) self.prepareGeometryChange() self.size = self.board_size def hoverLeaveEvent(self, ev): self.scoreboard_widget.setVisible(False) + self.scoreboard_widget.show_scores(False) self.prepareGeometryChange() self.size = self.icon_size @@ -235,4 +234,4 @@ if __name__ == "__main__": view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) view.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/graphic_components/scoreboard.py b/graphic_components/scoreboard.py index 351f8c9..bef5770 100644 --- a/graphic_components/scoreboard.py +++ b/graphic_components/scoreboard.py @@ -5,6 +5,12 @@ from PyQt5.QtCore import (QAbstractAnimation, QObject, QPointF, Qt, QRectF, QLin QPropertyAnimation, pyqtProperty, pyqtSignal, QSizeF, QTimer) from PyQt5.Qt import QApplication import sys +import random + +if not __name__ == "__main__": + sys.path.append("~/PycharmProjects/sudoku") + +from general import highscore as hs class HighScoreBoard(QWidget): @@ -12,26 +18,83 @@ class HighScoreBoard(QWidget): super().__init__() self.layout = QVBoxLayout(self) + self.layout.setAlignment(Qt.AlignCenter) self.layout.addLayout(DifficultySwitch()) - self.layout.addLayout(ScoreGrid()) + self.score_grid = ScoreGrid() + self.layout.addLayout(self.score_grid) self.layout.addWidget(NameInput()) self.setFixedSize(width, height) + self.setStyleSheet(""" + background-color: rgb(0, 0, 0); + color: rgb(255, 255, 255); + """) + + def show_scores(self, toggle): + self.score_grid.show_score_info(toggle) + class DifficultySwitch(QHBoxLayout): def __init__(self): super().__init__() + self.max_length = max(len(diff) for diff in hs.DIFFICULTIES) + self.full_text = ''.join(d.center(self.max_length) for d in hs.DIFFICULTIES) + left_btn = QPushButton('<') - difficulty_display = QLabel('Normal') + self.difficulty_display = QLabel('Normal') + self.difficulty_display.setAlignment(Qt.AlignCenter) right_btn = QPushButton('>') self.addWidget(left_btn) - self.addWidget(difficulty_display) + self.addWidget(self.difficulty_display) self.addWidget(right_btn) + self.show_pos = 0 + self.reach_end = True + self.anim = QPropertyAnimation(self, b'show_pos') + self.anim.setDuration((len(hs.DIFFICULTIES) - 1) * self.max_length * 20) + self.anim.setStartValue(0) + self.anim.setEndValue((len(hs.DIFFICULTIES) - 1) * self.max_length) + left_btn.clicked.connect(lambda: self.shift_difficulty(QAbstractAnimation.Backward)) + right_btn.clicked.connect(lambda: self.shift_difficulty(QAbstractAnimation.Forward)) + self.anim.valueChanged.connect(self.pause_anim) + + @pyqtProperty(int) + def show_pos(self): + """ + int : The value for the animation + + When the value is set, the text to be printed is generated accordingly. + It determines whether actual text is to be printed, and retains the + paragraphs when printing garbage. + """ + return self._shown_length + + @show_pos.setter + def show_pos(self, value): + self._shown_length = value + self.difficulty_display.setText(self.full_text[value:value+9]) + + def shift_difficulty(self, direction): + if not self.anim.state() == QAbstractAnimation.Running: + if (direction == QAbstractAnimation.Forward and self.show_pos < self.anim.endValue()) \ + or (direction == QAbstractAnimation.Backward and self.show_pos > self.anim.startValue()): + self.anim.setDirection(direction) + if self.anim.state() == QAbstractAnimation.Paused: + self.anim.resume() + else: + self.anim.start() + + def pause_anim(self, value): + if value % 9 == 0: + if value == self.anim.endValue() or value == self.anim.endValue(): + self.reach_end = True + else: + self.anim.pause() + class ScoreGrid(QGridLayout): @@ -39,14 +102,23 @@ class ScoreGrid(QGridLayout): super().__init__() for i in range(5): - label = QLabel(str(i)+'.') + label = QLabel(str(i+1)+'.') self.addWidget(label, i, 0) + self.animated_labels = [] for i, name in enumerate('ABCDE'): - label1 = QLabel(name) - label2 = QLabel('0') + label1 = AnimatedLabel(name * 7) + label1.setAlignment(Qt.AlignCenter) + label2 = AnimatedLabel('0'*5) + label2.setAlignment(Qt.AlignRight) self.addWidget(label1, i, 1) self.addWidget(label2, i, 2) + self.animated_labels.append(label1) + self.animated_labels.append(label2) + + def show_score_info(self, toggle): + for label in self.animated_labels: + label.toggle_anim(toggle) class NameInput(QWidget): @@ -62,10 +134,132 @@ class NameInput(QWidget): self.layout.addWidget(self.name_input) +class AnimatedLabel(QLabel): + """ + QLabel that a message, which is displayed through animation. + """ + + def __init__(self, text, speed=75, delay=20, parent=None): + """ + Does some text processing, and set up the animation to display the text + + Parameters + ---------- + text: str + Text to be displayed + speed : int + The period at which a new character is printed + The total time is calculated as length of text * speed + 0 means instant display, like a regular QLabel. + delay : int + The number of garbage to be printed before printing the actual text + parent: QWidget + Pass into QLabel init method + """ + super().__init__(text, parent) + + self.speed = speed + + self.setWordWrap(True) + self.setContentsMargins(0, 0, 0, 0) + # Text processing + # Make sure the garbage does not exceed the length of actual text + self.actual_text = text + self.shown_text = '' + if delay >= 0: + self.delay = min(delay, len(self.actual_text)) + else: + self.delay = len(self.actual_text) + + # Find out where the new paragraphs are so that it is retained + self.splitpoints = [] + current_point = 0 + line_splits = self.actual_text.split('\n') + for line in line_splits: + current_point += len(line) + self.splitpoints.append(current_point) + current_point += 1 + + # Set up the shown text length to be animated + self.shown_length = 0 + self.anim = QPropertyAnimation(self, b'shown_length') + self.anim.setDuration(len(self.actual_text) * speed) + self.anim.setStartValue(0) + self.anim.setEndValue(len(self.actual_text) + self.delay) + + #self.setStyleSheet(""" + # color: rgb(0, 255, 0); + # """) + self.toggle_anim(True) + + @pyqtProperty(int) + def shown_length(self): + """ + int : The value for the animation + + When the value is set, the text to be printed is generated accordingly. + It determines whether actual text is to be printed, and retains the + paragraphs when printing garbage. + """ + return self._shown_length + + @shown_length.setter + def shown_length(self, value): + self._shown_length = value + + if value < self.delay: + # All printed text should be garbage + garbage = [chr(num) for num in + [random.choice(range(33, 127)) for _ in range(value)]] + + # Retain the paragraphs + for num in self.splitpoints[:-1]: + if num < value: + garbage[num] = '\n' + + self.setText(''.join(garbage)) + else: + # Printed text contain some actual text + garbage = [chr(num) for num in + [random.choice(range(33, 127)) for _ in + range(min(len(self.actual_text) + self.delay - value, self.delay))]] + + # Retain the paragraphs, but offset by the number of actual text + non_garbage = value - self.delay + for num in self.splitpoints[:-1]: + if num - non_garbage > 0 and num < value: + garbage[num - non_garbage] = '\n' + + self.setText(self.actual_text[:value - self.delay] + ''.join(garbage)) + + def toggle_anim(self, toggling): + """ + Toggle the animation to be play forward or backward + + Parameters + ---------- + toggling: bool + True for forward, False for backward + """ + if toggling: + self.anim.setDirection(QAbstractAnimation.Forward) + else: + self.anim.setDirection(QAbstractAnimation.Backward) + + self.anim.start() + + def replace_text(self, new_text): + self.shown_length = 0 + self.actual_text = new_text + self.anim.setDuration(len(self.actual_text) * self.speed) + self.anim.setEndValue(len(self.actual_text) + self.delay) + self.toggle_anim(True) + + if __name__ == '__main__': app = 0 app = QApplication(sys.argv) - ex = HighScoreBoard() + ex = HighScoreBoard(500, 500) ex.show() sys.exit(app.exec_()) \ No newline at end of file