diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index 8f410206..de975832 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -202,6 +202,7 @@ class UIGraph(QObject): self._sortedDFSChunks = QObjectListModel(parent=self) self._layout = GraphLayout(self) self._selectedNode = None + self._hoveredNode = None if filepath: self.load(filepath) @@ -236,6 +237,7 @@ class UIGraph(QObject): def clear(self): if self._graph: + self.clearNodeHover() self.clearNodeSelection() self._graph.deleteLater() self._graph = None @@ -472,6 +474,10 @@ class UIGraph(QObject): """ Clear node selection. """ self.selectedNode = None + def clearNodeHover(self): + """ Reset currently hovered node to None. """ + self.hoveredNode = None + undoStack = Property(QObject, lambda self: self._undoStack, constant=True) graphChanged = Signal() graph = Property(Graph, lambda self: self._graph, notify=graphChanged) @@ -490,3 +496,7 @@ class UIGraph(QObject): selectedNodeChanged = Signal() # Currently selected node selectedNode = makeProperty(QObject, "_selectedNode", selectedNodeChanged, clearNodeSelection) + + hoveredNodeChanged = Signal() + # Currently hovered node + hoveredNode = makeProperty(QObject, "_hoveredNode", hoveredNodeChanged, clearNodeHover) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 238bba1d..3548b529 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -308,6 +308,7 @@ Item { width: uigraph.layout.nodeWidth readOnly: root.readOnly selected: uigraph.selectedNode === node + hovered: uigraph.hoveredNode === node onSelectedChanged: if(selected) forceActiveFocus() onAttributePinCreated: registerAttributePin(attribute, pin) @@ -331,6 +332,9 @@ Item { onMoved: uigraph.moveNode(node, position) + onEntered: uigraph.hoveredNode = node + onExited: uigraph.hoveredNode = null + Keys.onDeletePressed: uigraph.removeNode(node) Behavior on x { diff --git a/meshroom/ui/qml/GraphEditor/Node.qml b/meshroom/ui/qml/GraphEditor/Node.qml index 6f021929..babf3078 100755 --- a/meshroom/ui/qml/GraphEditor/Node.qml +++ b/meshroom/ui/qml/GraphEditor/Node.qml @@ -13,14 +13,17 @@ Item { readonly property bool isCompatibilityNode: node.hasOwnProperty("compatibilityIssue") readonly property color defaultColor: isCompatibilityNode ? "#444" : "#607D8B" property bool selected: false + property bool hovered: false signal pressed(var mouse) signal doubleClicked(var mouse) signal moved(var position) + signal entered() + signal exited() signal attributePinCreated(var attribute, var pin) signal attributePinDeleted(var attribute, var pin) - implicitHeight: body.height + implicitHeight: childrenRect.height objectName: node.name SystemPalette { id: activePalette } @@ -39,7 +42,8 @@ Item { } MouseArea { - anchors.fill: parent + width: parent.width + height: body.height drag.target: parent // small drag threshold to avoid moving the node by mistake drag.threshold: 2 @@ -47,128 +51,130 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: root.pressed(mouse) onDoubleClicked: root.doubleClicked(mouse) + onEntered: root.entered() + onExited: root.exited() drag.onActiveChanged: { if(!drag.active) root.moved(Qt.point(root.x, root.y)) } - } - // Selection border - Rectangle { - anchors.fill: parent - anchors.margins: -border.width - visible: root.selected - border.width: 2.5 - border.color: activePalette.highlight - opacity: 0.9 - color: "transparent" - } + // Selection border + Rectangle { + anchors.fill: parent + anchors.margins: -border.width + visible: root.selected || root.hovered + border.width: 2.5 + border.color: root.selected ? activePalette.highlight : Qt.darker(activePalette.highlight, 1.5) + opacity: 0.9 + color: "transparent" + } - Rectangle { - id: background - anchors.fill: parent - color: activePalette.base - layer.enabled: true - layer.effect: DropShadow { radius: 2; color: shadowColor } - } + Rectangle { + id: background + anchors.fill: parent + color: activePalette.base + layer.enabled: true + layer.effect: DropShadow { radius: 2; color: shadowColor } + } - Column { - id: body - width: parent.width - - Label { + Column { + id: body width: parent.width - horizontalAlignment: Text.AlignHCenter - padding: 4 - text: node.label - color: "#EEE" - font.pointSize: 8 - background: Rectangle { - color: root.baseColor + + Label { + width: parent.width + horizontalAlignment: Text.AlignHCenter + padding: 4 + text: node.label + color: "#EEE" + font.pointSize: 8 + background: Rectangle { + color: root.baseColor + } } - } - // Node Chunks - NodeChunks { - defaultColor: Qt.darker(baseColor, 1.3) - implicitHeight: 3 - width: parent.width - model: node.chunks - } + // Node Chunks + NodeChunks { + defaultColor: Qt.darker(baseColor, 1.3) + implicitHeight: 3 + width: parent.width + model: node.chunks + } - Item { width: 1; height: 2} + Item { width: 1; height: 2} - Item { - width: parent.width + 6 - height: childrenRect.height - anchors.horizontalCenter: parent.horizontalCenter + Item { + width: parent.width + 6 + height: childrenRect.height + anchors.horizontalCenter: parent.horizontalCenter - Column { - id: inputs - width: parent.width / 2 - spacing: 1 - Repeater { - model: node.attributes - delegate: Loader { - active: !object.isOutput && object.type == "File" - || (object.type == "ListAttribute" && object.desc.elementDesc.type == "File") // TODO: review this - width: inputs.width + Column { + id: inputs + width: parent.width / 2 + spacing: 1 + Repeater { + model: node.attributes + delegate: Loader { + active: !object.isOutput && object.type == "File" + || (object.type == "ListAttribute" && object.desc.elementDesc.type == "File") // TODO: review this + width: inputs.width - sourceComponent: AttributePin { - id: inPin - nodeItem: root - attribute: object - readOnly: root.readOnly - Component.onCompleted: attributePinCreated(attribute, inPin) - Component.onDestruction: attributePinDeleted(attribute, inPin) - onPressed: root.pressed(mouse) - onChildPinCreated: attributePinCreated(childAttribute, inPin) - onChildPinDeleted: attributePinDeleted(childAttribute, inPin) + sourceComponent: AttributePin { + id: inPin + nodeItem: root + attribute: object + readOnly: root.readOnly + Component.onCompleted: attributePinCreated(attribute, inPin) + Component.onDestruction: attributePinDeleted(attribute, inPin) + onPressed: root.pressed(mouse) + onChildPinCreated: attributePinCreated(childAttribute, inPin) + onChildPinDeleted: attributePinDeleted(childAttribute, inPin) + } + } + } + } + Column { + id: outputs + width: parent.width / 2 + anchors.right: parent.right + spacing: 1 + Repeater { + model: node.attributes + + delegate: Loader { + active: object.isOutput + anchors.right: parent.right + width: outputs.width + + sourceComponent: AttributePin { + id: outPin + nodeItem: root + attribute: object + readOnly: root.readOnly + onPressed: root.pressed(mouse) + Component.onCompleted: attributePinCreated(object, outPin) + Component.onDestruction: attributePinDeleted(attribute, outPin) + } } } } } - Column { - id: outputs - width: parent.width / 2 - anchors.right: parent.right - spacing: 1 - Repeater { - model: node.attributes + Item { width: 1; height: 2} + } - delegate: Loader { - active: object.isOutput - anchors.right: parent.right - width: outputs.width - - sourceComponent: AttributePin { - id: outPin - nodeItem: root - attribute: object - readOnly: root.readOnly - onPressed: root.pressed(mouse) - Component.onCompleted: attributePinCreated(object, outPin) - Component.onDestruction: attributePinDeleted(attribute, outPin) - } - } - } + // CompatibilityBadge icon for CompatibilityNodes + Loader { + active: root.isCompatibilityNode + anchors { + right: parent.right + top: parent.top + margins: -4 + } + sourceComponent: CompatibilityBadge { + sourceComponent: iconDelegate + canUpgrade: root.node.canUpgrade + issueDetails: root.node.issueDetails } - } - Item { width: 1; height: 2} - } - - // CompatibilityBadge icon for CompatibilityNodes - Loader { - active: root.isCompatibilityNode - anchors { - right: parent.right - top: parent.top - margins: -4 - } - sourceComponent: CompatibilityBadge { - sourceComponent: iconDelegate - canUpgrade: root.node.canUpgrade - issueDetails: root.node.issueDetails } } }