mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-29 18:27:23 +02:00
As connections between groups are currently not supported, add connecting any `GroupAttribute` to the list of connections to refuse.
462 lines
18 KiB
QML
Executable file
462 lines
18 KiB
QML
Executable file
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
|
|
import Utils 1.0
|
|
import MaterialIcons 2.2
|
|
|
|
/**
|
|
* The representation of an Attribute on a Node.
|
|
*/
|
|
|
|
RowLayout {
|
|
id: root
|
|
|
|
property var nodeItem
|
|
property var attribute
|
|
property bool expanded: false
|
|
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"
|
|
readonly property bool isGroup: attribute && attribute.type === "GroupAttribute"
|
|
readonly property bool isChild: attribute && attribute.root
|
|
readonly property bool isConnected: attribute.isLinkNested || attribute.hasOutputConnections
|
|
|
|
signal childPinCreated(var childAttribute, var pin)
|
|
signal childPinDeleted(var childAttribute, var pin)
|
|
|
|
signal pressed(var mouse)
|
|
signal edgeAboutToBeRemoved(var input)
|
|
signal clicked()
|
|
|
|
objectName: attribute ? attribute.name + "." : ""
|
|
layoutDirection: Qt.LeftToRight
|
|
spacing: 3
|
|
|
|
ToolTip {
|
|
text: attribute.fullName + ": " + attribute.type
|
|
visible: nameLabel.hovered
|
|
delay: 500
|
|
|
|
y: nameLabel.y + nameLabel.height
|
|
x: nameLabel.x
|
|
}
|
|
|
|
function updateLabel() {
|
|
var label = ""
|
|
var expandedGroup = expanded ? "-" : "+"
|
|
if (attribute && attribute.label !== undefined) {
|
|
label = attribute.label
|
|
if (isGroup && attribute.isOutput) {
|
|
label = label + " " + expandedGroup
|
|
} else if (isGroup && !attribute.isOutput) {
|
|
label = expandedGroup + " " + label
|
|
}
|
|
}
|
|
return label
|
|
}
|
|
|
|
// Instantiate empty Items for each child attribute
|
|
Repeater {
|
|
id: childrenRepeater
|
|
model: root.isList && !root.attribute.isLink ? root.attribute.value : 0
|
|
onItemAdded: function(index, item) { childPinCreated(item.childAttribute, root) }
|
|
onItemRemoved: function(index, item) { childPinDeleted(item.childAttribute, root) }
|
|
delegate: Item {
|
|
property var childAttribute: object
|
|
visible: false
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: !root.attribute.isOutput
|
|
id: inputAnchor
|
|
|
|
width: 8
|
|
height: width
|
|
radius: root.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 || (root.attribute && root.attribute.isLink && linkEnabled) || inputConnectMA.drag.active || inputDropArea.containsDrag
|
|
radius: root.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: function(drag) {
|
|
// 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 && 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)
|
|
|| (drag.source.isGroup || inputDragTarget.isGroup) // Refuse connection between Groups, which is unsupported
|
|
) {
|
|
// 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: function(drop) {
|
|
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: Boolean(attribute.isOutput)
|
|
readonly property string baseType: attribute.baseType !== undefined ? attribute.baseType : ""
|
|
readonly property alias isList: root.isList
|
|
readonly property alias isGroup: root.isGroup
|
|
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: root.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: function(mouse) {
|
|
root.pressed(mouse)
|
|
}
|
|
onReleased: {
|
|
inputDragTarget.Drag.drop()
|
|
}
|
|
onClicked: root.clicked()
|
|
hoverEnabled: root.visible
|
|
}
|
|
|
|
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
|
|
implicitHeight: childrenRect.height
|
|
Layout.fillWidth: true
|
|
Layout.alignment: {
|
|
if (root.attribute.isOutput) {
|
|
return Qt.AlignRight | Qt.AlignVCenter
|
|
}
|
|
return Qt.AlignLeft | Qt.AlignVCenter
|
|
}
|
|
|
|
MaterialToolLabel {
|
|
id: nameLabel
|
|
|
|
anchors.rightMargin: 0
|
|
anchors.right: root.attribute && root.attribute.isOutput ? parent.right : undefined
|
|
labelIconRow.layoutDirection: root.attribute.isOutput ? Qt.RightToLeft : Qt.LeftToRight
|
|
labelIconRow.spacing: 0
|
|
|
|
enabled: !root.readOnly
|
|
visible: true
|
|
property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active ||
|
|
inputDropArea.containsDrag || outputConnectMA.containsMouse ||
|
|
outputConnectMA.drag.active || outputDropArea.containsDrag)
|
|
|
|
labelIconColor: {
|
|
if ((root.attribute.hasOutputConnections || root.attribute.isLink) && !root.attribute.enabled) {
|
|
return Colors.lightgrey
|
|
} else if (hovered) {
|
|
return palette.highlight
|
|
}
|
|
return palette.text
|
|
}
|
|
labelIconMouseArea.enabled: false // Prevent mixing mouse interactions between the label and the pin context
|
|
|
|
// Text
|
|
label.text: root.attribute.label
|
|
label.font.pointSize: 7
|
|
label.elide: hovered ? Text.ElideNone : Text.ElideMiddle
|
|
label.horizontalAlignment: root.attribute && root.attribute.isOutput ? Text.AlignRight : Text.AlignLeft
|
|
|
|
// Icon
|
|
iconText: {
|
|
if (root.isGroup) {
|
|
return root.expanded ? MaterialIcons.expand_more : MaterialIcons.chevron_right
|
|
}
|
|
return ""
|
|
}
|
|
iconSize: 7
|
|
icon.horizontalAlignment: root.attribute && root.attribute.isOutput ? Text.AlignRight : Text.AlignLeft
|
|
|
|
// Handle tree view for nested attributes
|
|
icon.leftPadding: {
|
|
if (root.attribute.depth != 0 && !root.attribute.isOutput) {
|
|
return root.attribute.depth * 10
|
|
}
|
|
return 0
|
|
}
|
|
icon.rightPadding: {
|
|
if (root.attribute.depth != 0 && root.attribute.isOutput) {
|
|
return root.attribute.depth * 10
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: outputAnchor
|
|
|
|
visible: root.displayOutputPinForInput || root.attribute.isOutput
|
|
width: 8
|
|
height: width
|
|
radius: root.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: (root.attribute.hasOutputConnections && linkEnabled) || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag
|
|
radius: root.isList ? 0 : 2
|
|
anchors.fill: parent
|
|
anchors.margins: 2
|
|
color: {
|
|
if (modelData.enabled && (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: function(drag) {
|
|
// 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
|
|
|| (drag.source.isGroup || outputDragTarget.isGroup) // Refuse connection between Groups, which is unsupported
|
|
) {
|
|
// 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: function(drop) {
|
|
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: Boolean(attribute.isOutput)
|
|
readonly property alias isList: root.isList
|
|
readonly property alias isGroup: root.isGroup
|
|
readonly property string baseType: root.attribute.baseType !== undefined ? 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: function(mouse) { root.pressed(mouse) }
|
|
onReleased: outputDragTarget.Drag.drop()
|
|
onClicked: root.clicked()
|
|
|
|
hoverEnabled: root.visible
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|