在与鼠标速度成比例的 QGraphicsItem 中心调整大小?

Resize about center on a QGraphicsItem proportional to mouse speed?

很抱歉重复询问有关 pyqt 的问题,但有限的资源迫使我这样做。

我一直在尝试为我的圆形实现一个调整大小的功能,这是 QGraphicsItem 的扩展,它有一个椭圆和一个居中的文本。我可以根据需要调整我的形状,但形状需要一段时间才能赶上鼠标,即在切换方向时,圆圈继续增加但需要一段时间才能切换方向,此外,圆圈的大小会随着顶部的锚点而调整边界矩形的左角。

请参阅 了解一些代码背景

def updateHandlesPos(self):
        s = self.handleSize
        b = self.boundingRect()
        self.handles[self.handleTopLeft] = QRectF(b.left(), b.top(), s, s)
        self.handles[self.handleTopMiddle] = QRectF(b.center().x() - s / 2, b.top(), s, s)
        self.handles[self.handleTopRight] = QRectF(b.right() - s, b.top(), s, s)
        self.handles[self.handleMiddleLeft] = QRectF(b.left(), b.center().y() - s / 2, s, s)
        self.handles[self.handleMiddleRight] = QRectF(b.right() - s, b.center().y() - s / 2, s, s)
        self.handles[self.handleBottomLeft] = QRectF(b.left(), b.bottom() - s, s, s)
        self.handles[self.handleBottomMiddle] = QRectF(b.center().x() - s / 2, b.bottom() - s, s, s)
        self.handles[self.handleBottomRight] = QRectF(b.right() - s, b.bottom() - s, s, s)

    def interactiveResize(self, mousePos):
        self.prepareGeometryChange()
        if self.handleSelected in [self.handleTopLeft,
                                   self.handleTopRight,
                                   self.handleBottomLeft,
                                   self.handleBottomRight,
                                   self.handleTopMiddle,
                                   self.handleBottomMiddle,
                                   self.handleMiddleLeft,
                                   self.handleMiddleRight]:
            self.radius += (mousePos.y() + mousePos.x() + self.mousePressPos.x() - self.mousePressPos.y())/64
            self.setPos(self.x(),self.y())
        self.update()   
        self.updateHandlesPos()

由于OP没有提供MRE,所以我从头开始创建了一个例子。其逻辑是跟踪item的变化,并据此计算出新的geometry,建立其他item的新位置。

from PyQt5 import QtWidgets, QtGui, QtCore


class GripItem(QtWidgets.QGraphicsPathItem):
    circle = QtGui.QPainterPath()
    circle.addEllipse(QtCore.QRectF(-5, -5, 10, 10))
    square = QtGui.QPainterPath()
    square.addRect(QtCore.QRectF(-10, -10, 20, 20))

    def __init__(self, annotation_item, index):
        super(GripItem, self).__init__()
        self.m_annotation_item = annotation_item
        self.m_index = index

        self.setPath(GripItem.circle)
        self.setBrush(QtGui.QColor("green"))
        self.setPen(QtGui.QPen(QtGui.QColor("green"), 2))
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setAcceptHoverEvents(True)
        self.setZValue(11)
        self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))

    def hoverEnterEvent(self, event):
        self.setPath(GripItem.square)
        self.setBrush(QtGui.QColor("red"))
        super(GripItem, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        self.setPath(GripItem.circle)
        self.setBrush(QtGui.QColor("green"))
        super(GripItem, self).hoverLeaveEvent(event)

    def mouseReleaseEvent(self, event):
        self.setSelected(False)
        super(GripItem, self).mouseReleaseEvent(event)

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
            self.m_annotation_item.movePoint(self.m_index, value)
        return super(GripItem, self).itemChange(change, value)


class DirectionGripItem(GripItem):
    def __init__(self, annotation_item, direction=QtCore.Qt.Horizontal, parent=None):
        super(DirectionGripItem, self).__init__(annotation_item, parent)
        self._direction = direction

    @property
    def direction(self):
        return self._direction

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
            p = QtCore.QPointF(self.pos())
            if self.direction == QtCore.Qt.Horizontal:
                p.setX(value.x())
            elif self.direction == QtCore.Qt.Vertical:
                p.setY(value.y())
            self.m_annotation_item.movePoint(self.m_index, p)
            return p
        return super(DirectionGripItem, self).itemChange(change, value)


class CircleAnnotation(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, radius=1, parent=None):
        super(CircleAnnotation, self).__init__(parent)
        self.setZValue(11)
        self.m_items = []

        self.setPen(QtGui.QPen(QtGui.QColor("green"), 4))

        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)

        self.setAcceptHoverEvents(True)

        self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))

        self._radius = radius
        self.update_rect()

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, r):
        if r <= 0:
            raise ValueError("radius must be positive")
        self._radius = r
        self.update_rect()
        self.add_grip_items()
        self.update_items_positions()

    def update_rect(self):
        rect = QtCore.QRectF(0, 0, 2 * self.radius, 2 * self.radius)
        rect.moveCenter(self.rect().center())
        self.setRect(rect)

    def add_grip_items(self):
        if self.scene() and not self.m_items:
            for i, (direction) in enumerate(
                (
                    QtCore.Qt.Vertical,
                    QtCore.Qt.Horizontal,
                    QtCore.Qt.Vertical,
                    QtCore.Qt.Horizontal,
                )
            ):
                item = DirectionGripItem(self, direction, i)
                self.scene().addItem(item)
                self.m_items.append(item)

    def movePoint(self, i, p):
        if 0 <= i < min(4, len(self.m_items)):
            item_selected = self.m_items[i]
            lp = self.mapFromScene(p)
            self._radius = (lp - self.rect().center()).manhattanLength()
            k = self.indexOf(lp)
            if k is not None:
                self.m_items = [item for item in self.m_items if not item.isSelected()]
                self.m_items.insert(k, item_selected)
                self.update_items_positions([k])
                self.update_rect()

    def update_items_positions(self, index_no_updates=None):
        index_no_updates = index_no_updates or []
        for i, (item, direction) in enumerate(
            zip(
                self.m_items,
                (
                    QtCore.Qt.Vertical,
                    QtCore.Qt.Horizontal,
                    QtCore.Qt.Vertical,
                    QtCore.Qt.Horizontal,
                ),
            ),
        ):
            item.m_index = i
            if i not in index_no_updates:
                pos = self.mapToScene(self.point(i))
                item = self.m_items[i]
                item._direction = direction
                item.setEnabled(False)
                item.setPos(pos)
                item.setEnabled(True)

    def indexOf(self, p):
        for i in range(4):
            if p == self.point(i):
                return i

    def point(self, index):
        if 0 <= index < 4:
            return [
                QtCore.QPointF(0, -self.radius),
                QtCore.QPointF(self.radius, 0),
                QtCore.QPointF(0, self.radius),
                QtCore.QPointF(-self.radius, 0),
            ][index]

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
            self.update_items_positions()
            return
        if change == QtWidgets.QGraphicsItem.ItemSceneHasChanged:
            self.add_grip_items()
            self.update_items_positions()
            return
        return super(CircleAnnotation, self).itemChange(change, value)

    def hoverEnterEvent(self, event):
        self.setBrush(QtGui.QColor(255, 0, 0, 100))
        super(CircleAnnotation, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        self.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
        super(CircleAnnotation, self).hoverLeaveEvent(event)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    scene = QtWidgets.QGraphicsScene()
    view = QtWidgets.QGraphicsView(scene)
    view.setRenderHints(QtGui.QPainter.Antialiasing)
    item = CircleAnnotation()
    item.radius = 100
    scene.addItem(item)
    view.showMaximized()

    sys.exit(app.exec_())