Skip to content

Commit e3acac4

Browse files
committed
[GraphEditor] Allow to display and connect attributes within GroupAttribute
1 parent b67727b commit e3acac4

File tree

2 files changed

+194
-34
lines changed

2 files changed

+194
-34
lines changed

meshroom/ui/qml/GraphEditor/AttributePin.qml

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ RowLayout {
1212

1313
property var nodeItem
1414
property var attribute
15+
property bool expanded: false
1516
property bool readOnly: false
1617
/// Whether to display an output pin for input attribute
1718
property bool displayOutputPinForInput: true
@@ -24,13 +25,17 @@ RowLayout {
2425
outputAnchor.y + outputAnchor.height / 2)
2526

2627
readonly property bool isList: attribute && attribute.type === "ListAttribute"
28+
readonly property bool isGroup: attribute && attribute.type === "GroupAttribute"
29+
readonly property bool isChild: attribute && attribute.root
2730

2831
signal childPinCreated(var childAttribute, var pin)
2932
signal childPinDeleted(var childAttribute, var pin)
3033

3134
signal pressed(var mouse)
3235
signal edgeAboutToBeRemoved(var input)
3336

37+
signal clicked()
38+
3439
objectName: attribute ? attribute.name + "." : ""
3540
layoutDirection: Qt.LeftToRight
3641
spacing: 3
@@ -43,14 +48,30 @@ RowLayout {
4348
x: nameLabel.x
4449
}
4550

46-
function updatePin(isSrc, isVisible)
47-
{
51+
function updatePin(isSrc, isVisible) {
4852
if (isSrc) {
4953
innerOutputAnchor.linkEnabled = isVisible
5054
} else {
5155
innerInputAnchor.linkEnabled = isVisible
5256
}
57+
}
58+
59+
function updateLabel() {
60+
var label = ""
61+
var expandedGroup = expanded ? "-" : "+"
62+
if (attribute && attribute.label !== undefined) {
63+
label = attribute.label
64+
if (isGroup && attribute.isOutput) {
65+
label = label + " " + expandedGroup
66+
} else if (isGroup && !attribute.isOutput) {
67+
label = expandedGroup + " " + label
68+
}
69+
}
70+
return label
71+
}
5372

73+
onExpandedChanged: {
74+
nameLabel.text = updateLabel()
5475
}
5576

5677
// Instantiate empty Items for each child attribute
@@ -160,17 +181,16 @@ RowLayout {
160181
drag.smoothed: false
161182
enabled: !root.readOnly
162183
anchors.fill: parent
163-
// use the same negative margins as DropArea to ease pin selection
184+
// Use the same negative margins as DropArea to ease pin selection
164185
anchors.margins: inputDropArea.anchors.margins
165186
anchors.leftMargin: inputDropArea.anchors.leftMargin
166187
anchors.rightMargin: inputDropArea.anchors.rightMargin
167-
onPressed: {
168-
root.pressed(mouse)
169-
}
170-
onReleased: {
171-
inputDragTarget.Drag.drop()
172-
}
173-
hoverEnabled: true
188+
189+
onPressed: root.pressed(mouse)
190+
onReleased: inputDragTarget.Drag.drop()
191+
onClicked: root.clicked()
192+
193+
hoverEnabled: root.visible
174194
}
175195

176196
Edge {
@@ -186,7 +206,6 @@ RowLayout {
186206
}
187207

188208

189-
190209
// Attribute name
191210
Item {
192211
id: nameContainer
@@ -199,17 +218,22 @@ RowLayout {
199218
id: nameLabel
200219

201220
enabled: !root.readOnly
221+
visible: true
202222
property bool hovered: (inputConnectMA.containsMouse || inputConnectMA.drag.active || inputDropArea.containsDrag || outputConnectMA.containsMouse || outputConnectMA.drag.active || outputDropArea.containsDrag)
203-
text: (attribute && attribute.label) !== undefined ? attribute.label : ""
223+
text: root.updateLabel()
204224
elide: hovered ? Text.ElideNone : Text.ElideMiddle
205225
width: hovered ? contentWidth : parent.width
206226
font.pointSize: 7
227+
font.italic: isChild ? true : false
207228
horizontalAlignment: attribute && attribute.isOutput ? Text.AlignRight : Text.AlignLeft
208229
anchors.right: attribute && attribute.isOutput ? parent.right : undefined
209230
rightPadding: 0
210231
color: {
211-
if ((object.hasOutputConnections || object.isLink) && !object.enabled) return Colors.lightgrey
212-
return hovered ? palette.highlight : palette.text
232+
if ((object.hasOutputConnections || object.isLink) && !object.enabled)
233+
return Colors.lightgrey
234+
else if (hovered)
235+
return palette.highlight
236+
return palette.text
213237
}
214238
}
215239
}
@@ -236,8 +260,8 @@ RowLayout {
236260
anchors.fill: parent
237261
anchors.margins: 2
238262
color: {
239-
if (object.enabled && (outputConnectMA.containsMouse || outputConnectMA.drag.active ||
240-
(outputDropArea.containsDrag && outputDropArea.acceptableDrop)))
263+
if (modelData.enabled && (outputConnectMA.containsMouse || outputConnectMA.drag.active ||
264+
(outputDropArea.containsDrag && outputDropArea.acceptableDrop)))
241265
return Colors.sysPalette.highlight
242266
return Colors.sysPalette.text
243267
}
@@ -309,15 +333,16 @@ RowLayout {
309333
// Move the edge's tip straight to the the current mouse position instead of waiting after the drag operation has started
310334
drag.smoothed: false
311335
anchors.fill: parent
312-
// use the same negative margins as DropArea to ease pin selection
336+
// Use the same negative margins as DropArea to ease pin selection
313337
anchors.margins: outputDropArea.anchors.margins
314338
anchors.leftMargin: outputDropArea.anchors.leftMargin
315339
anchors.rightMargin: outputDropArea.anchors.rightMargin
316340

317341
onPressed: root.pressed(mouse)
318342
onReleased: outputDragTarget.Drag.drop()
343+
onClicked: root.clicked()
319344

320-
hoverEnabled: true
345+
hoverEnabled: root.visible
321346
}
322347

323348
Edge {

meshroom/ui/qml/GraphEditor/Node.qml

Lines changed: 151 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,54 @@ Item {
115115
return str
116116
}
117117

118+
function updateChildPin(attribute, parentPins, pin) {
119+
/*
120+
* Update the pin of a child attribute: if the attribute is enabled and its parent is a GroupAttribute,
121+
* the visibility is determined based on the parent pin's "expanded" state, using the "parentPins" map to
122+
* access the status.
123+
* If the current pin is also a GroupAttribute and is expanded while its newly "visible" state is false,
124+
* it is reset.
125+
*/
126+
if (Boolean(attribute.enabled)) {
127+
// If the parent's a GroupAttribute, use status of the parent's pin to determine visibility
128+
if (attribute.root && attribute.root.type === "GroupAttribute") {
129+
var visible = Boolean(parentPins.get(attribute.root.name))
130+
if (!visible && parentPins.has(attribute.name) && parentPins.get(attribute.name) === true) {
131+
parentPins.set(attribute.name, false)
132+
pin.expanded = false
133+
}
134+
return visible
135+
}
136+
return true
137+
}
138+
return false
139+
}
140+
141+
function generateAttributesModel(isOutput, parentPins) {
142+
if (!node)
143+
return undefined
144+
145+
const attributes = []
146+
for (let i = 0; i < node.attributes.count; ++i) {
147+
let attr = node.attributes.at(i)
148+
if (attr.isOutput == isOutput) {
149+
attributes.push(attr)
150+
if (attr.type === "GroupAttribute") {
151+
parentPins.set(attr.name, false)
152+
}
153+
154+
for (let j = 0; j < attr.flattenedChildren.count; ++j) {
155+
attributes.push(attr.flattenedChildren.at(j))
156+
if (attr.flattenedChildren.at(j).type === "GroupAttribute") {
157+
parentPins.set(attr.flattenedChildren.at(j).name, false)
158+
}
159+
}
160+
}
161+
}
162+
163+
return attributes
164+
}
165+
118166
// Main Layout
119167
MouseArea {
120168
id: mouseArea
@@ -394,25 +442,52 @@ Item {
394442
width: parent.width
395443
spacing: 3
396444

445+
property var parentPins: new Map()
446+
signal parentPinsUpdated()
447+
397448
Repeater {
398-
model: node ? node.attributes : undefined
449+
model: generateAttributesModel(true, outputs.parentPins) // isOutput = true
399450

400451
delegate: Loader {
401452
id: outputLoader
402-
active: Boolean(object.isOutput && object.desc.visible)
403-
visible: Boolean(object.enabled || object.hasOutputConnections)
453+
active: Boolean(modelData.isOutput && modelData.desc.visible)
454+
455+
visible: {
456+
if (Boolean(modelData.enabled || modelData.hasOutputConnections)) {
457+
if (modelData.root && modelData.root.type === "GroupAttribute") {
458+
return Boolean(outputs.parentPins.get(modelData.root.name))
459+
}
460+
return true
461+
}
462+
return false
463+
}
404464
anchors.right: parent.right
405465
width: outputs.width
406466

467+
Connections {
468+
target: outputs
469+
470+
function onParentPinsUpdated() {
471+
visible = updateChildPin(modelData, outputs.parentPins, outputLoader.item)
472+
}
473+
}
474+
407475
sourceComponent: AttributePin {
408476
id: outPin
409477
nodeItem: root
410-
attribute: object
478+
attribute: modelData
411479

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

415483
onPressed: root.pressed(mouse)
484+
onClicked: {
485+
expanded = !expanded
486+
if (outputs.parentPins.has(modelData.name)) {
487+
outputs.parentPins.set(modelData.name, expanded)
488+
outputs.parentPinsUpdated()
489+
}
490+
}
416491
onEdgeAboutToBeRemoved: root.edgeAboutToBeRemoved(input)
417492

418493
Component.onCompleted: attributePinCreated(attribute, outPin)
@@ -429,27 +504,55 @@ Item {
429504
width: parent.width
430505
spacing: 3
431506

507+
property var parentPins: new Map()
508+
signal parentPinsUpdated()
509+
432510
Repeater {
433-
model: node ? node.attributes : undefined
511+
model: generateAttributesModel(false, inputs.parentPins) // isOutput = false
434512

435513
delegate: Loader {
436514
id: inputLoader
437-
active: !object.isOutput && object.exposed && object.desc.visible
438-
visible: Boolean(object.enabled)
515+
active: !modelData.isOutput && modelData.exposed && modelData.desc.visible
516+
visible: {
517+
if (Boolean(modelData.enabled)) {
518+
if (modelData.root && modelData.root.type === "GroupAttribute") {
519+
return Boolean(inputs.parentPins.get(modelData.root.name))
520+
}
521+
return true
522+
}
523+
return false
524+
}
439525
width: inputs.width
440526

527+
Connections {
528+
target: inputs
529+
530+
function onParentPinsUpdated() {
531+
visible = updateChildPin(modelData, inputs.parentPins, inputLoader.item)
532+
}
533+
}
534+
441535
sourceComponent: AttributePin {
442536
id: inPin
443537
nodeItem: root
444-
attribute: object
538+
attribute: modelData
445539

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

449-
readOnly: root.readOnly || object.isReadOnly
543+
readOnly: Boolean(root.readOnly || modelData.isReadOnly)
450544
Component.onCompleted: attributePinCreated(attribute, inPin)
451545
Component.onDestruction: attributePinDeleted(attribute, inPin)
546+
452547
onPressed: root.pressed(mouse)
548+
onClicked: {
549+
expanded = !expanded
550+
if (inputs.parentPins.has(modelData.name)) {
551+
inputs.parentPins.set(modelData.name, expanded)
552+
inputs.parentPinsUpdated()
553+
}
554+
}
555+
453556
onEdgeAboutToBeRemoved: root.edgeAboutToBeRemoved(input)
454557
onChildPinCreated: attributePinCreated(childAttribute, inPin)
455558
onChildPinDeleted: attributePinDeleted(childAttribute, inPin)
@@ -489,30 +592,62 @@ Item {
489592
id: inputParams
490593
width: parent.width
491594
spacing: 3
595+
596+
property var parentPins: new Map()
597+
signal parentPinsUpdated()
598+
492599
Repeater {
493-
id: inputParamsRepeater
494-
model: node ? node.attributes : undefined
600+
model: generateAttributesModel(false, inputParams.parentPins) // isOutput = false
601+
495602
delegate: Loader {
496603
id: paramLoader
497-
active: !object.isOutput && !object.exposed && object.desc.visible
498-
visible: Boolean(object.enabled || object.isLink || object.hasOutputConnections)
499-
property bool isFullyActive: Boolean(m.displayParams || object.isLink || object.hasOutputConnections)
604+
active: !modelData.isOutput && !modelData.exposed && modelData.desc.visible
605+
visible: {
606+
if (Boolean(modelData.enabled || modelData.isLink || modelData.hasOutputConnections)) {
607+
if (modelData.root && modelData.root.type === "GroupAttribute") {
608+
return Boolean(inputParams.parentPins.get(modelData.root.name))
609+
active: !modelData.isOutput && modelData.desc.exposed && modelData.desc.visible }
610+
return true
611+
}
612+
return false
613+
}
614+
property bool isFullyActive: Boolean(m.displayParams || modelData.isLink || modelData.hasOutputConnections)
500615
width: parent.width
501616

617+
Connections {
618+
target: inputParams
619+
620+
function onParentPinsUpdated() {
621+
visible = updateChildPin(modelData, inputParams.parentPins, paramLoader.item)
622+
}
623+
}
624+
502625
sourceComponent: AttributePin {
503626
id: inParamsPin
504627
nodeItem: root
628+
attribute: modelData
629+
505630
property real globalX: root.x + nodeAttributes.x + inputParamsRect.x + paramLoader.x + inParamsPin.x
506631
property real globalY: root.y + nodeAttributes.y + inputParamsRect.y + paramLoader.y + inParamsPin.y
507632

508633
height: isFullyActive ? childrenRect.height : 0
509634
Behavior on height { PropertyAnimation {easing.type: Easing.Linear} }
510635
visible: (height == childrenRect.height)
511-
attribute: object
512-
readOnly: Boolean(root.readOnly || object.isReadOnly)
636+
637+
readOnly: Boolean(root.readOnly || modelData.isReadOnly)
513638
Component.onCompleted: attributePinCreated(attribute, inParamsPin)
514639
Component.onDestruction: attributePinDeleted(attribute, inParamsPin)
640+
515641
onPressed: root.pressed(mouse)
642+
643+
onClicked: {
644+
expanded = !expanded
645+
if (inputParams.parentPins.has(modelData.name)) {
646+
inputParams.parentPins.set(modelData.name, expanded)
647+
inputParams.parentPinsUpdated()
648+
}
649+
}
650+
516651
onEdgeAboutToBeRemoved: root.edgeAboutToBeRemoved(input)
517652
onChildPinCreated: attributePinCreated(childAttribute, inParamsPin)
518653
onChildPinDeleted: attributePinDeleted(childAttribute, inParamsPin)

0 commit comments

Comments
 (0)