Created
September 1, 2023 11:44
-
-
Save qycyfjy/dca40203734691da9ffc3f5c2a372baa to your computer and use it in GitHub Desktop.
PySide6 NumberPickWidget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Optional | |
from PySide6.QtCore import ( | |
Qt, | |
Signal, | |
QPropertyAnimation, | |
QEasingCurve, | |
QRectF, | |
Property, | |
) | |
from PySide6.QtGui import QMouseEvent, QWheelEvent, QPaintEvent, QPainter, QColor, QFont | |
from PySide6.QtWidgets import QWidget | |
class NumberPickerWidget(QWidget): | |
currentValueChanged = Signal(int) | |
deviationChanged = Signal(int) | |
def __init__(self, parent: Optional[QWidget] = None) -> None: | |
super().__init__(parent) | |
self._minimum = 0 | |
self._maximum = 60 | |
self._current_value = 0 | |
self._dragging = False | |
self._deviation = 0 | |
self._mouse_pressed_pos = 0 | |
self._interval = 1 | |
self._division = 3 | |
self._number_size = 14 | |
self._number_color = QColor(0, 0, 0) | |
self.setMinimumSize(50, 150) | |
self._centering_animation = QPropertyAnimation(self, b"deviation") | |
self._centering_animation.setDuration(300) | |
self._centering_animation.setEasingCurve(QEasingCurve.Type.OutQuad) | |
def set_range(self, minimum: int, maximum: int): | |
self._minimum = minimum | |
self._maximum = maximum | |
if self._current_value < minimum: | |
self._current_value = minimum | |
if self._current_value > maximum: | |
self._current_value = maximum | |
self.repaint() | |
def read_value(self) -> int: | |
return self._current_value | |
def get_deviation(self): | |
return self._deviation | |
def set_deviation(self, new_value: int): | |
self._deviation = int(new_value) | |
self.repaint() | |
deviation = Property(int, get_deviation, set_deviation) | |
def set_interval(self, interval): | |
self._interval = int(interval) | |
def set_division(self, division): | |
self._division = int(division) | |
def set_number_size(self, size): | |
self._number_size = int(size) | |
def set_number_color(self, color: QColor): | |
self._number_color = color | |
def set_value(self, value): | |
if value < self._minimum or value > self._maximum: | |
return | |
self._current_value = int(value) | |
def mousePressEvent(self, event: QMouseEvent) -> None: | |
self._centering_animation.stop() | |
self._dragging = True | |
self._mouse_pressed_pos = event.position().y() | |
return super().mousePressEvent(event) | |
def mouseMoveEvent(self, event: QMouseEvent) -> None: | |
if self._dragging: | |
self._deviation = int(event.position().y() - self._mouse_pressed_pos) | |
p = int((self.height() - 1) / self._division) | |
if self._deviation > p: | |
self._deviation = p | |
elif self._deviation < -p: | |
self._deviation = -p | |
self.deviationChanged.emit(self._deviation / p) | |
self.repaint() | |
def mouseReleaseEvent(self, event: QMouseEvent) -> None: | |
if self._dragging: | |
self._dragging = False | |
self.center_number() | |
def wheelEvent(self, event: QWheelEvent) -> None: | |
if event.angleDelta().y() > 0: | |
self._deviation = int((self.height() - 1) / self._division) | |
else: | |
self._deviation = int(-(self.height() - 1) / self._division) | |
self.center_number() | |
self.repaint() | |
def paintEvent(self, event: QPaintEvent) -> None: | |
painter = QPainter(self) | |
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) | |
h = self.height() - 1 | |
p = int(h / self._division) | |
if self._deviation >= p and self._current_value > self._minimum: | |
self._mouse_pressed_pos += p | |
self._deviation -= p | |
self._current_value -= self._interval | |
if self._current_value < 0: | |
self._current_value = self._current_value + self._maximum | |
if self._deviation <= -p and self._current_value < self._maximum: | |
self._mouse_pressed_pos -= p | |
self._deviation += p | |
self._current_value += self._interval | |
if abs(int(self._current_value)) >= int(self._maximum): | |
self._current_value = self._minimum | |
self.paint_number( | |
painter, | |
abs((self._current_value + self._maximum) % self._maximum), | |
self._deviation, | |
) | |
self.paint_number( | |
painter, | |
abs((self._current_value - self._interval + self._maximum) % self._maximum), | |
self._deviation - p, | |
) | |
self.paint_number( | |
painter, | |
abs((self._current_value + self._interval + self._maximum) % self._maximum), | |
self._deviation + p, | |
) | |
for i in range(2, int(self._division / 2)): | |
if abs(self._current_value - self._interval * i) >= self._minimum: | |
self.paint_number( | |
painter, | |
abs(self._current_value - self._interval * i + self._maximum) | |
% self._maximum, | |
self._deviation - p * i, | |
) | |
if abs(self._current_value + self._interval * i) <= self._maximum: | |
self.paint_number( | |
painter, | |
abs(self._current_value + self._interval * i + self._maximum) | |
% self._maximum, | |
self._deviation + p * i, | |
) | |
def paint_number(self, painter: QPainter, num: int, deviation: int): | |
w = self.width() - 1 | |
h = self.height() - 1 | |
size = (h - abs(deviation)) * self._number_size / 80 | |
transparency = 255 - 255 * abs(deviation) / h | |
self._number_color.setAlpha(transparency) | |
height = h / self._division | |
y = h / 2 + deviation - height / 2 | |
font = QFont() | |
font.setPixelSize(size) | |
painter.setFont(font) | |
painter.setPen(self._number_color) | |
if y >= 0 and y + height < h: | |
if num < 10: | |
painter.drawText( | |
QRectF(0, y, w, height), Qt.AlignmentFlag.AlignCenter, f"0{num}" | |
) | |
else: | |
painter.drawText( | |
QRectF(0, y, w, height), Qt.AlignmentFlag.AlignCenter, f"{num}" | |
) | |
def center_number(self): | |
h = self.height() | |
if self._deviation > h / 10: | |
self._centering_animation.setStartValue((h - 1) / 8 - self._deviation) | |
self._centering_animation.setEndValue(0) | |
self._current_value -= self._interval | |
elif self._deviation > -h / 10: | |
self._centering_animation.setStartValue(self._deviation) | |
self._centering_animation.setEndValue(0) | |
elif self._deviation < -h / 10: | |
self._centering_animation.setStartValue(-(h - 1) / 8 - self._deviation) | |
self._centering_animation.setEndValue(0) | |
self._current_value += self._interval | |
self.currentValueChanged.emit(self._current_value) | |
self._centering_animation.start() | |
if __name__ == "__main__": | |
from PySide6.QtWidgets import QApplication | |
app = QApplication() | |
w = NumberPickerWidget() | |
w.show() | |
app.exec() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment