From b0e9d0afcb8103068dd479d66f303f915eccc401 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 17:10:11 +0530 Subject: [PATCH 01/12] [ui][fix] Edge: Fixed the type for mouse event modifiers With PySide6 the modifiers are now enum.Flag based. Updated type ensures the alt+mouse click removes edges from graphs --- meshroom/ui/components/edge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshroom/ui/components/edge.py b/meshroom/ui/components/edge.py index 4108fc6a2c..20b48495f4 100755 --- a/meshroom/ui/components/edge.py +++ b/meshroom/ui/components/edge.py @@ -1,4 +1,4 @@ -from PySide6.QtCore import Signal, Property, QPointF, Qt, QObject +from PySide6.QtCore import Signal, Property, QPointF, Qt, QObject, Slot, QRectF from PySide6.QtGui import QPainterPath, QVector2D from PySide6.QtQuick import QQuickItem @@ -17,7 +17,7 @@ def __init__(self, evt): x = Property(float, lambda self: self._x, constant=True) y = Property(float, lambda self: self._y, constant=True) button = Property(Qt.MouseButton, lambda self: self._button, constant=True) - modifiers = Property(int, lambda self: self._modifiers, constant=True) + modifiers = Property(Qt.KeyboardModifier, lambda self: self._modifiers, constant=True) class EdgeMouseArea(QQuickItem): From 3168e4e559977ff407d049fbc9c6b03ecb79255d Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 17:22:11 +0530 Subject: [PATCH 02/12] [ui] Graph: Added disconnectSelectedNodes function This function is responsible to disconnect any edges incoming and outgoing edges from the nodes which do not lie in the current selection --- meshroom/ui/graph.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index 0cde6b2ddd..4ba6c5942c 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -850,6 +850,18 @@ def removeEdge(self, edge): else: self.push(commands.RemoveEdgeCommand(self._graph, edge)) + @Slot() + def disconnectSelectedNodes(self): + with self.groupedGraphModification("Disconnect Nodes"): + selectedNodes = self.getSelectedNodes() + for edge in self._graph.edges[:]: + # Remove only the edges which are coming or going out of the current selection + if edge.src.node in selectedNodes and edge.dst.node in selectedNodes: + continue + + if edge.dst.node in selectedNodes or edge.src.node in selectedNodes: + self.removeEdge(edge) + @Slot(Edge, Attribute, Attribute, result=Edge) def replaceEdge(self, edge, newSrc, newDst): with self.groupedGraphModification("Replace Edge '{}'->'{}' with '{}'->'{}'".format(edge.src.getFullNameToNode(), edge.dst.getFullNameToNode(), newSrc.getFullNameToNode(), newDst.getFullNameToNode())): From 2ff3596c5917ca7b127b802f6370deb68db43d98 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 17:25:39 +0530 Subject: [PATCH 03/12] [ui] GraphEditor: Added shortcut to invoke disconnectSelectedNodes functionality for the graph --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 0f33730828..611efdbdbc 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -120,9 +120,14 @@ Item { } } else if (event.key === Qt.Key_D) { duplicateNode(event.modifiers === Qt.AltModifier) - } else if (event.key === Qt.Key_X && event.modifiers === Qt.ControlModifier) { - copyNodes() - uigraph.removeSelectedNodes() + } else if (event.key === Qt.Key_X) { + if (event.modifiers === Qt.ControlModifier) { + copyNodes() + uigraph.removeSelectedNodes() + } + else { + uigraph.disconnectSelectedNodes() + } } else if (event.key === Qt.Key_C) { if (event.modifiers === Qt.ControlModifier) { copyNodes() From 265faabe6502483015322424d6103dc300215d3e Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 17:40:01 +0530 Subject: [PATCH 04/12] [ui] GraphEditor: Added Menu item to invoke disconnectSelectedNodes functionality for the graph --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 611efdbdbc..c236b70532 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -721,6 +721,13 @@ Item { pasteNodes() } } + MenuItem { + text: "Disconnect Node(s)"; + enabled: true; + ToolTip.text: "Disconnect all edges from the selected Node(s)"; + ToolTip.visible: hovered; + onTriggered: uigraph.disconnectSelectedNodes(); + } MenuItem { text: "Duplicate Node(s)" + (duplicateFollowingButton.hovered ? " From Here" : "") enabled: true From ee7195336913768495e3323930ab97c104a6fca1 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 17:45:24 +0530 Subject: [PATCH 05/12] [ui] Node: Added backend and signal for a Node Shake Node shake is detected when the node starts changins positions after being clicked and it is tracked via a timer to consider a shake. Emits the shaked signal when the node is shaked --- meshroom/ui/qml/GraphEditor/Node.qml | 130 ++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/Node.qml b/meshroom/ui/qml/GraphEditor/Node.qml index 65b95cd827..6d069c51c4 100755 --- a/meshroom/ui/qml/GraphEditor/Node.qml +++ b/meshroom/ui/qml/GraphEditor/Node.qml @@ -31,6 +31,20 @@ Item { readonly property color defaultColor: isCompatibilityNode ? "#444" : !node.isComputable ? "#BA3D69" : activePalette.base property color baseColor: defaultColor + /// Shake Relevance + readonly property double maxAmplitude: 300.0; + readonly property int shakeThreshold: 5; + + property int shakeCounter: 0; + property bool shaking: false; + property int shakeDetectionInterval: 1000; // 1 Second to complete the shake else the counter is reset + + property double originalRootX: 0.0; + property double originalRootY: 0.0; + + property int directionX: 0; + property int directionY: 0; + property point mousePosition: Qt.point(mouseArea.mouseX, mouseArea.mouseY) Item { @@ -44,6 +58,7 @@ Item { signal clicked(var mouse) signal doubleClicked(var mouse) signal moved(var position) + signal shaked() signal entered() signal exited() @@ -75,6 +90,107 @@ Item { } } + Timer { + id: shakeDetectionTimer; + interval: root.shakeDetectionInterval; + onTriggered: { + if (root.shaking) { + root.resetShaking(); + } + } + } + + function beginShaking() { + /** + * Sets up the shake related values. + * Enables Shake detection. + */ + root.shaking = true; + + // Capture the current Root's X and Y to use in detecting the movement of the node around these points + root.originalRootX = root.x; + root.originalRootY = root.y; + } + + function resetShaking() { + /** + * Resets the shaking and the variables tracking a shake. + */ + // Reset the shake counter when shaking has ended + root.shakeCounter = 0; + + // Reset the direction detection + root.directionX = 0; + root.directionY = 0; + } + + function endShaking() { + /** + * Resets all values related to shaking. + * Ends the shake detection. + */ + root.shaking = false; + + root.resetShaking(); + } + + function checkForShake() { + /** + * Detects a shake if a the node has been moved across the originally captured x and y positions + * back and forth a given number of times specified by the amplitude. + */ + + if (!root.shaking) { + return; + } + + // This indicates that the shake was either reset or we're starting from scratch + if (root.shakeCounter === 0 && !shakeDetectionTimer.running) { + shakeDetectionTimer.start(); + } + + const deltaX = root.x - root.originalRootX; + const deltaY = root.y - root.originalRootY; + + // Check if the node has not travelled too much from the original position + // If so, stop detecting a shake as that might not be needed + if (Math.abs(deltaX) > root.maxAmplitude || Math.abs(deltaY) > root.maxAmplitude) { + root.endShaking(); + } + + // This checks the current direction in which the node is travelling + // if the node has moved on the left side of the original position -1 + // if the node has moved on the right side of the original position +1 + + // <-- Origin + // [Node] | + // | [Node] + // | --> + // If the motion continues to be like this 'threshold' number of times + // This will be considered as a shake effect + + const currentDirectionX = deltaX > 0 ? 1 : -1; + const currentDirectionY = deltaY > 0 ? 1 : -1; + + // Check if we're in the opposite direction of what was the previous direction of the Node + // If yes then we're propagating as a shake effect + if (currentDirectionX != root.directionX || currentDirectionY != root.directionY) { + // One shake cycle is complete, increment the counter + root.shakeCounter++; + + // Update the original direction to be the current one + root.directionX = currentDirectionX; + root.directionY = currentDirectionY; + } + + // The node has moved in a shake effect to match the threshold and this is causing it to be detected as a shake + if (root.shakeCounter > root.shakeThreshold) { + root.shaked(); + // Reset the counter to detect another shake effect + root.resetShaking(); + } + } + function formatInternalAttributesTooltip(invalidation, comment) { /* * Creates a string that contains the invalidation message (if it is not empty) in bold, @@ -127,8 +243,6 @@ Item { drag.threshold: 2 hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton - onPressed: (mouse) => root.pressed(mouse) - onReleased: (mouse) => root.released(mouse) onClicked: (mouse) => root.clicked(mouse) onDoubleClicked: (mouse) => root.doubleClicked(mouse) onEntered: root.entered() @@ -141,6 +255,18 @@ Item { cursorShape: drag.active ? Qt.ClosedHandCursor : Qt.ArrowCursor + onPressed: function(mouse) { + root.pressed(mouse); + // Begin shake detection + root.beginShaking(); + } + + onReleased: function(mouse) { + root.released(mouse); + // End shake detection + root.endShaking(); + } + // Selection border Rectangle { anchors.fill: nodeContent From d978114d732bbe85fe1bf6f41a87c16e7adc7c37 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 17:52:11 +0530 Subject: [PATCH 06/12] [ui] GraphEditor: Added check for node shake and connected shake to disconnect nodes method --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index c236b70532..fea627e125 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -860,6 +860,10 @@ Item { onAttributePinCreated: function(attribute, pin) { registerAttributePin(attribute, pin) } onAttributePinDeleted: function(attribute, pin) { unregisterAttributePin(attribute, pin) } + onShaked: { + uigraph.disconnectSelectedNodes(); + } + onPressed: function(mouse) { nodeRepeater.updateSelectionOnClick = true; nodeRepeater.ongoingDrag = true; @@ -955,6 +959,9 @@ Item { if(!selected || !dragging) { return; } + + // Check for shake on the node + checkForShake(); // Compute offset between the delegate and the stored node position. const offset = Qt.point(x - node.x, y - node.y); From e81c344bbc3890735ddb1e06123e45c374973ab9 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 19:23:09 +0530 Subject: [PATCH 07/12] [ui] GraphEditor: Moved the click on edge to remove with the remove popup to be triggered with right click --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index fea627e125..2ffab85b03 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -506,7 +506,7 @@ Item { if (event.button) { if (canEdit && (event.modifiers & Qt.AltModifier)) { uigraph.removeEdge(edge) - } else { + } else if (event.button == Qt.RightButton) { edgeMenu.currentEdge = edge edgeMenu.forLoop = forLoop var spawnPosition = mouseArea.mapToItem(draggable, mouseArea.mouseX, mouseArea.mouseY) @@ -962,6 +962,7 @@ Item { // Check for shake on the node checkForShake(); + // Compute offset between the delegate and the stored node position. const offset = Qt.point(x - node.x, y - node.y); From 87eedb8e6415ecce3eb3d1bd6b180ed2cddd5576 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 19:24:54 +0530 Subject: [PATCH 08/12] [ui] Edge: Added method to detect an intersection of a Rectangle's diagonal agasint the Edge Path If an edge or shape were to cut the edge, the intersection of the shake is detected by this function --- meshroom/ui/components/edge.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/meshroom/ui/components/edge.py b/meshroom/ui/components/edge.py index 20b48495f4..25477cefcc 100755 --- a/meshroom/ui/components/edge.py +++ b/meshroom/ui/components/edge.py @@ -110,6 +110,17 @@ def setContainsMouse(self, value): self._containsMouse = value self.containsMouseChanged.emit() + @Slot(QRectF, result=bool) + def intersects(self, rect): + """ Checks whether the given rectangle's diagonal intersects with the Path. """ + path = QPainterPath() + # Starting point + path.moveTo(QPointF(rect.x(), rect.y())) + # Create a diagonal line to the other end of the rect + path.lineTo(QPointF(rect.width() + rect.x(), rect.height() + rect.y())) + + return self._path.intersects(path) + thicknessChanged = Signal() thickness = Property(float, getThickness, setThickness, notify=thicknessChanged) curveScaleChanged = Signal() From b1f2f6b73ed4cc8b33cce9fe67dd6d89d0e6e9a9 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 19:33:46 +0530 Subject: [PATCH 09/12] [ui] Edge: Exposed the intersects method from the QML Edge component --- meshroom/ui/qml/GraphEditor/Edge.qml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/meshroom/ui/qml/GraphEditor/Edge.qml b/meshroom/ui/qml/GraphEditor/Edge.qml index ba90a688a8..e0eef12f87 100644 --- a/meshroom/ui/qml/GraphEditor/Edge.qml +++ b/meshroom/ui/qml/GraphEditor/Edge.qml @@ -40,6 +40,17 @@ Item { property real endY: height + function intersects(rect) { + /** + * Detects whether a line along the given rects diagonal intersects with the edge mouse area. + */ + // The edgeArea is within the parent Item and its bounds and position are relative to its parent + // Map the original rect to the coordinates of the edgeArea by subtracting the parent's coordinates from the rect + // This mapped rect would ensure that the rect coordinates map to 0 of the edge area + const mappedRect = Qt.rect(rect.x - x, rect.y - y, rect.width, rect.height); + return edgeArea.intersects(mappedRect); + } + Shape { anchors.fill: parent // Cause rendering artifacts when enabled (and don't support hot reload really well) From 0ee575f40c423818557a2271bc6e989617e0d73b Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 19:42:32 +0530 Subject: [PATCH 10/12] [ui] Graph: Added method to delete edges by indices This method allows removal of edges from the graph by index of the edge --- meshroom/ui/graph.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index 4ba6c5942c..b6c5cb23ad 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -850,6 +850,13 @@ def removeEdge(self, edge): else: self.push(commands.RemoveEdgeCommand(self._graph, edge)) + @Slot(list) + def deleteEdgesByIndices(self, indices): + with self.groupedGraphModification("Remove Edges"): + copied = list(self._graph.edges) + for index in indices: + self.removeEdge(copied[index]) + @Slot() def disconnectSelectedNodes(self): with self.groupedGraphModification("Disconnect Nodes"): From b2a6b1836446664477feb914dbb54cc436277e60 Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 19:44:32 +0530 Subject: [PATCH 11/12] [ui] Added selectionLine and delegate variant to allow drawing an edge The drawn edge is checked for intersection with individual node edges and any edge found interesecting is emitted when the selection ends --- .../ui/qml/Controls/DelegateSelectionLine.qml | 31 ++++++++ meshroom/ui/qml/Controls/SelectionLine.qml | 77 +++++++++++++++++++ meshroom/ui/qml/Controls/qmldir | 4 +- 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 meshroom/ui/qml/Controls/DelegateSelectionLine.qml create mode 100644 meshroom/ui/qml/Controls/SelectionLine.qml diff --git a/meshroom/ui/qml/Controls/DelegateSelectionLine.qml b/meshroom/ui/qml/Controls/DelegateSelectionLine.qml new file mode 100644 index 0000000000..779ec5fd9c --- /dev/null +++ b/meshroom/ui/qml/Controls/DelegateSelectionLine.qml @@ -0,0 +1,31 @@ +import QtQuick +import Meshroom.Helpers + +/* +A SelectionLine that can be used to select delegates in a model instantiator (Repeater, ListView...). +Interesection test is done in the coordinate system of the container Item, using delegate's bounding boxes. +The list of selected indices is emitted when the selection ends. +*/ + +SelectionLine { + id: root + + // The Item instantiating the delegates. + property Item modelInstantiator + // The Item containing the delegates (used for coordinate mapping). + property Item container + // Emitted when the selection has ended, with the list of selected indices and modifiers. + signal delegateSelectionEnded(list indices, int modifiers) + + onSelectionEnded: function(selectionRect, modifiers) { + let selectedIndices = []; + const mappedSelectionRect = mapToItem(container, selectionRect); + for (var i = 0; i < modelInstantiator.count; ++i) { + const delegate = modelInstantiator.itemAt(i); + if (delegate.intersects(mappedSelectionRect)) { + selectedIndices.push(i); + } + } + delegateSelectionEnded(selectedIndices, modifiers); + } +} diff --git a/meshroom/ui/qml/Controls/SelectionLine.qml b/meshroom/ui/qml/Controls/SelectionLine.qml new file mode 100644 index 0000000000..80cf854584 --- /dev/null +++ b/meshroom/ui/qml/Controls/SelectionLine.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Shapes + +/* +Simple selection line that can be used by a MouseArea. + +Usage: +1. Create a MouseArea and a selectionShape. +2. Bind the selectionShape to the MouseArea by setting the `mouseArea` property. +3. Call startSelection() with coordinates when the selection starts. +4. Call endSelection() when the selection ends. +5. Listen to the selectionEnded signal to get the rectangle whose Diagonal is the selection line. +*/ + +Item { + id: root + + property MouseArea mouseArea + + readonly property bool active: mouseArea.drag.target == dragTarget + + signal selectionEnded(rect selectionRect, int modifiers) + + function startSelection(mouse) { + dragTarget.startPos.x = dragTarget.x = mouse.x; + dragTarget.startPos.y = dragTarget.y = mouse.y; + dragTarget.modifiers = mouse.modifiers; + mouseArea.drag.target = dragTarget; + } + + function endSelection() { + if (!active) { + return; + } + mouseArea.drag.target = null; + const rect = Qt.rect(selectionShape.x, selectionShape.y, selectionShape.width, selectionShape.height) + selectionEnded(rect, dragTarget.modifiers); + } + + visible: active + + Item { + id: selectionShape + x: dragTarget.startPos.x + y: dragTarget.startPos.y + width: dragTarget.x - dragTarget.startPos.x + height: dragTarget.y - dragTarget.startPos.y + + Shape { + id: dynamicLine; + width: selectionShape.width; + height: selectionShape.height; + anchors.fill: parent; + + ShapePath { + strokeWidth: 2; + strokeStyle: ShapePath.DashLine; + strokeColor: "#FF0000"; + dashPattern: [3, 2]; + + startX: 0; + startY: 0; + + PathLine { + x: selectionShape.width; + y: selectionShape.height; + } + } + } + } + + Item { + id: dragTarget + property point startPos + property var modifiers + } +} diff --git a/meshroom/ui/qml/Controls/qmldir b/meshroom/ui/qml/Controls/qmldir index 9d3e23cb75..c11085ac7d 100644 --- a/meshroom/ui/qml/Controls/qmldir +++ b/meshroom/ui/qml/Controls/qmldir @@ -18,4 +18,6 @@ MScrollBar 1.0 MScrollBar.qml MSplitView 1.0 MSplitView.qml DirectionalLightPane 1.0 DirectionalLightPane.qml SelectionBox 1.0 SelectionBox.qml -DelegateSelectionBox 1.0 DelegateSelectionBox.qml \ No newline at end of file +SelectionLine 1.0 SelectionLine.qml +DelegateSelectionBox 1.0 DelegateSelectionBox.qml +DelegateSelectionLine 1.0 DelegateSelectionLine.qml From bf72cf8059033d0056d67409c69649e20cf69c0a Mon Sep 17 00:00:00 2001 From: waaake Date: Sun, 12 Jan 2025 19:46:10 +0530 Subject: [PATCH 12/12] [ui] GraphEditor: Introduced DelegateSelectionLine to the graph editor Upon selection end the found intersected edges gets removed --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 2ffab85b03..eedccfe629 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -150,6 +150,7 @@ Item { id: mouseArea anchors.fill: parent property double factor: 1.15 + property bool removingEdges: false; // Activate multisampling for edges antialiasing layer.enabled: true layer.samples: 8 @@ -157,7 +158,7 @@ Item { hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton drag.threshold: 0 - cursorShape: drag.target == draggable ? Qt.ClosedHandCursor : Qt.ArrowCursor + cursorShape: drag.target == draggable ? Qt.ClosedHandCursor : removingEdges ? Qt.CrossCursor : Qt.ArrowCursor onWheel: function(wheel) { var zoomFactor = wheel.angleDelta.y > 0 ? factor : 1 / factor @@ -182,9 +183,15 @@ Item { if (mouse.button == Qt.MiddleButton || (mouse.button == Qt.LeftButton && mouse.modifiers & Qt.AltModifier)) { drag.target = draggable // start drag } + if (mouse.button == Qt.LeftButton && (mouse.modifiers & Qt.ControlModifier) && (mouse.modifiers & Qt.AltModifier)) { + edgeSelectionLine.startSelection(mouse); + removingEdges = true; + } } onReleased: { + removingEdges = false; + edgeSelectionLine.endSelection() nodeSelectionBox.endSelection(); drag.target = null; root.forceActiveFocus() @@ -1009,6 +1016,16 @@ Item { } } + DelegateSelectionLine { + id: edgeSelectionLine + mouseArea: mouseArea + modelInstantiator: edgesRepeater + container: draggable + onDelegateSelectionEnded: function(selectedIndices, modifiers) { + uigraph.deleteEdgesByIndices(selectedIndices); + } + } + DropArea { id: dropArea anchors.fill: parent