Fixed dragging corners
This commit is contained in:
@@ -115,7 +115,8 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
|
|||||||
clicks, use right-angle segments, and snap to the graph snapping grid.
|
clicks, use right-angle segments, and snap to the graph snapping grid.
|
||||||
Right-click cancels an unfinished connection.
|
Right-click cancels an unfinished connection.
|
||||||
- Select a routed connection to reveal its draggable nodes. Right-click a line
|
- Select a routed connection to reveal its draggable nodes. Right-click a line
|
||||||
to add a node, or right-click a node to delete it.
|
to add a node, or right-click a node to delete it. Angled routes persist every
|
||||||
|
orthogonal corner as a draggable, grid-snapped node.
|
||||||
- Use **Box**, **Line**, and **Text** to add persistent graph annotations. Lines
|
- Use **Box**, **Line**, and **Text** to add persistent graph annotations. Lines
|
||||||
share the Direct, Angled, and Spline routing controls. Annotation context menus
|
share the Direct, Angled, and Spline routing controls. Annotation context menus
|
||||||
provide the same shape styling as the icon editor and can move annotations
|
provide the same shape styling as the icon editor and can move annotations
|
||||||
@@ -129,7 +130,8 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
|
|||||||
icon shape, icon text, fill color, and border color. The same dialog can hide
|
icon shape, icon text, fill color, and border color. The same dialog can hide
|
||||||
that component's contained subtree from the Libraries tree.
|
that component's contained subtree from the Libraries tree.
|
||||||
- The icon editor uses Pointer and click-drag drawing tools. Its toolbar and
|
- The icon editor uses Pointer and click-drag drawing tools. Its toolbar and
|
||||||
mouse wheel provide zoom in, zoom out, and fit-to-canvas controls.
|
mouse wheel provide zoom in, zoom out, and fit-to-canvas controls. Selected
|
||||||
|
rectangles expose draggable corner and edge resize handles in both editors.
|
||||||
- Double-click a text component to edit its input list, output list, and
|
- Double-click a text component to edit its input list, output list, and
|
||||||
`implementation.source` JSON.
|
`implementation.source` JSON.
|
||||||
- Right-click any graph component under Current Document to add nested graph or
|
- Right-click any graph component under Current Document to add nested graph or
|
||||||
|
|||||||
@@ -34,50 +34,37 @@ def _snap(value: float) -> float:
|
|||||||
|
|
||||||
|
|
||||||
class ResizeHandle(QGraphicsEllipseItem):
|
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)
|
super().__init__(-4, -4, 8, 8, owner)
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
|
self.role = role
|
||||||
self.setBrush(QColor("#ffffff"))
|
self.setBrush(QColor("#ffffff"))
|
||||||
self.setPen(QPen(QColor("#2563eb"), 1.5))
|
self.setPen(QPen(QColor("#2563eb"), 1.5))
|
||||||
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
|
self.setCursor(self.CURSORS[role])
|
||||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
|
|
||||||
self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges)
|
|
||||||
self.setZValue(20)
|
self.setZValue(20)
|
||||||
|
|
||||||
def itemChange(self, change, value): # noqa: N802
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
self.owner.begin_resize()
|
||||||
maximum = (
|
event.accept()
|
||||||
self.owner.scene().sceneRect().bottomRight() - self.owner.pos()
|
|
||||||
if self.owner.scene()
|
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
else QPointF(128, 128)
|
self.owner.resize_from_handle(self.role, event.scenePos())
|
||||||
)
|
event.accept()
|
||||||
if self.owner.element.get("type") == "line":
|
|
||||||
minimum = (
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
self.owner.scene().sceneRect().topLeft() - self.owner.pos()
|
self.owner.finish_resize()
|
||||||
if self.owner.scene()
|
event.accept()
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class ShapeOptionsDialog(QDialog):
|
class ShapeOptionsDialog(QDialog):
|
||||||
@@ -178,9 +165,16 @@ class ShapeItem(QGraphicsObject):
|
|||||||
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||||
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
)
|
)
|
||||||
self.resize_handle = ResizeHandle(self)
|
roles = (
|
||||||
self.resize_handle.setPos(float(element.get("width", 20)), float(element.get("height", 20)))
|
("n", "ne", "e", "se", "s", "sw", "w", "nw")
|
||||||
self.resize_handle.hide()
|
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
|
def boundingRect(self) -> QRectF: # noqa: N802
|
||||||
margin = max(3.0, float(self.element.get("lineWidth", 1.5)))
|
margin = max(3.0, float(self.element.get("lineWidth", 1.5)))
|
||||||
@@ -233,7 +227,8 @@ class ShapeItem(QGraphicsObject):
|
|||||||
elif change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
elif change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged:
|
||||||
self.element["x"], self.element["y"] = value.x(), value.y()
|
self.element["x"], self.element["y"] = value.x(), value.y()
|
||||||
elif change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
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)
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
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()))),
|
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
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
options = menu.addAction("Shape Options…")
|
options = menu.addAction("Shape Options…")
|
||||||
@@ -270,10 +333,7 @@ class ShapeItem(QGraphicsObject):
|
|||||||
if self.element.get("type") == "line":
|
if self.element.get("type") == "line":
|
||||||
self.element["width"] *= width_sign
|
self.element["width"] *= width_sign
|
||||||
self.element["height"] *= height_sign
|
self.element["height"] *= height_sign
|
||||||
self.resize_handle.setPos(
|
self._position_resize_handles()
|
||||||
float(self.element.get("width", 20)),
|
|
||||||
float(self.element.get("height", 20)),
|
|
||||||
)
|
|
||||||
self.update()
|
self.update()
|
||||||
elif chosen is delete and self.scene() is not None:
|
elif chosen is delete and self.scene() is not None:
|
||||||
self.scene().removeItem(self)
|
self.scene().removeItem(self)
|
||||||
|
|||||||
@@ -217,9 +217,6 @@ class InterfaceTerminalItem(QGraphicsObject):
|
|||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
if self.index < 0:
|
|
||||||
event.accept()
|
|
||||||
return
|
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
options_action = menu.addAction(f"{self.direction.title()} Options…")
|
options_action = menu.addAction(f"{self.direction.title()} Options…")
|
||||||
if menu.exec(event.screenPos()) is options_action:
|
if menu.exec(event.screenPos()) is options_action:
|
||||||
@@ -362,6 +359,20 @@ class WaypointHandle(QGraphicsEllipseItem):
|
|||||||
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
)
|
)
|
||||||
self.setCursor(Qt.CursorShape.SizeAllCursor)
|
self.setCursor(Qt.CursorShape.SizeAllCursor)
|
||||||
|
self.drag_offset = QPointF()
|
||||||
|
|
||||||
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
scene = self.scene()
|
||||||
|
if isinstance(scene, GraphScene):
|
||||||
|
owner = scene.route_graphics_item(self.item_kind, self.item_id)
|
||||||
|
if owner is not None:
|
||||||
|
owner.setSelected(True)
|
||||||
|
self.drag_offset = self.pos() - event.scenePos()
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
self.setPos(_snapped(event.scenePos() + self.drag_offset))
|
||||||
|
event.accept()
|
||||||
|
|
||||||
def itemChange(self, change, value): # noqa: N802
|
def itemChange(self, change, value): # noqa: N802
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
@@ -373,12 +384,15 @@ class WaypointHandle(QGraphicsEllipseItem):
|
|||||||
return super().itemChange(change, value)
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
super().mouseReleaseEvent(event)
|
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
if isinstance(scene, GraphScene):
|
if isinstance(scene, GraphScene):
|
||||||
scene.move_route_node(self.item_kind, self.item_id, self.index, self.pos())
|
scene.move_route_node(self.item_kind, self.item_id, self.index, self.pos())
|
||||||
|
event.accept()
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
|
if self.index < 0:
|
||||||
|
event.accept()
|
||||||
|
return
|
||||||
menu = QMenu()
|
menu = QMenu()
|
||||||
delete_action = menu.addAction("Delete Node")
|
delete_action = menu.addAction("Delete Node")
|
||||||
if menu.exec(event.screenPos()) is delete_action:
|
if menu.exec(event.screenPos()) is delete_action:
|
||||||
@@ -388,6 +402,40 @@ class WaypointHandle(QGraphicsEllipseItem):
|
|||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class AnnotationResizeHandle(QGraphicsEllipseItem):
|
||||||
|
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: "AnnotationGraphicsItem", role: str) -> None:
|
||||||
|
super().__init__(-4, -4, 8, 8, owner)
|
||||||
|
self.owner, self.role = owner, role
|
||||||
|
self.setBrush(QColor("#ffffff"))
|
||||||
|
self.setPen(QPen(QColor("#2563eb"), 1.5))
|
||||||
|
self.setCursor(self.CURSORS[role])
|
||||||
|
self.setZValue(1000)
|
||||||
|
|
||||||
|
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
|
||||||
|
self.owner.setSelected(True)
|
||||||
|
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 AnnotationGraphicsItem(QGraphicsObject):
|
class AnnotationGraphicsItem(QGraphicsObject):
|
||||||
def __init__(self, annotation: Annotation, controller: DocumentController) -> None:
|
def __init__(self, annotation: Annotation, controller: DocumentController) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -395,6 +443,7 @@ class AnnotationGraphicsItem(QGraphicsObject):
|
|||||||
self.annotation_id = annotation.id
|
self.annotation_id = annotation.id
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.drag_start = QPointF(annotation.x, annotation.y)
|
self.drag_start = QPointF(annotation.x, annotation.y)
|
||||||
|
self.resize_start: dict[str, float] | None = None
|
||||||
self.setPos(annotation.x, annotation.y)
|
self.setPos(annotation.x, annotation.y)
|
||||||
self.setZValue(annotation.layer)
|
self.setZValue(annotation.layer)
|
||||||
self.setFlags(
|
self.setFlags(
|
||||||
@@ -402,6 +451,15 @@ class AnnotationGraphicsItem(QGraphicsObject):
|
|||||||
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
| QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
|
||||||
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
| QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
|
||||||
)
|
)
|
||||||
|
roles = ("n", "ne", "e", "se", "s", "sw", "w", "nw")
|
||||||
|
self.resize_handles = (
|
||||||
|
[AnnotationResizeHandle(self, role) for role in roles]
|
||||||
|
if annotation.kind == "box"
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
self._position_resize_handles()
|
||||||
|
for handle in self.resize_handles:
|
||||||
|
handle.hide()
|
||||||
|
|
||||||
def boundingRect(self) -> QRectF: # noqa: N802
|
def boundingRect(self) -> QRectF: # noqa: N802
|
||||||
width, height = self.annotation.width, self.annotation.height
|
width, height = self.annotation.width, self.annotation.height
|
||||||
@@ -457,8 +515,75 @@ class AnnotationGraphicsItem(QGraphicsObject):
|
|||||||
def itemChange(self, change, value): # noqa: N802
|
def itemChange(self, change, value): # noqa: N802
|
||||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange:
|
||||||
return _snapped(value)
|
return _snapped(value)
|
||||||
|
if change == QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged:
|
||||||
|
for handle in self.resize_handles:
|
||||||
|
handle.setVisible(bool(value))
|
||||||
return super().itemChange(change, value)
|
return super().itemChange(change, value)
|
||||||
|
|
||||||
|
def _position_resize_handles(self) -> None:
|
||||||
|
width, height = self.annotation.width, self.annotation.height
|
||||||
|
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 = {
|
||||||
|
"x": self.annotation.x,
|
||||||
|
"y": self.annotation.y,
|
||||||
|
"width": self.annotation.width,
|
||||||
|
"height": self.annotation.height,
|
||||||
|
}
|
||||||
|
|
||||||
|
def resize_from_handle(self, role: str, scene_position: QPointF) -> None:
|
||||||
|
if self.resize_start is None:
|
||||||
|
self.begin_resize()
|
||||||
|
start = self.resize_start
|
||||||
|
edges = {
|
||||||
|
"left": start["x"],
|
||||||
|
"top": start["y"],
|
||||||
|
"right": start["x"] + start["width"],
|
||||||
|
"bottom": start["y"] + start["height"],
|
||||||
|
}
|
||||||
|
point = _snapped(scene_position)
|
||||||
|
minimum = _graph_snap_size()
|
||||||
|
if "w" in role:
|
||||||
|
edges["left"] = min(point.x(), edges["right"] - minimum)
|
||||||
|
if "e" in role:
|
||||||
|
edges["right"] = max(point.x(), edges["left"] + minimum)
|
||||||
|
if "n" in role:
|
||||||
|
edges["top"] = min(point.y(), edges["bottom"] - minimum)
|
||||||
|
if "s" in role:
|
||||||
|
edges["bottom"] = max(point.y(), edges["top"] + minimum)
|
||||||
|
self.prepareGeometryChange()
|
||||||
|
self.annotation.x, self.annotation.y = edges["left"], edges["top"]
|
||||||
|
self.annotation.width = edges["right"] - edges["left"]
|
||||||
|
self.annotation.height = edges["bottom"] - edges["top"]
|
||||||
|
self.setPos(self.annotation.x, self.annotation.y)
|
||||||
|
self._position_resize_handles()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def finish_resize(self) -> None:
|
||||||
|
if self.resize_start is None:
|
||||||
|
return
|
||||||
|
old = self.resize_start
|
||||||
|
new = {
|
||||||
|
"x": self.annotation.x,
|
||||||
|
"y": self.annotation.y,
|
||||||
|
"width": self.annotation.width,
|
||||||
|
"height": self.annotation.height,
|
||||||
|
}
|
||||||
|
self.resize_start = None
|
||||||
|
self.controller.set_annotation_geometry(self.annotation_id, old, new)
|
||||||
|
|
||||||
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: # noqa: N802
|
||||||
scene = self.scene()
|
scene = self.scene()
|
||||||
if not isinstance(scene, GraphScene):
|
if not isinstance(scene, GraphScene):
|
||||||
@@ -655,6 +780,7 @@ class GraphScene(QGraphicsScene):
|
|||||||
elif isinstance(graphics, AnnotationGraphicsItem):
|
elif isinstance(graphics, AnnotationGraphicsItem):
|
||||||
graphics.prepareGeometryChange()
|
graphics.prepareGeometryChange()
|
||||||
graphics.setPos(annotation.x, annotation.y)
|
graphics.setPos(annotation.x, annotation.y)
|
||||||
|
graphics._position_resize_handles()
|
||||||
graphics.update()
|
graphics.update()
|
||||||
|
|
||||||
def edit_annotation_options(self, annotation_id: str) -> None:
|
def edit_annotation_options(self, annotation_id: str) -> None:
|
||||||
@@ -951,6 +1077,13 @@ class GraphScene(QGraphicsScene):
|
|||||||
]
|
]
|
||||||
return item, points
|
return item, points
|
||||||
|
|
||||||
|
def route_graphics_item(self, item_kind: str, item_id: str) -> QGraphicsItem | None:
|
||||||
|
return (
|
||||||
|
self.connection_items.get(item_id)
|
||||||
|
if item_kind == "connection"
|
||||||
|
else self.annotation_items.get(item_id)
|
||||||
|
)
|
||||||
|
|
||||||
def add_route_node(self, item_kind: str, item_id: str, position: QPointF) -> None:
|
def add_route_node(self, item_kind: str, item_id: str, position: QPointF) -> None:
|
||||||
item, points = self._route_values(item_kind, item_id)
|
item, points = self._route_values(item_kind, item_id)
|
||||||
if item is None:
|
if item is None:
|
||||||
@@ -986,6 +1119,8 @@ class GraphScene(QGraphicsScene):
|
|||||||
)
|
)
|
||||||
points.insert(insertion, snapped)
|
points.insert(insertion, snapped)
|
||||||
routing = "angled" if item.properties.get("routing") == "direct" else None
|
routing = "angled" if item.properties.get("routing") == "direct" else None
|
||||||
|
if routing == "angled" or item.properties.get("routing") == "angled":
|
||||||
|
points = self._orthogonal_points(start, end, points)[1:-1]
|
||||||
self.controller.set_route_waypoints(item_kind, item_id, points, routing)
|
self.controller.set_route_waypoints(item_kind, item_id, points, routing)
|
||||||
|
|
||||||
def move_route_node(self, item_kind: str, item_id: str, index: int, position: QPointF) -> None:
|
def move_route_node(self, item_kind: str, item_id: str, index: int, position: QPointF) -> None:
|
||||||
@@ -1012,6 +1147,14 @@ class GraphScene(QGraphicsScene):
|
|||||||
return
|
return
|
||||||
if index < len(points):
|
if index < len(points):
|
||||||
points[index] = position
|
points[index] = position
|
||||||
|
if item.properties.get("routing") == "angled":
|
||||||
|
if item_kind == "annotation":
|
||||||
|
start = QPointF(item.x, item.y)
|
||||||
|
end = QPointF(item.x + item.width, item.y + item.height)
|
||||||
|
else:
|
||||||
|
graphics = self.connection_items[item_id]
|
||||||
|
start, end = graphics.start, graphics.end
|
||||||
|
points = self._orthogonal_points(start, end, points)[1:-1]
|
||||||
self.controller.set_route_waypoints(item_kind, item_id, points)
|
self.controller.set_route_waypoints(item_kind, item_id, points)
|
||||||
|
|
||||||
def preview_route_node(
|
def preview_route_node(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"x": 32.0,
|
"x": 32.0,
|
||||||
"y": 32.0,
|
"y": 32.0,
|
||||||
"width": 64.0,
|
"width": 88.0,
|
||||||
"height": 64.0,
|
"height": 64.0,
|
||||||
"fill": "#f4f4f4",
|
"fill": "#f4f4f4",
|
||||||
"stroke": "#303030",
|
"stroke": "#303030",
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"x": 40.0,
|
"x": 40.0,
|
||||||
"y": 40.0,
|
"y": 40.0,
|
||||||
"width": 48.0,
|
"width": 72.0,
|
||||||
"height": 48.0,
|
"height": 40.0,
|
||||||
"text": "Graph",
|
"text": "Graph",
|
||||||
"color": "#202020",
|
"color": "#202020",
|
||||||
"fontSize": 12.0
|
"fontSize": 12.0
|
||||||
@@ -288,9 +288,17 @@
|
|||||||
"y": 64.0
|
"y": 64.0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"x": 64.0,
|
"x": 0.0,
|
||||||
"y": 64.0
|
"y": 64.0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 120.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 64.0,
|
||||||
|
"y": 120.0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"x": 64.0,
|
"x": 64.0,
|
||||||
"y": -80.0
|
"y": -80.0
|
||||||
@@ -308,8 +316,8 @@
|
|||||||
"y": -328.0
|
"y": -328.0
|
||||||
},
|
},
|
||||||
"size": {
|
"size": {
|
||||||
"width": 160.0,
|
"width": 304.0,
|
||||||
"height": 112.0
|
"height": 160.0
|
||||||
},
|
},
|
||||||
"text": "",
|
"text": "",
|
||||||
"layer": -1,
|
"layer": -1,
|
||||||
|
|||||||
Reference in New Issue
Block a user