Реализация одномерной операции перетаскивания в Qt - PullRequest
1 голос
/ 11 июня 2019

Я хочу разрешить перетаскивание QGraphicsItem только в определенных направлениях, таких как +/- 45 градусов, по горизонтали или вертикали, и иметь возможность "прыгать" в новом направлении, когда курсор перемещается достаточно далеко от текущего ближайшего направления. Это будет повторять поведение, например, Inkscape при рисовании прямой линии и удерживании Ctrl (см., Например, это видео ), но я не уверен, как это реализовать.

Я реализовал обработчик перетаскивания, который захватывает новую позицию элемента при его перемещении:

class Circle(QGraphicsEllipseItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Flags to allow dragging and tracking of dragging.
        self.setFlag(self.ItemSendsGeometryChanges)
        self.setFlag(self.ItemIsMovable)
        self.setFlag(self.ItemIsSelectable)

    def itemChange(self, change, value):
        if change == self.ItemPositionChange and self.isSelected():
            # do something...

        # Return the new position to parent to have this item move there.
        return super().itemChange(change, value)

Поскольку позиция, возвращаемая родителю этим методом, используется для обновления позиции элемента в сцене, я ожидаю, что могу изменить это значение QPointF, чтобы ограничить его одной осью, но я не уверен, как это сделать. таким образом, что позволяет линии «прыгать» в другом направлении, когда курсор перемещается достаточно далеко. Существуют ли «стандартные алгоритмы» для такого поведения? Или, может быть, какой-то встроенный код Qt, который может сделать это для меня?

1 Ответ

2 голосов
/ 11 июня 2019

Задача сводится к вычислению проекции точки (положения элемента) на линию.Выполняя небольшую математику, как объяснено в этом посте .

Пусть p1 и p2 - две разные точки на линии, а точка p - это алгоритм:

e1 = p2 - p1
e2 = p - p1
dp = e1 • e2 # dot product
l  = e1 • e1 # dot product
pp = p1 + dp * e1 / l

Реализация вышеупомянутого решения:

import math
import random
from PyQt5 import QtCore, QtGui, QtWidgets


class Circle(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, *args, **kwargs):
        self._line = QtCore.QLineF()
        super().__init__(*args, **kwargs)
        # Flags to allow dragging and tracking of dragging.
        self.setFlags(
            self.flags()
            | QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
            | QtWidgets.QGraphicsItem.ItemIsMovable
            | QtWidgets.QGraphicsItem.ItemIsSelectable
        )

    @property
    def line(self):
        return self._line

    @line.setter
    def line(self, line):
        self._line = line

    def itemChange(self, change, value):
        if (
            change == QtWidgets.QGraphicsItem.ItemPositionChange
            and self.isSelected()
            and not self.line.isNull()
        ):
            # http://www.sunshine2k.de/coding/java/PointOnLine/PointOnLine.html
            p1 = self.line.p1()
            p2 = self.line.p2()
            e1 = p2 - p1
            e2 = value - p1
            dp = QtCore.QPointF.dotProduct(e1, e2)
            l = QtCore.QPointF.dotProduct(e1, e1)
            p = p1 + dp * e1 / l
            return p
        return super().itemChange(change, value)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    scene = QtWidgets.QGraphicsScene(QtCore.QRectF(-200, -200, 400, 400))
    view = QtWidgets.QGraphicsView(scene)

    points = (
        QtCore.QPointF(*random.sample(range(-150, 150), 2)) for _ in range(4)
    )
    angles = (math.pi / 4, math.pi / 3, math.pi / 5, math.pi / 2)

    for point, angle in zip(points, angles):
        item = Circle(QtCore.QRectF(-10, -10, 20, 20))
        item.setBrush(QtGui.QColor("salmon"))
        scene.addItem(item)
        item.setPos(point)
        end = 100 * QtCore.QPointF(math.cos(angle), math.sin(angle))
        line = QtCore.QLineF(QtCore.QPointF(), end)
        item.line = line.translated(item.pos())
        line_item = scene.addLine(item.line)
        line_item.setPen(QtGui.QPen(QtGui.QColor("green"), 4))

    view.resize(640, 480)
    view.show()

    sys.exit(app.exec_())
...