def drawMagnifier(self): # First, calculate the magnifier position due to the mouse position watchAreaWidth = 16 watchAreaHeight = 16 watchAreaPixmap = QPixmap() cursor_pos = self.mousePoint watchArea = QRect( QPoint(cursor_pos.x() - watchAreaWidth / 2, cursor_pos.y() - watchAreaHeight / 2), QPoint(cursor_pos.x() + watchAreaWidth / 2, cursor_pos.y() + watchAreaHeight / 2)) if watchArea.left() < 0: watchArea.moveLeft(0) watchArea.moveRight(watchAreaWidth) if self.mousePoint.x() + watchAreaWidth / 2 >= self.screenPixel.width( ): watchArea.moveRight(self.screenPixel.width() - 1) watchArea.moveLeft(watchArea.right() - watchAreaWidth) if self.mousePoint.y() - watchAreaHeight / 2 < 0: watchArea.moveTop(0) watchArea.moveBottom(watchAreaHeight) if self.mousePoint.y( ) + watchAreaHeight / 2 >= self.screenPixel.height(): watchArea.moveBottom(self.screenPixel.height() - 1) watchArea.moveTop(watchArea.bottom() - watchAreaHeight) # tricks to solve the hidpi impact on QCursor.pos() watchArea.setTopLeft( QPoint(watchArea.topLeft().x() * self.scale, watchArea.topLeft().y() * self.scale)) watchArea.setBottomRight( QPoint(watchArea.bottomRight().x() * self.scale, watchArea.bottomRight().y() * self.scale)) watchAreaPixmap = self.screenPixel.copy(watchArea) # second, calculate the magnifier area magnifierAreaWidth = watchAreaWidth * 10 magnifierAreaHeight = watchAreaHeight * 10 fontAreaHeight = 40 cursorSize = 24 magnifierArea = QRectF( QPoint(QCursor.pos().x() + cursorSize, QCursor.pos().y() + cursorSize), QPoint(QCursor.pos().x() + cursorSize + magnifierAreaWidth, QCursor.pos().y() + cursorSize + magnifierAreaHeight)) if magnifierArea.right() >= self.screenPixel.width(): magnifierArea.moveLeft(QCursor.pos().x() - magnifierAreaWidth - cursorSize / 2) if magnifierArea.bottom() + fontAreaHeight >= self.screenPixel.height( ): magnifierArea.moveTop(QCursor.pos().y() - magnifierAreaHeight - cursorSize / 2 - fontAreaHeight) # third, draw the watch area to magnifier area watchAreaScaled = watchAreaPixmap.scaled( QSize(magnifierAreaWidth * self.scale, magnifierAreaHeight * self.scale)) magnifierPixmap = self.graphicsScene.addPixmap(watchAreaScaled) magnifierPixmap.setOffset(magnifierArea.topLeft()) # then draw lines and text self.graphicsScene.addRect(QRectF(magnifierArea), QPen(QColor(255, 255, 255), 2)) self.graphicsScene.addLine( QLineF(QPointF(magnifierArea.center().x(), magnifierArea.top()), QPointF(magnifierArea.center().x(), magnifierArea.bottom())), QPen(QColor(0, 255, 255), 2)) self.graphicsScene.addLine( QLineF(QPointF(magnifierArea.left(), magnifierArea.center().y()), QPointF(magnifierArea.right(), magnifierArea.center().y())), QPen(QColor(0, 255, 255), 2)) # get the rgb of mouse point pointRgb = QColor(self.screenPixel.toImage().pixel(self.mousePoint)) # draw information self.graphicsScene.addRect( QRectF( magnifierArea.bottomLeft(), magnifierArea.bottomRight() + QPoint(0, fontAreaHeight + 30)), QPen(Qt.black), QBrush(Qt.black)) rgbInfo = self.graphicsScene.addSimpleText( ' Rgb: ({0}, {1}, {2})'.format(pointRgb.red(), pointRgb.green(), pointRgb.blue())) rgbInfo.setPos(magnifierArea.bottomLeft() + QPoint(0, 5)) rgbInfo.setPen(QPen(QColor(255, 255, 255), 2)) rect = self.selectedArea.normalized() sizeInfo = self.graphicsScene.addSimpleText(' Size: {0} x {1}'.format( rect.width() * self.scale, rect.height() * self.scale)) sizeInfo.setPos(magnifierArea.bottomLeft() + QPoint(0, 15) + QPoint(0, fontAreaHeight / 2)) sizeInfo.setPen(QPen(QColor(255, 255, 255), 2))
class Callout(QGraphicsItem): def __init__(self, chart): super().__init__(chart) self.m_chart = chart self.m_text = "" self.m_textRect = QRectF() self.m_rect = QRectF() self.m_anchor = QPointF() self.m_font = QFont() def boundingRect(self): anchor = self.mapFromParent(self.m_chart.mapToPosition(self.m_anchor)) rect = QRectF() rect.setLeft(min(self.m_rect.left(), anchor.x())) rect.setRight(max(self.m_rect.right(), anchor.x())) rect.setTop(min(self.m_rect.top(), anchor.y())) rect.setBottom(max(self.m_rect.bottom(), anchor.y())) return rect def paint(self, painter, option, widget=None): path = QPainterPath() path.addRoundedRect(self.m_rect, 5, 5) anchor = self.mapFromParent(self.m_chart.mapToPosition(self.m_anchor)) if not self.m_rect.contains(anchor): point1 = QPointF() point2 = QPointF() # establish the position of the anchor point in relation to m_rect above = anchor.y() <= self.m_rect.top() aboveCenter = (anchor.y() > self.m_rect.top() and anchor.y() <= self.m_rect.center().y()) belowCenter = (anchor.y() > self.m_rect.center().y() and anchor.y() <= self.m_rect.bottom()) below = anchor.y() > self.m_rect.bottom() onLeft = anchor.x() <= self.m_rect.left() leftOfCenter = (anchor.x() > self.m_rect.left() and anchor.x() <= self.m_rect.center().x()) rightOfCenter = (anchor.x() > self.m_rect.center().x() and anchor.x() <= self.m_rect.right()) onRight = anchor.x() > self.m_rect.right() # get the nearest m_rect corner. x = (onRight + rightOfCenter) * self.m_rect.width() y = (below + belowCenter) * self.m_rect.height() cornerCase = ((above and onLeft) or (above and onRight) or (below and onLeft) or (below and onRight)) vertical = abs(anchor.x() - x) > abs(anchor.y() - y) x1 = (x + leftOfCenter * 10 - rightOfCenter * 20 + cornerCase * int(not vertical) * (onLeft * 10 - onRight * 20)) y1 = (y + aboveCenter * 10 - belowCenter * 20 + cornerCase * int(vertical) * (above * 10 - below * 20)) point1.setX(x1) point1.setY(y1) x2 = (x + leftOfCenter * 20 - rightOfCenter * 10 + cornerCase * int(not vertical) * (onLeft * 20 - onRight * 10)) y2 = (y + aboveCenter * 20 - belowCenter * 10 + cornerCase * int(vertical) * (above * 20 - below * 10)) point2.setX(x2) point2.setY(y2) path.moveTo(point1) path.lineTo(anchor) path.lineTo(point2) path = path.simplified() painter.setBrush(QColor(255, 255, 255)) painter.drawPath(path) painter.drawText(self.m_textRect, self.m_text) def mousePressEvent(self, event): event.setAccepted(True) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: self.setPos( self.mapToParent(event.pos() - event.buttonDownPos(Qt.LeftButton))) event.setAccepted(True) else: event.setAccepted(False) def setText(self, text): self.m_text = text metrics = QFontMetrics(self.m_font) self.m_textRect = QRectF( metrics.boundingRect(QRect(0, 0, 150, 150), Qt.AlignLeft, self.m_text)) self.m_textRect.translate(5, 5) self.prepareGeometryChange() self.m_rect = self.m_textRect.adjusted(-5, -5, 5, 5) def setAnchor(self, point): self.m_anchor = point def updateGeometry(self): self.prepareGeometryChange() self.setPos( self.m_chart.mapToPosition(self.m_anchor) + QPoint(10, -50))
class JoyPad(QWidget, object): xChanged = Signal(float) yChanged = Signal(float) def __init__(self, parent=None): super(JoyPad, self).__init__(parent=parent) self._x = 0 self._y = 0 self._bounds = QRectF() self._knop_bounds = QRectF() self._last_pos = QPoint() self._knop_pressed = False self._return_animation = QParallelAnimationGroup(self) self._x_anim = QPropertyAnimation(self, 'x') self._y_anim = QPropertyAnimation(self, 'y') self._alignment = Qt.AlignTop | Qt.AlignLeft self._x_anim.setEndValue(0.0) self._x_anim.setDuration(400) self._x_anim.setEasingCurve(QEasingCurve.OutSine) self._y_anim.setEndValue(0.0) self._y_anim.setDuration(400) self._y_anim.setEasingCurve(QEasingCurve.OutSine) self._return_animation.addAnimation(self._x_anim) self._return_animation.addAnimation(self._y_anim) # region Static Functions @staticmethod def constraint(value, min_value, max_value): return min_value if value < min_value else max_value if value > max_value else value # endregion # region Properties def get_x(self): return self._x def set_x(self, x): self._x = self.constraint(x, -1.0, 1.0) radius = (self._bounds.width() - self._knop_bounds.width()) * 0.5 self._knop_bounds.moveCenter( QPointF(self._bounds.center().x() + self._x * radius, self._knop_bounds.center().y())) self.update() self.xChanged.emit(self._x) def get_y(self): return self._y def set_y(self, y): self._y = self.constraint(y, -1.0, 1.0) radius = (self._bounds.width() - self._knop_bounds.width()) * 0.5 self._knop_bounds.moveCenter( QPointF(self._knop_bounds.center().x(), self._bounds.center().y() - self._y * radius)) self.update() self.yChanged.emit(self._x) def get_alignment(self): return self._alignment def set_alignment(self, alignment): self._alignment = alignment x = property(get_x, set_x) y = property(get_y, set_y) alignment = property(get_alignment, set_alignment) # endregion # region Override Functions def resizeEvent(self, event): a = min(self.width(), self.height()) top_left = QPointF() if self._alignment & Qt.AlignTop: top_left.setY(0) elif self._alignment & Qt.AlignVCenter: top_left.setY((self.height() - a) * 0.5) elif self._alignment & Qt.AlignBottom: top_left.setY(self.height() - a) if self._alignment & Qt.AlignLeft: top_left.setX(0) elif self._alignment & Qt.AlignHCenter: top_left.setX((self.width() - a) * 0.5) elif self._alignment & Qt.AlignRight: top_left.setX(self.width() - a) self._bounds = QRectF(top_left, QSize(a, a)) self._knop_bounds.setWidth(a * 0.3) self._knop_bounds.setHeight(a * 0.3) radius = (self._bounds.width() - self._knop_bounds.height()) * 0.5 self._knop_bounds.moveCenter( QPointF(self._bounds.center().x() + self._x * radius, self._bounds.center().y() - self._y * radius)) def mousePressEvent(self, event): if self._knop_bounds.contains(event.pos()): # self._return_animation.stop() self._last_pos = event.pos() self._knop_pressed = True def mouseReleaseEvent(self, event): self._knop_pressed = False self.x = 0.0 self.y = 0.0 # self._return_animation.start() def mouseMoveEvent(self, event): if not self._knop_pressed: return delta_pos = QPointF(event.pos() - self._last_pos) delta_pos += 0.5 * (QPointF(event.pos()) - self._knop_bounds.center()) from_center_to_knop = self._knop_bounds.center( ) + delta_pos - self._bounds.center() radius = (self._bounds.width() - self._knop_bounds.width()) * 0.5 from_center_to_knop.setX( self.constraint(from_center_to_knop.x(), -radius, radius)) from_center_to_knop.setY( self.constraint(from_center_to_knop.y(), -radius, radius)) self._knop_bounds.moveCenter(from_center_to_knop + self._bounds.center()) self._last_pos = event.pos() self.update() if radius == 0: return x = (self._knop_bounds.center().x() - self._bounds.center().x()) / radius y = (-self._knop_bounds.center().y() + self._bounds.center().y()) / radius if self._x != x: self._x = x self.xChanged.emit(self._x) if self._y != y: self._y = y self.yChanged.emit(self._y) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.HighQualityAntialiasing) gradient = QRadialGradient(self._bounds.center(), self._bounds.width() * 0.5, self._bounds.center()) gradient.setFocalRadius(self._bounds.width() * 0.3) gradient.setCenterRadius(self._bounds.width() * 0.7) gradient.setColorAt(0, Qt.white) gradient.setColorAt(1, Qt.lightGray) painter.setPen(QPen(QBrush(Qt.gray), self._bounds.width() * 0.005)) painter.setBrush(QBrush(gradient)) painter.drawEllipse(self._bounds) painter.setPen(QPen(QBrush(Qt.gray), self._bounds.width() * 0.005)) painter.drawLine( QPointF(self._bounds.left(), self._bounds.center().y()), QPointF(self._bounds.center().x() - self._bounds.width() * 0.35, self._bounds.center().y())) painter.drawLine( QPointF(self._bounds.center().x() + self._bounds.width() * 0.35, self._bounds.center().y()), QPointF(self._bounds.right(), self._bounds.center().y())) painter.drawLine( QPointF(self._bounds.center().x(), self._bounds.top()), QPointF(self._bounds.center().x(), self._bounds.center().y() - self._bounds.width() * 0.35)) painter.drawLine( QPointF(self._bounds.center().x(), self._bounds.center().y() + self._bounds.width() * 0.35), QPointF(self._bounds.center().x(), self._bounds.bottom())) if not self.isEnabled(): return gradient = QRadialGradient(self._knop_bounds.center(), self._knop_bounds.width() * 0.5, self._knop_bounds.center()) gradient.setFocalRadius(self._knop_bounds.width() * 0.2) gradient.setCenterRadius(self._knop_bounds.width() * 0.5) gradient.setColorAt(0, Qt.gray) gradient.setColorAt(1, Qt.darkGray) painter.setPen(QPen(QBrush(Qt.darkGray), self._bounds.width() * 0.005)) painter.setBrush(QBrush(gradient)) painter.drawEllipse(self._knop_bounds) # endregion # region Public Functions def add_x_animation(self): # Abort if the animation is already added if self._x_anim.parent() == self._return_animation: return self._return_animation.addAnimation(self._x_anim) def remove_x_animation(self): # Abort if the animation is already removed if self._x_anim.parent() != self._return_animation: return self._return_animation.removeAnimation(self._x_anim) # Take ownership of the animation (parent is 0 after removeAnimation()) self._x_anim.setParent(self) def add_y_animation(self): # Abort if the animation is already added if self._y_anim.parent() == self._return_animation: return self._return_animation.addAnimation(self._y_anim) def remove_y_animation(self): # Abort if the animation is already removed if self._y_anim.parent() != self._return_animation: return self._return_animation.removeAnimation(self._y_anim) # Take ownership of the animation (parent is 0 after removeAnimation()) self._y_anim.setParent(self)