Skip to content

Instantly share code, notes, and snippets.

@qycyfjy
Created September 1, 2023 11:44
Show Gist options
  • Save qycyfjy/dca40203734691da9ffc3f5c2a372baa to your computer and use it in GitHub Desktop.
Save qycyfjy/dca40203734691da9ffc3f5c2a372baa to your computer and use it in GitHub Desktop.
PySide6 NumberPickWidget
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