Meshroom/meshroom/ui/qml/GraphEditor/AttributePin.qml
Candice Bentéjac 6f1ac9a06e [ui] Hide disabled File attributes and their edges but keep the connections
This commit effectively hides a node's attributes when they are disabled.
If these attributes have connections, the edges representing these
connections are hidden but not destroyed. When the edges are hidden, if
one of the connected attributes is still enabled, its pin becomes empty as
if it was not connected to anything.

If the disabled attribute is re-enabled and the connection has not been
broken (if the enabled attribute on the other side of the edge has not
been reconnected to another attribute, for example), the edge re-appears.
If the connection has been broken, then the attribute will be unconnected.

Hidden connections are saved in project files and taken into account when
a project file containing some is loaded.
2024-04-19 17:16:20 +02:00

393 lines
15 KiB
QML
Executable file

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.11
import MaterialIcons 2.2
import Utils 1.0
/**
The representation of an Attribute on a Node.
*/
RowLayout {
id: root
property var nodeItem
property var attribute
property bool readOnly: false
/// Whether to display an output pin for input attribute
property bool displayOutputPinForInput: true
// position of the anchor for attaching and edge to this attribute pin
readonly property point inputAnchorPos: Qt.point(inputAnchor.x + inputAnchor.width / 2,
inputAnchor.y + inputAnchor.height / 2)
readonly property point outputAnchorPos: Qt.point(outputAnchor.x + outputAnchor.width / 2,
outputAnchor.y + outputAnchor.height / 2)
readonly property bool isList: attribute && attribute.type === "ListAttribute"
signal childPinCreated(var childAttribute, var pin)
signal childPinDeleted(var childAttribute, var pin)
signal pressed(var mouse)
signal edgeAboutToBeRemoved(var input)
objectName: attribute ? attribute.name + "." : ""
layoutDirection: Qt.LeftToRight
spacing: 3
function updatePin(isSrc, isVisible)
{
if (isSrc) {
innerOutputAnchor.linkEnabled = isVisible
} else {
innerInputAnchor.linkEnabled = isVisible
}
}
// Instantiate empty Items for each child attribute
Repeater {
id: childrenRepeater
model: isList && !attribute.isLink ? attribute.value : 0
onItemAdded: childPinCreated(item.childAttribute, item)
onItemRemoved: childPinDeleted(item.childAttribute, item)
delegate: Item {
property var childAttribute: object
}
}
Rectangle {
visible: !attribute.isOutput
id: inputAnchor
width: 8
height: width
radius: isList ? 0 : width / 2
Layout.alignment: Qt.AlignVCenter
border.color: Colors.sysPalette.mid
color: Colors.sysPalette.base
Rectangle {
id: innerInputAnchor
property bool linkEnabled: true
visible: inputConnectMA.containsMouse || childrenRepeater.count > 0 || (attribute && attribute.isLink && linkEnabled) || inputConnectMA.drag.active || inputDropArea.containsDrag
radius: isList ? 0 : 2
anchors.fill: parent
anchors.margins: 2
color: {
if (inputConnectMA.containsMouse || inputConnectMA.drag.active || (inputDropArea.containsDrag && inputDropArea.acceptableDrop))
return Colors.sysPalette.highlight
return Colors.sysPalette.text
}
}
DropArea {
id: inputDropArea
property bool acceptableDrop: false
// add negative margins for DropArea to make the connection zone easier to reach
anchors.fill: parent
anchors.margins: -2
// add horizontal negative margins according to the current layout
anchors.rightMargin: -root.width * 0.3
keys: [inputDragTarget.objectName]
onEntered: {
// Check if attributes are compatible to create a valid connection
if (root.readOnly // cannot connect on a read-only attribute
|| drag.source.objectName != inputDragTarget.objectName // not an edge connector
|| drag.source.baseType !== inputDragTarget.baseType // not the same base type
|| drag.source.nodeItem === inputDragTarget.nodeItem // connection between attributes of the same node
|| (drag.source.isList && !inputDragTarget.isList) // connection between a list and a simple attribute
|| (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children
|| drag.source.connectorType === "input" // refuse to connect an "input pin" on another one (input attr can be connected to input attr, but not the graphical pin)
) {
// Refuse attributes connection
drag.accepted = false
} else if (inputDragTarget.attribute.isLink) { // already connected attribute
root.edgeAboutToBeRemoved(inputDragTarget.attribute)
}
inputDropArea.acceptableDrop = drag.accepted
}
onExited: {
if (inputDragTarget.attribute.isLink) { // already connected attribute
root.edgeAboutToBeRemoved(undefined)
}
acceptableDrop = false
drag.source.dropAccepted = false
}
onDropped: {
root.edgeAboutToBeRemoved(undefined)
_reconstruction.addEdge(drag.source.attribute, inputDragTarget.attribute)
}
}
Item {
id: inputDragTarget
objectName: "edgeConnector"
readonly property string connectorType: "input"
readonly property alias attribute: root.attribute
readonly property alias nodeItem: root.nodeItem
readonly property bool isOutput: attribute.isOutput
readonly property string baseType: attribute.baseType
readonly property alias isList: root.isList
property bool dragAccepted: false
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: parent.height
Drag.keys: [inputDragTarget.objectName]
Drag.active: inputConnectMA.drag.active
Drag.hotSpot.x: width * 0.5
Drag.hotSpot.y: height * 0.5
}
MouseArea {
id: inputConnectMA
drag.target: attribute.isReadOnly ? undefined : inputDragTarget
drag.threshold: 0
// Move the edge's tip straight to the the current mouse position instead of waiting after the drag operation has started
drag.smoothed: false
enabled: !root.readOnly
anchors.fill: parent
// use the same negative margins as DropArea to ease pin selection
anchors.margins: inputDropArea.anchors.margins
anchors.leftMargin: inputDropArea.anchors.leftMargin
anchors.rightMargin: inputDropArea.anchors.rightMargin
onPressed: {
root.pressed(mouse)
}
onReleased: {
inputDragTarget.Drag.drop()
}
hoverEnabled: true
}
Edge {
id: inputConnectEdge
visible: false
point1x: inputDragTarget.x + inputDragTarget.width / 2
point1y: inputDragTarget.y + inputDragTarget.height / 2
point2x: parent.width / 2
point2y: parent.width / 2
color: palette.highlight
thickness: outputDragTarget.dropAccepted ? 2 : 1
}
}
// Attribute name
Item {
id: nameContainer
Layout.fillWidth: true
implicitHeight: childrenRect.height
Layout.alignment: Qt.AlignVCenter
Label {
id: nameLabel
enabled: !root.readOnly
property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active || inputDropArea.containsDrag || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag)
text: attribute ? attribute.label : ""
elide: hovered ? Text.ElideNone : Text.ElideMiddle
width: hovered ? contentWidth : parent.width
font.pointSize: 7
horizontalAlignment: attribute && attribute.isOutput ? Text.AlignRight : Text.AlignLeft
anchors.right: attribute && attribute.isOutput ? parent.right : undefined
rightPadding: 0
color: hovered ? palette.highlight : palette.text
}
}
Rectangle {
id: outputAnchor
visible: displayOutputPinForInput || attribute.isOutput
width: 8
height: width
radius: isList ? 0 : width / 2
Layout.alignment: Qt.AlignVCenter
border.color: Colors.sysPalette.mid
color: Colors.sysPalette.base
Rectangle {
id: innerOutputAnchor
property bool linkEnabled: true
visible: (attribute.hasOutputConnections && linkEnabled) || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag
radius: isList ? 0 : 2
anchors.fill: parent
anchors.margins: 2
color: {
if (outputConnectMA.containsMouse || outputConnectMA.drag.active || (outputDropArea.containsDrag && outputDropArea.acceptableDrop))
return Colors.sysPalette.highlight
return Colors.sysPalette.text
}
}
DropArea {
id: outputDropArea
property bool acceptableDrop: false
// add negative margins for DropArea to make the connection zone easier to reach
anchors.fill: parent
anchors.margins: -2
// add horizontal negative margins according to the current layout
anchors.leftMargin: -root.width * 0.2
keys: [outputDragTarget.objectName]
onEntered: {
// Check if attributes are compatible to create a valid connection
if (drag.source.objectName != outputDragTarget.objectName // not an edge connector
|| drag.source.baseType !== outputDragTarget.baseType // not the same base type
|| drag.source.nodeItem === outputDragTarget.nodeItem // connection between attributes of the same node
|| (!drag.source.isList && outputDragTarget.isList) // connection between a list and a simple attribute
|| (drag.source.isList && childrenRepeater.count) // source/target are lists but target already has children
|| drag.source.connectorType === "output" // refuse to connect an output pin on another one
) {
// Refuse attributes connection
drag.accepted = false
} else if (drag.source.attribute.isLink) { // already connected attribute
root.edgeAboutToBeRemoved(drag.source.attribute)
}
outputDropArea.acceptableDrop = drag.accepted
}
onExited: {
root.edgeAboutToBeRemoved(undefined)
acceptableDrop = false
}
onDropped: {
root.edgeAboutToBeRemoved(undefined)
_reconstruction.addEdge(outputDragTarget.attribute, drag.source.attribute)
}
}
Item {
id: outputDragTarget
objectName: "edgeConnector"
readonly property string connectorType: "output"
readonly property alias attribute: root.attribute
readonly property alias nodeItem: root.nodeItem
readonly property bool isOutput: attribute.isOutput
readonly property alias isList: root.isList
readonly property string baseType: attribute.baseType
property bool dropAccepted: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: parent.height
Drag.keys: [outputDragTarget.objectName]
Drag.active: outputConnectMA.drag.active
Drag.hotSpot.x: width * 0.5
Drag.hotSpot.y: height * 0.5
}
MouseArea {
id: outputConnectMA
drag.target: outputDragTarget
drag.threshold: 0
// Move the edge's tip straight to the the current mouse position instead of waiting after the drag operation has started
drag.smoothed: false
anchors.fill: parent
// use the same negative margins as DropArea to ease pin selection
anchors.margins: outputDropArea.anchors.margins
anchors.leftMargin: outputDropArea.anchors.leftMargin
anchors.rightMargin: outputDropArea.anchors.rightMargin
onPressed: root.pressed(mouse)
onReleased: outputDragTarget.Drag.drop()
hoverEnabled: true
}
Edge {
id: outputConnectEdge
visible: false
point1x: parent.width / 2
point1y: parent.width / 2
point2x: outputDragTarget.x + outputDragTarget.width / 2
point2y: outputDragTarget.y + outputDragTarget.height / 2
color: palette.highlight
thickness: outputDragTarget.dropAccepted ? 2 : 1
}
}
state: (inputConnectMA.pressed) ? "DraggingInput" : outputConnectMA.pressed ? "DraggingOutput" : ""
states: [
State {
name: ""
AnchorChanges {
target: outputDragTarget
anchors.horizontalCenter: outputAnchor.horizontalCenter
anchors.verticalCenter: outputAnchor.verticalCenter
}
AnchorChanges {
target: inputDragTarget
anchors.horizontalCenter: inputAnchor.horizontalCenter
anchors.verticalCenter: inputAnchor.verticalCenter
}
PropertyChanges {
target: inputDragTarget
x: 0
y: 0
}
PropertyChanges {
target: outputDragTarget
x: 0
y: 0
}
},
State {
name: "DraggingInput"
AnchorChanges {
target: inputDragTarget
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
PropertyChanges {
target: inputConnectEdge
z: 100
visible: true
}
StateChangeScript {
script: {
// Add the right offset if the initial click is not exactly at the center of the connection circle.
var pos = inputDragTarget.mapFromItem(inputConnectMA, inputConnectMA.mouseX, inputConnectMA.mouseY);
inputDragTarget.x = pos.x - inputDragTarget.width / 2;
inputDragTarget.y = pos.y - inputDragTarget.height / 2;
}
}
},
State {
name: "DraggingOutput"
AnchorChanges {
target: outputDragTarget
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
PropertyChanges {
target: outputConnectEdge
z: 100
visible: true
}
StateChangeScript {
script: {
var pos = outputDragTarget.mapFromItem(outputConnectMA, outputConnectMA.mouseX, outputConnectMA.mouseY);
outputDragTarget.x = pos.x - outputDragTarget.width / 2;
outputDragTarget.y = pos.y - outputDragTarget.height / 2;
}
}
}
]
}