Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui]: Introduction of multiple ways to remove Node Edges #2644

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions meshroom/ui/components/edge.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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):
Expand Down Expand Up @@ -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()
Expand Down
19 changes: 19 additions & 0 deletions meshroom/ui/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,25 @@ 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"):
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())):
Expand Down
31 changes: 31 additions & 0 deletions meshroom/ui/qml/Controls/DelegateSelectionLine.qml
Original file line number Diff line number Diff line change
@@ -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<int> 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);
}
}
77 changes: 77 additions & 0 deletions meshroom/ui/qml/Controls/SelectionLine.qml
Original file line number Diff line number Diff line change
@@ -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
}
}
4 changes: 3 additions & 1 deletion meshroom/ui/qml/Controls/qmldir
Original file line number Diff line number Diff line change
Expand Up @@ -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
SelectionLine 1.0 SelectionLine.qml
DelegateSelectionBox 1.0 DelegateSelectionBox.qml
DelegateSelectionLine 1.0 DelegateSelectionLine.qml
11 changes: 11 additions & 0 deletions meshroom/ui/qml/GraphEditor/Edge.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
47 changes: 42 additions & 5 deletions meshroom/ui/qml/GraphEditor/GraphEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -145,14 +150,15 @@ 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

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
Expand All @@ -177,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()
Expand Down Expand Up @@ -501,7 +513,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)
Expand Down Expand Up @@ -716,6 +728,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
Expand Down Expand Up @@ -848,6 +867,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;
Expand Down Expand Up @@ -943,6 +966,10 @@ 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);

Expand Down Expand Up @@ -989,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
Expand Down
Loading
Loading