Skip to content

Commit

Permalink
[GraphEditor] Allow to display and connect attributes within `GroupAt…
Browse files Browse the repository at this point in the history
…tribute`
  • Loading branch information
cbentejac committed Sep 26, 2024
1 parent 06b86eb commit 7592b3a
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 26 deletions.
38 changes: 28 additions & 10 deletions meshroom/ui/qml/GraphEditor/AttributePin.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RowLayout {

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
Expand All @@ -24,13 +25,16 @@ RowLayout {
outputAnchor.y + outputAnchor.height / 2)

readonly property bool isList: attribute && attribute.type === "ListAttribute"
readonly property bool isGroup: attribute && attribute.type === "GroupAttribute"

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
Expand All @@ -43,14 +47,30 @@ RowLayout {
x: nameLabel.x
}

function updatePin(isSrc, isVisible)
{
function updatePin(isSrc, isVisible) {
if (isSrc) {
innerOutputAnchor.linkEnabled = isVisible
} else {
innerInputAnchor.linkEnabled = isVisible
}
}

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
}

onExpandedChanged: {
nameLabel.text = updateLabel()
}

// Instantiate empty Items for each child attribute
Expand Down Expand Up @@ -164,12 +184,9 @@ RowLayout {
anchors.margins: inputDropArea.anchors.margins
anchors.leftMargin: inputDropArea.anchors.leftMargin
anchors.rightMargin: inputDropArea.anchors.rightMargin
onPressed: {
root.pressed(mouse)
}
onReleased: {
inputDragTarget.Drag.drop()
}
onPressed: root.pressed(mouse)
onReleased: inputDragTarget.Drag.drop()
onClicked: root.clicked()
hoverEnabled: true
}

Expand All @@ -186,7 +203,6 @@ RowLayout {
}



// Attribute name
Item {
id: nameContainer
Expand All @@ -199,8 +215,9 @@ RowLayout {
id: nameLabel

enabled: !root.readOnly
visible: true
property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active || inputDropArea.containsDrag || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag)
text: (attribute && attribute.label) !== undefined ? attribute.label : ""
text: root.updateLabel()
elide: hovered ? Text.ElideNone : Text.ElideMiddle
width: hovered ? contentWidth : parent.width
font.pointSize: 7
Expand Down Expand Up @@ -315,6 +332,7 @@ RowLayout {

onPressed: root.pressed(mouse)
onReleased: outputDragTarget.Drag.drop()
onClicked: root.clicked()

hoverEnabled: true
}
Expand Down
167 changes: 151 additions & 16 deletions meshroom/ui/qml/GraphEditor/Node.qml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,54 @@ Item {
return str
}

function updateChildPin(attribute, parentPins, pin) {
/*
* Update the pin of a child attribute: if the attribute is enabled and its parent is a GroupAttribute,
* the visibility is determined based on the parent pin's "expanded" state, using the "parentPins" map to
* access the status.
* If the current pin is also a GroupAttribute and is expanded while its newly "visible" state is false,
* it is reset.
*/
if (Boolean(attribute.enabled)) {
// If the parent's a GroupAttribute, use status of the parent's pin to determine visibility
if (attribute.root && attribute.root.type === "GroupAttribute") {
var visible = Boolean(parentPins.get(attribute.root.name))
if (!visible && parentPins.has(attribute.name) && parentPins.get(attribute.name) === true) {
parentPins.set(attribute.name, false)
pin.expanded = false
}
return visible
}
return true
}
return false
}

function generateAttributesModel(isOutput, parentPins) {
if (!node)
return undefined

const attributes = []
for (let i = 0; i < node.attributes.count; ++i) {
let attr = node.attributes.at(i)
if (attr.isOutput == isOutput) {
attributes.push(attr)
if (attr.type === "GroupAttribute") {
parentPins.set(attr.name, false)
}

for (let j = 0; j < attr.flattenedChildren.count; ++j) {
attributes.push(attr.flattenedChildren.at(j))
if (attr.flattenedChildren.at(j).type === "GroupAttribute") {
parentPins.set(attr.flattenedChildren.at(j).name, false)
}
}
}
}

return attributes
}

// Main Layout
MouseArea {
id: mouseArea
Expand Down Expand Up @@ -394,25 +442,52 @@ Item {
width: parent.width
spacing: 3

property var parentPins: new Map()
signal parentPinsUpdated()

Repeater {
model: node ? node.attributes : undefined
model: generateAttributesModel(true, outputs.parentPins) // isOutput = true

delegate: Loader {
id: outputLoader
active: Boolean(object.isOutput && object.desc.visible)
visible: Boolean(object.enabled || object.hasOutputConnections)
active: Boolean(modelData.isOutput && modelData.desc.visible)

visible: {
if (Boolean(modelData.enabled || modelData.hasOutputConnections)) {
if (modelData.root && modelData.root.type === "GroupAttribute") {
return Boolean(outputs.parentPins.get(modelData.root.name))
}
return true
}
return false
}
anchors.right: parent.right
width: outputs.width

Connections {
target: outputs

function onParentPinsUpdated() {
visible = updateChildPin(modelData, outputs.parentPins, outputLoader.item)
}
}

sourceComponent: AttributePin {
id: outPin
nodeItem: root
attribute: object
attribute: modelData

property real globalX: root.x + nodeAttributes.x + outputs.x + outputLoader.x + outPin.x
property real globalY: root.y + nodeAttributes.y + outputs.y + outputLoader.y + outPin.y

onPressed: root.pressed(mouse)
onClicked: {
expanded = !expanded
if (outputs.parentPins.has(modelData.name)) {
outputs.parentPins.set(modelData.name, expanded)
outputs.parentPinsUpdated()
}
}
onEdgeAboutToBeRemoved: root.edgeAboutToBeRemoved(input)

Component.onCompleted: attributePinCreated(attribute, outPin)
Expand All @@ -429,27 +504,55 @@ Item {
width: parent.width
spacing: 3

property var parentPins: new Map()
signal parentPinsUpdated()

Repeater {
model: node ? node.attributes : undefined
model: generateAttributesModel(false, inputs.parentPins) // isOutput = false

delegate: Loader {
id: inputLoader
active: !object.isOutput && object.exposed && object.desc.visible
visible: Boolean(object.enabled)
active: !modelData.isOutput && modelData.exposed && modelData.desc.visible
visible: {
if (Boolean(modelData.enabled)) {
if (modelData.root && modelData.root.type === "GroupAttribute") {
return Boolean(inputs.parentPins.get(modelData.root.name))
}
return true
}
return false
}
width: inputs.width

Connections {
target: inputs

function onParentPinsUpdated() {
visible = updateChildPin(modelData, inputs.parentPins, inputLoader.item)
}
}

sourceComponent: AttributePin {
id: inPin
nodeItem: root
attribute: object
attribute: modelData

property real globalX: root.x + nodeAttributes.x + inputs.x + inputLoader.x + inPin.x
property real globalY: root.y + nodeAttributes.y + inputs.y + inputLoader.y + inPin.y

readOnly: root.readOnly || object.isReadOnly
readOnly: Boolean(root.readOnly || modelData.isReadOnly)
Component.onCompleted: attributePinCreated(attribute, inPin)
Component.onDestruction: attributePinDeleted(attribute, inPin)

onPressed: root.pressed(mouse)
onClicked: {
expanded = !expanded
if (inputs.parentPins.has(modelData.name)) {
inputs.parentPins.set(modelData.name, expanded)
inputs.parentPinsUpdated()
}
}

onEdgeAboutToBeRemoved: root.edgeAboutToBeRemoved(input)
onChildPinCreated: attributePinCreated(childAttribute, inPin)
onChildPinDeleted: attributePinDeleted(childAttribute, inPin)
Expand Down Expand Up @@ -489,30 +592,62 @@ Item {
id: inputParams
width: parent.width
spacing: 3

property var parentPins: new Map()
signal parentPinsUpdated()

Repeater {
id: inputParamsRepeater
model: node ? node.attributes : undefined
model: generateAttributesModel(false, inputParams.parentPins) // isOutput = false

delegate: Loader {
id: paramLoader
active: !object.isOutput && !object.exposed && object.desc.visible
visible: Boolean(object.enabled || object.isLink || object.hasOutputConnections)
property bool isFullyActive: Boolean(m.displayParams || object.isLink || object.hasOutputConnections)
active: !modelData.isOutput && !modelData.exposed && modelData.desc.visible
visible: {
if (Boolean(modelData.enabled || modelData.isLink || modelData.hasOutputConnections)) {
if (modelData.root && modelData.root.type === "GroupAttribute") {
return Boolean(inputParams.parentPins.get(modelData.root.name))
active: !modelData.isOutput && modelData.desc.exposed && modelData.desc.visible }
return true
}
return false
}
property bool isFullyActive: Boolean(m.displayParams || modelData.isLink || modelData.hasOutputConnections)
width: parent.width

Connections {
target: inputParams

function onParentPinsUpdated() {
visible = updateChildPin(modelData, inputParams.parentPins, paramLoader.item)
}
}

sourceComponent: AttributePin {
id: inParamsPin
nodeItem: root
attribute: modelData

property real globalX: root.x + nodeAttributes.x + inputParamsRect.x + paramLoader.x + inParamsPin.x
property real globalY: root.y + nodeAttributes.y + inputParamsRect.y + paramLoader.y + inParamsPin.y

height: isFullyActive ? childrenRect.height : 0
Behavior on height { PropertyAnimation {easing.type: Easing.Linear} }
visible: (height == childrenRect.height)
attribute: object
readOnly: Boolean(root.readOnly || object.isReadOnly)

readOnly: Boolean(root.readOnly || modelData.isReadOnly)
Component.onCompleted: attributePinCreated(attribute, inParamsPin)
Component.onDestruction: attributePinDeleted(attribute, inParamsPin)

onPressed: root.pressed(mouse)

onClicked: {
expanded = !expanded
if (inputParams.parentPins.has(modelData.name)) {
inputParams.parentPins.set(modelData.name, expanded)
inputParams.parentPinsUpdated()
}
}

onEdgeAboutToBeRemoved: root.edgeAboutToBeRemoved(input)
onChildPinCreated: attributePinCreated(childAttribute, inParamsPin)
onChildPinDeleted: attributePinDeleted(childAttribute, inParamsPin)
Expand Down

0 comments on commit 7592b3a

Please sign in to comment.