diff --git a/meshroom/ui/qml/GraphEditor/AttributePin.qml b/meshroom/ui/qml/GraphEditor/AttributePin.qml index f3b30c0d56..9d72cc1982 100755 --- a/meshroom/ui/qml/GraphEditor/AttributePin.qml +++ b/meshroom/ui/qml/GraphEditor/AttributePin.qml @@ -36,6 +36,9 @@ RowLayout { signal pressed(var mouse) signal edgeAboutToBeRemoved(var input) signal clicked() + /// Emitted when the user pans with the middle mouse button while dragging an edge. + /// dx and dy are delta values in screen pixel coordinates. + signal panRequested(real dx, real dy) objectName: attribute ? attribute.name + "." : "" layoutDirection: Qt.LeftToRight @@ -184,6 +187,8 @@ RowLayout { enabled: !root.readOnly anchors.fill: parent hoverEnabled: root.visible + // Also accept middle button to handle graph panning without releasing the edge drag + acceptedButtons: Qt.LeftButton | Qt.MiddleButton // Use the same negative margins as DropArea to ease pin selection anchors.margins: inputDropArea.anchors.margins @@ -194,16 +199,34 @@ RowLayout { property bool isPressed: false // The mouse has been pressed but not released yet property double initialX: 0.0 property double initialY: 0.0 + property point lastPanPos: Qt.point(0, 0) // Last global position for middle-button panning onPressed: function(mouse) { + if (mouse.button === Qt.MiddleButton) { + lastPanPos = mapToGlobal(mouse.x, mouse.y) + return + } root.pressed(mouse) isPressed = true initialX = mouse.x initialY = mouse.y } - onReleased: { - inputDragTarget.Drag.drop() + onReleased: function(mouse) { + if (mouse.button === Qt.MiddleButton) { + return + } + if (mouse.button === Qt.LeftButton) { + inputDragTarget.Drag.drop() + } else { + inputDragTarget.Drag.cancel() + } + isPressed = false + dragTriggered = false + } + + onCanceled: { + inputDragTarget.Drag.cancel() isPressed = false dragTriggered = false } @@ -213,6 +236,12 @@ RowLayout { } onPositionChanged: function(mouse) { + if (pressedButtons & Qt.MiddleButton) { + var globalPos = mapToGlobal(mouse.x, mouse.y) + root.panRequested(globalPos.x - lastPanPos.x, globalPos.y - lastPanPos.y) + lastPanPos = globalPos + return + } // If there has been a significant move (5px along the -X or -Y axis) while the // mouse is being pressed, then we can consider being in the dragging state if (isPressed && (Math.abs(mouse.x - initialX) >= 5.0 || Math.abs(mouse.y - initialY) >= 5.0)) { @@ -421,13 +450,20 @@ RowLayout { anchors.rightMargin: outputDropArea.anchors.rightMargin hoverEnabled: root.visible + // Also accept middle button to handle graph panning without releasing the edge drag + acceptedButtons: Qt.LeftButton | Qt.MiddleButton property bool dragTriggered: false // An edge is being dragged from the output connector property bool isPressed: false // The mouse has been pressed but not released yet property double initialX: 0.0 property double initialY: 0.0 + property point lastPanPos: Qt.point(0, 0) // Last global position for middle-button panning onPressed: function(mouse) { + if (mouse.button === Qt.MiddleButton) { + lastPanPos = mapToGlobal(mouse.x, mouse.y) + return + } root.pressed(mouse) isPressed = true initialX = mouse.x @@ -435,7 +471,20 @@ RowLayout { } onReleased: function(mouse) { - outputDragTarget.Drag.drop() + if (mouse.button === Qt.MiddleButton) { + return + } + if (mouse.button === Qt.LeftButton) { + outputDragTarget.Drag.drop() + } else { + outputDragTarget.Drag.cancel() + } + isPressed = false + dragTriggered = false + } + + onCanceled: { + outputDragTarget.Drag.cancel() isPressed = false dragTriggered = false } @@ -445,6 +494,12 @@ RowLayout { } onPositionChanged: function(mouse) { + if (pressedButtons & Qt.MiddleButton) { + var globalPos = mapToGlobal(mouse.x, mouse.y) + root.panRequested(globalPos.x - lastPanPos.x, globalPos.y - lastPanPos.y) + lastPanPos = globalPos + return + } // If there's been a significant move (5px along the -X or -Y axis) while the mouse is being // pressed, then we can consider being in the dragging state. if (isPressed && (Math.abs(mouse.x - initialX) >= 5.0 || Math.abs(mouse.y - initialY) >= 5.0)) { diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index ca5ff8b3e2..be59a68509 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -1029,6 +1029,12 @@ Item { } } + onPanRequested: function(dx, dy) { + draggable.x += dx + draggable.y += dy + workspaceMoved() + } + // Interactive dragging: move the visual delegates onPositionChanged: { if (!selected || !dragging) { diff --git a/meshroom/ui/qml/GraphEditor/Node.qml b/meshroom/ui/qml/GraphEditor/Node.qml index b513ed383e..6cbcaa6336 100755 --- a/meshroom/ui/qml/GraphEditor/Node.qml +++ b/meshroom/ui/qml/GraphEditor/Node.qml @@ -66,6 +66,9 @@ Item { // Already connected attribute with another edge in DropArea signal edgeAboutToBeRemoved(var input) + /// Emitted when an attribute pin requests graph panning during edge drag (middle mouse button). + signal panRequested(real dx, real dy) + /// Emitted when child attribute pins are created signal attributePinCreated(var attribute, var pin) /// Emitted when child attribute pins are deleted @@ -661,6 +664,8 @@ Item { root.edgeAboutToBeRemoved(input) } + onPanRequested: function(dx, dy) { root.panRequested(dx, dy) } + Component.onCompleted: attributePinCreated(attribute, outPin) onChildPinCreated: attributePinCreated(childAttribute, outPin) Component.onDestruction: attributePinDeleted(attribute, outPin) @@ -739,6 +744,8 @@ Item { root.edgeAboutToBeRemoved(input) } + onPanRequested: function(dx, dy) { root.panRequested(dx, dy) } + onChildPinCreated: function(childAttribute, inPin) { attributePinCreated(childAttribute, inPin) } onChildPinDeleted: function(childAttribute, inPin) { attributePinDeleted(childAttribute, inPin) } } @@ -849,6 +856,8 @@ Item { root.edgeAboutToBeRemoved(input) } + onPanRequested: function(dx, dy) { root.panRequested(dx, dy) } + onChildPinCreated: function(childAttribute, inParamsPin) { attributePinCreated(childAttribute, inParamsPin) } onChildPinDeleted: function(childAttribute, inParamsPin) { attributePinDeleted(childAttribute, inParamsPin) } }