SudokuGame/graphic_components/buttons.py

222 lines
7.4 KiB
Python

"""
This module contains all kinds of animated buttons
"""
import math
import random
from PyQt5.QtCore import (QAbstractAnimation, Qt, QRectF, QLineF,
QPropertyAnimation, pyqtProperty, pyqtSignal)
from PyQt5.QtGui import QPen
from PyQt5.QtWidgets import (QGraphicsObject)
from general import extras
RANDOMCHAR = "~!@#$%^&*()_+`-=[]\{}|;:'<>,./?\""
class AnimBox(QGraphicsObject):
# Prepare the signal
hoverEnter = pyqtSignal()
hoverExit = pyqtSignal()
buttonClicked = pyqtSignal(str)
# Initialisation
def __init__(self, x, y, width, height, text, parent=None):
super().__init__(parent=parent)
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
self.circumference = 2*(width+height)
# Set up pens for drawing
self.default_pen = QPen()
self.default_pen.setColor(Qt.white)
self.outline_pen = QPen()
self.outline_pen.setColor(Qt.white)
self.outline_pen.setWidth(5)
# Whether the mouse hover over the box
self.detected = False
self.btn_rect = QRectF(self.x, self.y, self.width, self.height)
# The 4 lines to construct the box
self.left = QLineF()
self.down = QLineF()
self.right = QLineF()
self.up = QLineF()
self.line_order = [self.up, self.right, self.down, self.left]
self.set_freeze(False)
# Length of the box to be drawn
self.length = 0
# Set up the length to be animated
self.anim = QPropertyAnimation(self, b'length')
self.anim.setDuration(400) # Animation speed
self.anim.setStartValue(0)
for t in range(1, 10):
self.anim.setKeyValueAt(t / 10, self.logistic_func(t / 10))
self.anim.setEndValue(self.circumference)
self.animText = AnimatedText(self.text, parent=self)
def set_freeze(self, freeze):
if freeze:
self.setAcceptedMouseButtons(Qt.NoButton)
self.setAcceptHoverEvents(False)
else:
self.setAcceptedMouseButtons(Qt.LeftButton)
self.setAcceptHoverEvents(True)
# Toggle the animation to be play forward or backward
def toggle_anim(self, toggling):
if toggling:
self.anim.setDirection(QAbstractAnimation.Forward)
else:
self.anim.setDirection(QAbstractAnimation.Backward)
self.anim.start()
# The logistic function that determines the animation motion
def logistic_func(self, x):
return self.circumference / (1+math.exp(-(x-0.5)*18))
# Reimplemented boundingRect
def boundingRect(self):
return QRectF(self.x-5, self.y-5, self.width+10, self.height+10)
# Reimplemented paint
def paint(self, painter, style, widget=None):
painter.setPen(self.outline_pen)
for line in self.line_order:
if line.length() > 1:
painter.drawLine(line)
painter.setPen(self.default_pen)
painter.fillRect(self.btn_rect, Qt.black)
painter.drawRect(self.btn_rect)
# Defining the length to be drawn as a pyqtProperty
@pyqtProperty(float)
def length(self):
return self._length
# Determine the length of the four lines to be drawn
@length.setter
def length(self, value):
self._length = value
remaining_length = value
if remaining_length >= 2 * self.width + self.height:
length_to_draw = remaining_length - (2 * self.width + self.height)
remaining_length -= length_to_draw
else:
length_to_draw = 0
self.line_order[3].setLine(self.x, self.y + self.height,
self.x, self.y + self.height - length_to_draw)
if remaining_length >= self.width + self.height:
length_to_draw = remaining_length - (self.width + self.height)
remaining_length -= length_to_draw
else:
length_to_draw = 0
self.line_order[2].setLine(self.x + self.width, self.y + self.height,
self.x + self.width - length_to_draw,
self.y + self.height)
if remaining_length >= self.width:
length_to_draw = remaining_length - self.width
remaining_length -= length_to_draw
else:
length_to_draw = 0
self.line_order[1].setLine(self.x + self.width, self.y,
self.x + self.width, self.y + length_to_draw)
self.line_order[0].setLine(self.x, self.y,
self.x + remaining_length, self.y)
self.update()
# Reimplemented hoverEvents to detect the mouse and toggle the animation
def hoverEnterEvent(self, event):
if ~self.detected:
self.hoverEnter.emit()
self.detected = True
self.toggle_anim(True)
super().hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
if self.detected:
self.hoverExit.emit()
self.detected = False
self.toggle_anim(False)
super().hoverLeaveEvent(event)
def mousePressEvent(self, event):
self.length = 0
self.buttonClicked.emit(self.text)
class AnimatedText(QGraphicsObject):
def __init__(self, text, parent=None):
super().__init__(parent=parent)
self.parent = parent
self.actual_text = text
self.shown_text = ''
self.delay = 3
# Set up pens for drawing
self.default_pen = QPen()
self.default_pen.setColor(Qt.white)
self.randomchar_list = [c for c in RANDOMCHAR]
self.shown_length = 0
# Set up the length to be animated
self.anim = QPropertyAnimation(self, b'shown_length')
self.anim.setDuration(len(self.actual_text) * 50) # Animation speed
self.anim.setStartValue(0)
for t in range(1, 10):
self.anim.setKeyValueAt(t / 10, (len(self.actual_text) + self.delay) * t/10)
self.anim.setEndValue(len(self.actual_text) + self.delay)
self.visibleChanged.connect(self.show_text)
def show_text(self):
if self.isVisible():
self.toggle_anim(True)
else:
self.shown_length = 0
# Toggle the animation to be play forward or backward
def toggle_anim(self, toggling):
if toggling:
self.anim.setDirection(QAbstractAnimation.Forward)
else:
self.anim.setDirection(QAbstractAnimation.Backward)
self.anim.start()
def boundingRect(self):
return self.parent.boundingRect()
def paint(self, painter, style, widget=None):
painter.setPen(self.default_pen)
painter.drawText(self.parent.boundingRect(), Qt.AlignCenter, self.shown_text)
# Defining the length to be drawn as a pyqtProperty
@pyqtProperty(int)
def shown_length(self):
return self._shown_length
# Determine the length of the four lines to be drawn
@shown_length.setter
def shown_length(self, value):
self._shown_length = value
text_length = extras.bound_value(0, value-self.delay, len(self.actual_text))
text = self.actual_text[:text_length]
random.shuffle(self.randomchar_list)
text += ''.join(self.randomchar_list[:min(value, 3)])
self.shown_text = text[:min(self.shown_length, len(self.actual_text))]
self.update()