Fixed dragging corners
This commit is contained in:
@@ -34,50 +34,37 @@ def _snap(value: float) -> float:
|
||||
|
||||
|
||||
class ResizeHandle(QGraphicsEllipseItem):
|
||||
def __init__(self, owner: "ShapeItem") -> None:
|
||||
CURSORS = {
|
||||
"n": Qt.CursorShape.SizeVerCursor,
|
||||
"s": Qt.CursorShape.SizeVerCursor,
|
||||
"e": Qt.CursorShape.SizeHorCursor,
|
||||
"w": Qt.CursorShape.SizeHorCursor,
|
||||
"nw": Qt.CursorShape.SizeFDiagCursor,
|
||||
"se": Qt.CursorShape.SizeFDiagCursor,
|
||||
"ne": Qt.CursorShape.SizeBDiagCursor,
|
||||
"sw": Qt.CursorShape.SizeBDiagCursor,
|
||||
}
|
||||
|
||||
def __init__(self, owner: "ShapeItem", role: str = "se") -> None:
|
||||
super().__init__(-4, -4, 8, 8, owner)
|
||||
self.owner = owner
|
||||
self.role = role
|
||||
self.setBrush(QColor("#ffffff"))
|
||||
self.setPen(QPen(QColor("#2563eb"), 1.5))
|
||||
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
|
||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
|
||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
||||
self.setCursor(self.CURSORS[role])
|
||||
self.setZValue(20)
|
||||
|
||||
def itemChange(self, change, value): # noqa: N802
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||
maximum = (
|
||||
self.owner.scene().sceneRect().bottomRight() - self.owner.pos()
|
||||
if self.owner.scene()
|
||||
else QPointF(128, 128)
|
||||
)
|
||||
if self.owner.element.get("type") == "line":
|
||||
minimum = (
|
||||
self.owner.scene().sceneRect().topLeft() - self.owner.pos()
|
||||
if self.owner.scene()
|
||||
else QPointF(-128, -128)
|
||||
)
|
||||
value = QPointF(
|
||||
min(maximum.x(), max(minimum.x(), _snap(value.x()))),
|
||||
min(maximum.y(), max(minimum.y(), _snap(value.y()))),
|
||||
)
|
||||
self.owner.prepareGeometryChange()
|
||||
self.owner.element["width"] = value.x()
|
||||
self.owner.element["height"] = value.y()
|
||||
self.owner.update()
|
||||
return super().itemChange(change, value)
|
||||
value = QPointF(
|
||||
min(maximum.x(), max(_icon_grid_size(), _snap(value.x()))),
|
||||
min(maximum.y(), max(_icon_grid_size(), _snap(value.y()))),
|
||||
)
|
||||
if self.owner.element.get("type") == "circle":
|
||||
side = min(maximum.x(), maximum.y(), max(value.x(), value.y()))
|
||||
value = QPointF(side, side)
|
||||
self.owner.prepareGeometryChange()
|
||||
self.owner.element["width"] = value.x()
|
||||
self.owner.element["height"] = value.y()
|
||||
self.owner.update()
|
||||
return super().itemChange(change, value)
|
||||
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
self.owner.begin_resize()
|
||||
event.accept()
|
||||
|
||||
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
self.owner.resize_from_handle(self.role, event.scenePos())
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
self.owner.finish_resize()
|
||||
event.accept()
|
||||
|
||||
|
||||
class ShapeOptionsDialog(QDialog):
|
||||
@@ -178,9 +165,16 @@ class ShapeItem(QGraphicsObject):
|
||||
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||
)
|
||||
self.resize_handle = ResizeHandle(self)
|
||||
self.resize_handle.setPos(float(element.get("width", 20)), float(element.get("height", 20)))
|
||||
self.resize_handle.hide()
|
||||
roles = (
|
||||
("n", "ne", "e", "se", "s", "sw", "w", "nw")
|
||||
if element.get("type") == "rectangle"
|
||||
else ("se",)
|
||||
)
|
||||
self.resize_handles = [ResizeHandle(self, role) for role in roles]
|
||||
self.resize_start: dict | None = None
|
||||
self._position_resize_handles()
|
||||
for handle in self.resize_handles:
|
||||
handle.hide()
|
||||
|
||||
def boundingRect(self) -> QRectF: # noqa: N802
|
||||
margin = max(3.0, float(self.element.get("lineWidth", 1.5)))
|
||||
@@ -233,7 +227,8 @@ class ShapeItem(QGraphicsObject):
|
||||
elif change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
||||
self.element["x"], self.element["y"] = value.x(), value.y()
|
||||
elif change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
||||
self.resize_handle.setVisible(bool(value))
|
||||
for handle in self.resize_handles:
|
||||
handle.setVisible(bool(value))
|
||||
return super().itemChange(change, value)
|
||||
|
||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||
@@ -251,6 +246,74 @@ class ShapeItem(QGraphicsObject):
|
||||
max(bounds.top() - minimum_y, min(bounds.bottom() - maximum_y, _snap(position.y()))),
|
||||
)
|
||||
|
||||
def _position_resize_handles(self) -> None:
|
||||
width = float(self.element.get("width", 20))
|
||||
height = float(self.element.get("height", 20))
|
||||
positions = {
|
||||
"n": QPointF(width / 2, 0),
|
||||
"ne": QPointF(width, 0),
|
||||
"e": QPointF(width, height / 2),
|
||||
"se": QPointF(width, height),
|
||||
"s": QPointF(width / 2, height),
|
||||
"sw": QPointF(0, height),
|
||||
"w": QPointF(0, height / 2),
|
||||
"nw": QPointF(0, 0),
|
||||
}
|
||||
for handle in self.resize_handles:
|
||||
handle.setPos(positions[handle.role])
|
||||
|
||||
def begin_resize(self) -> None:
|
||||
self.resize_start = {
|
||||
"left": self.pos().x(),
|
||||
"top": self.pos().y(),
|
||||
"right": self.pos().x() + float(self.element.get("width", 20)),
|
||||
"bottom": self.pos().y() + float(self.element.get("height", 20)),
|
||||
}
|
||||
|
||||
def resize_from_handle(self, role: str, scene_position: QPointF) -> None:
|
||||
if self.resize_start is None:
|
||||
self.begin_resize()
|
||||
if self.element.get("type") == "line":
|
||||
bounds = self.scene().sceneRect() if self.scene() else QRectF(0, 0, 128, 128)
|
||||
endpoint = QPointF(
|
||||
min(bounds.right(), max(bounds.left(), _snap(scene_position.x()))),
|
||||
min(bounds.bottom(), max(bounds.top(), _snap(scene_position.y()))),
|
||||
)
|
||||
self.prepareGeometryChange()
|
||||
self.element["width"] = endpoint.x() - self.pos().x()
|
||||
self.element["height"] = endpoint.y() - self.pos().y()
|
||||
self._position_resize_handles()
|
||||
self.update()
|
||||
return
|
||||
edges = dict(self.resize_start)
|
||||
point = QPointF(_snap(scene_position.x()), _snap(scene_position.y()))
|
||||
if "w" in role:
|
||||
edges["left"] = min(point.x(), edges["right"] - _icon_grid_size())
|
||||
if "e" in role:
|
||||
edges["right"] = max(point.x(), edges["left"] + _icon_grid_size())
|
||||
if "n" in role:
|
||||
edges["top"] = min(point.y(), edges["bottom"] - _icon_grid_size())
|
||||
if "s" in role:
|
||||
edges["bottom"] = max(point.y(), edges["top"] + _icon_grid_size())
|
||||
bounds = self.scene().sceneRect() if self.scene() else QRectF(0, 0, 128, 128)
|
||||
edges["left"] = max(bounds.left(), edges["left"])
|
||||
edges["top"] = max(bounds.top(), edges["top"])
|
||||
edges["right"] = min(bounds.right(), edges["right"])
|
||||
edges["bottom"] = min(bounds.bottom(), edges["bottom"])
|
||||
if self.element.get("type") == "circle":
|
||||
side = min(edges["right"] - edges["left"], edges["bottom"] - edges["top"])
|
||||
edges["right"] = edges["left"] + side
|
||||
edges["bottom"] = edges["top"] + side
|
||||
self.prepareGeometryChange()
|
||||
self.element["width"] = edges["right"] - edges["left"]
|
||||
self.element["height"] = edges["bottom"] - edges["top"]
|
||||
self.setPos(edges["left"], edges["top"])
|
||||
self._position_resize_handles()
|
||||
self.update()
|
||||
|
||||
def finish_resize(self) -> None:
|
||||
self.resize_start = None
|
||||
|
||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||
menu = QMenu()
|
||||
options = menu.addAction("Shape Options…")
|
||||
@@ -270,10 +333,7 @@ class ShapeItem(QGraphicsObject):
|
||||
if self.element.get("type") == "line":
|
||||
self.element["width"] *= width_sign
|
||||
self.element["height"] *= height_sign
|
||||
self.resize_handle.setPos(
|
||||
float(self.element.get("width", 20)),
|
||||
float(self.element.get("height", 20)),
|
||||
)
|
||||
self._position_resize_handles()
|
||||
self.update()
|
||||
elif chosen is delete and self.scene() is not None:
|
||||
self.scene().removeItem(self)
|
||||
|
||||
Reference in New Issue
Block a user