Основываясь на ответе , который дал Ромха Корев, я сделал вариант.
Этот пример позволяет устанавливать различные параметры и может отслеживать положение мыши, любые данные и ограничения, которые у вас есть.,Он не учитывает фиксированный размер / диапазон виджетов (по крайней мере, в моих тестах), но все равно адаптируется в соответствии с размером виджета.
class Bubble(QtWidgets.QWidget):
xChanged = QtCore.pyqtSignal(int)
yChanged = QtCore.pyqtSignal(int)
valuesChanged = QtCore.pyqtSignal(int, int)
moving = False
def __init__(self, minX=0, maxX=100, minY=0, maxY=100, cursorSize=10):
QtWidgets.QWidget.__init__(self)
self.margin = 10
self.setContentsMargins(0, 0, 0, 0)
self.minX = minX
self.minY = minY
self.maxX = maxX
self.maxY = maxY
self.extentX = abs(maxX - minX)
self.extentY = abs(maxY - minY)
self.setMinimumSize(self.extentX + self.margin * 2, self.extentY + self.margin * 2)
self.cursorSize = cursorSize
self.halfCursorSize = cursorSize * .5
self.cursorColor = QtGui.QColor(0, 174, 239)
self.movingCursorColor = QtGui.QColor(120, 124, 205)
self.cursorRect = QtCore.QRectF(-self.halfCursorSize, -self.halfCursorSize, cursorSize, cursorSize)
self.cursorX = self.minX + self.extentX * .5
self.cursorY = self.minY + self.extentY * .5
self.trackAnywhere = True
def visualPos(self):
# convert values to visual coordinates
return QtCore.QPointF(self.margin + self.halfCursorSize + ((self.cursorX - self.minX) / self.extentX) * (self.width() - self.margin * 2 - self.cursorSize),
self.margin + self.halfCursorSize + ((self.cursorY - self.minY) / self.extentY) * (self.height() - self.margin * 2 - self.cursorSize))
def realX(self, visualX):
visualExtent = self.width() - self.margin * 2 - self.cursorSize
realX = max(0, min(float(visualX - self.margin - self.halfCursorSize) / visualExtent, 1.)) * self.extentX
return self.minX + realX
def realY(self, visualY):
visualExtent = self.height() - self.margin * 2 - self.cursorSize
realY = max(0., min(float(visualY - self.margin - self.halfCursorSize) / visualExtent, 1.)) * self.extentY
return self.minY + realY
def realValues(self, pos):
return self.realX(pos.x()), self.realY(pos.y())
def mousePressEvent(self, event):
pos = event.pos()
visualPos = self.visualPos()
if self.trackAnywhere:
self.moving = True
if pos in self.cursorRect.translated(visualPos):
self.mouseDelta = visualPos - event.pos()
else:
self.mouseDelta = QtCore.QPoint()
elif pos in self.cursorRect.translated(visualPos):
self.moving = True
self.mouseDelta = visualPos - event.pos()
else:
self.moving = False
self.update()
def mouseMoveEvent(self, event):
if self.moving:
self.cursorX = self.realX(event.x() + self.mouseDelta.x())
self.cursorY = self.realY(event.y() + self.mouseDelta.y())
self.xChanged.emit(self.cursorX)
self.yChanged.emit(self.cursorY)
self.valuesChanged.emit(self.cursorX, self.cursorY)
self.update()
def mouseReleaseEvent(self, event):
self.moving = False
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# *** unnecessary, but it could be useful to show the visible range
qp.save()
qp.translate(.5, .5)
qp.drawRoundedRect(self.rect().adjusted(self.margin - 1, self.margin - 1, -self.margin, -self.margin), 2, 2)
qp.restore()
# comment the lines from the *** to this point, if not interested in the visual range
qp.setPen(QtCore.Qt.NoPen)
if self.moving:
qp.setBrush(self.movingCursorColor)
else:
qp.setBrush(self.cursorColor)
qp.drawEllipse(self.cursorRect.translated(self.visualPos()))
Одним из многих преимуществ этого решения является то, что он можетСледите за тем, где нажимается «ползунок», при этом сохраняя согласованность.
Обратите внимание, что realX
, realY
и realValues
являются числами с плавающей точкой, и они автоматически преобразуются в int в сигнале, поскольку их объявления используют этотип объекта.