-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[ui] Add NodeActions to launch computation #2884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
bef34f9
[ui] Add NodeActions qml element to re-launch computation
Alxiice 1d0feee
[ui] NodeActions: Fix issues in properties binding
Alxiice 750f992
[ui] Rework NodeActions to handle signals better + combine recompute …
Alxiice 48429ef
[ui] NodeActions : Replace the recompute by delete node data
Alxiice 9c182e6
[ui] NodeActions: handle compatibility nodes
Alxiice d84aa44
[ui] NodeActions: Explore graph to check if we can compute/submit
Alxiice f31684a
[ui] NodeActions: Separate delete button to make sure we can alsways …
Alxiice b64da83
[ui] NodeActions: Make sure position is correct when width change
Alxiice 8b4d9bf
[ui] NodeActions: Fix color indicator on submitted locked node
Alxiice 82c17f2
[ui] NodeActions: disable submit when the node isn't cleared first
Alxiice File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,297 @@ | ||
| import QtQuick | ||
| import QtQuick.Controls | ||
| import QtQuick.Layouts | ||
|
|
||
| import MaterialIcons 2.2 | ||
| import Utils 1.0 | ||
|
|
||
| Item { | ||
| id: root | ||
|
|
||
| // Settings | ||
| readonly property real headerOffset: 10 // Distance above the node in screen pixels | ||
| readonly property real _opacity: 0.9 | ||
|
|
||
| // Objects passed from the graph editor | ||
| property var uigraph: null | ||
| property var draggable: null // The draggable container from GraphEditor | ||
| property var nodeRepeater: null // Reference to nodeRepeater to find delegates | ||
|
|
||
| // Signals | ||
| signal computeRequest(var node) | ||
| signal stopComputeRequest(var node) | ||
| signal deleteDataRequest(var node) | ||
| signal submitRequest(var node) | ||
|
|
||
| SystemPalette { id: activePalette } | ||
|
|
||
| /** | ||
| * Get the node delegate | ||
| */ | ||
| function nodeDelegate(node) { | ||
| if (!nodeRepeater) | ||
| return null | ||
| for (var i = 0; i < nodeRepeater.count; ++i) { | ||
| if (nodeRepeater.itemAt(i).node === node) | ||
| return nodeRepeater.itemAt(i) | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| enum ButtonState { | ||
| DISABLED = 0, | ||
| LAUNCHABLE = 1, | ||
| DELETABLE = 2, | ||
| STOPPABLE = 3 | ||
| } | ||
|
|
||
| Rectangle { | ||
| id: actionHeader | ||
|
|
||
| readonly property bool hasSelectedNode: uigraph && uigraph.nodeSelection.selectedIndexes.length === 1 | ||
| readonly property var selectedNode: hasSelectedNode ? uigraph.selectedNode : null | ||
| readonly property var selectedNodeDelegate: selectedNode ? root.nodeDelegate(selectedNode) : null | ||
|
|
||
| visible: selectedNodeDelegate !== null | ||
| color: "transparent" | ||
| width: actionItemsRow.width | ||
| height: actionItemsRow.height | ||
|
|
||
| // | ||
| // ===== Manage NodeActions position ===== | ||
| // | ||
|
|
||
| // Prevents losing focus on the node when we click on buttons of the actionItems | ||
| MouseArea { | ||
| anchors.fill: parent | ||
| onPressed: function(mouse) { mouse.accepted = true } | ||
| onReleased: function(mouse) { mouse.accepted = true } | ||
| onClicked: function(mouse) { mouse.accepted = true } | ||
| onDoubleClicked: function(mouse) { mouse.accepted = true } | ||
| hoverEnabled: false | ||
| } | ||
|
|
||
| // Update position | ||
| function updatePosition() { | ||
| if (!selectedNodeDelegate || !draggable) return | ||
| // Calculate node position in screen coordinates | ||
| const nodeScreenX = selectedNodeDelegate.x * draggable.scale + draggable.x | ||
| const nodeScreenY = selectedNodeDelegate.y * draggable.scale + draggable.y | ||
| // Position header above the node (fixed offset in screen pixels) | ||
| x = nodeScreenX + (selectedNodeDelegate.width * draggable.scale - width) / 2 | ||
| y = nodeScreenY - height - headerOffset | ||
| } | ||
|
|
||
| onWidthChanged: { | ||
| updatePosition() | ||
| } | ||
|
|
||
| // Update position when the user moves on the graph | ||
| Connections { | ||
| target: root.draggable | ||
| function onXChanged() { actionHeader.updatePosition() } | ||
| function onYChanged() { actionHeader.updatePosition() } | ||
| function onScaleChanged() { actionHeader.updatePosition() } | ||
| } | ||
|
|
||
| // Update position when nodes are moved | ||
| Connections { | ||
| target: actionHeader.selectedNodeDelegate | ||
| function onXChanged() { actionHeader.updatePosition() } | ||
| function onYChanged() { actionHeader.updatePosition() } | ||
| ignoreUnknownSignals: true | ||
| } | ||
|
|
||
| // | ||
| // ===== Manage buttons ===== | ||
| // | ||
|
|
||
| property bool nodeIsLocked: false | ||
| property bool canComputeNode: false | ||
| property bool canStopNode: false | ||
| property bool canRestartNode: false | ||
| property bool canSubmitNode: false | ||
| property bool nodeSubmitted: false | ||
|
|
||
| property int computeButtonState: NodeActions.ButtonState.LAUNCHABLE | ||
| property string computeButtonIcon: { | ||
| switch (computeButtonState) { | ||
| case NodeActions.ButtonState.STOPPABLE: return MaterialIcons.cancel_schedule_send | ||
| default: return MaterialIcons.send | ||
| } | ||
| } | ||
| property int submitButtonState: NodeActions.ButtonState.LAUNCHABLE | ||
|
|
||
| function getComputeButtonState(node) { | ||
| if (actionHeader.canStopNode) | ||
| return NodeActions.ButtonState.STOPPABLE | ||
| if (!actionHeader.nodeIsLocked) { | ||
| if (node.globalStatus == "SUCCESS") | ||
| return NodeActions.ButtonState.DELETABLE | ||
| if (actionHeader.canComputeNode) | ||
| return NodeActions.ButtonState.LAUNCHABLE | ||
| } | ||
| return NodeActions.ButtonState.DISABLED | ||
| } | ||
|
|
||
| function getSubmitButtonState(node) { | ||
| if (actionHeader.nodeIsLocked || actionHeader.canStopNode) | ||
| return NodeActions.ButtonState.DISABLED | ||
| if (!actionHeader.nodeIsLocked && actionHeader.canSubmitNode) | ||
| return NodeActions.ButtonState.LAUNCHABLE | ||
| return NodeActions.ButtonState.DISABLED | ||
| } | ||
|
|
||
| function isSubmittedExternally(node) { | ||
| return node.globalExecMode == "EXTERN" && ["RUNNING", "SUBMITTED"].includes(node.globalStatus) | ||
| } | ||
|
|
||
| function isNodeRestartable(node) { | ||
| return actionHeader.computeButtonState == NodeActions.ButtonState.LAUNCHABLE && | ||
| ["ERROR", "STOPPED", "KILLED"].includes(node.globalStatus) | ||
| } | ||
|
|
||
| function updateProperties(node) { | ||
| if (!node) return | ||
| // Update properties values | ||
| actionHeader.canComputeNode = uigraph.canComputeNode(node) | ||
| actionHeader.canSubmitNode = uigraph.canSubmitNode(node) | ||
| actionHeader.canStopNode = node.canBeStopped() || node.canBeCanceled() | ||
| actionHeader.nodeIsLocked = node.locked | ||
| actionHeader.nodeSubmitted = isSubmittedExternally(node) | ||
| // Update button states | ||
| actionHeader.computeButtonState = getComputeButtonState(node) | ||
| actionHeader.submitButtonState = getSubmitButtonState(node) | ||
| actionHeader.canRestartNode = isNodeRestartable(node) | ||
| } | ||
|
|
||
| // Set initial state & position | ||
| onSelectedNodeDelegateChanged: { | ||
| if (actionHeader.selectedNode) { | ||
| actionHeader.updateProperties(actionHeader.selectedNode) | ||
| Qt.callLater(actionHeader.updatePosition) | ||
| } | ||
| } | ||
|
|
||
| // Listen to updates to status | ||
| Connections { | ||
| target: actionHeader.selectedNode | ||
| function onGlobalStatusChanged() { | ||
| actionHeader.updateProperties(target) | ||
| } | ||
| function onLockedChanged() { | ||
| actionHeader.nodeIsLocked = target.locked | ||
| } | ||
| ignoreUnknownSignals: true | ||
| } | ||
|
|
||
| // Listen to updates from nodes that are not selected | ||
| Connections { | ||
| target: root.uigraph | ||
| function onComputingChanged() { | ||
| actionHeader.updateProperties(actionHeader.selectedNode) | ||
| } | ||
| ignoreUnknownSignals: true | ||
| } | ||
|
|
||
| Row { | ||
| id: actionItemsRow | ||
| anchors.centerIn: parent | ||
| spacing: 2 | ||
|
|
||
| // Compute button | ||
Alxiice marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| MaterialToolButton { | ||
| id: computeButton | ||
| font.pointSize: 16 | ||
| text: actionHeader.computeButtonIcon | ||
| padding: 6 | ||
| ToolTip.text: "Start/Stop/Restart Compute" | ||
| ToolTip.visible: hovered | ||
| ToolTip.delay: 1000 | ||
| visible: actionHeader.computeButtonState != NodeActions.ButtonState.DELETABLE | ||
| enabled: actionHeader.computeButtonState % 2 == 1 // Launchable & Stoppable | ||
| background: Rectangle { | ||
| color: { | ||
| if (!computeButton.enabled) { | ||
| if (actionHeader.nodeSubmitted) | ||
| return Qt.darker(Colors.statusColors["SUBMITTED"], 1.2) | ||
| return activePalette.button | ||
| } | ||
| if (actionHeader.computeButtonState == NodeActions.ButtonState.STOPPABLE) | ||
| return computeButton.hovered ? Colors.orange : Qt.darker(Colors.orange, 1.3) | ||
| return computeButton.hovered ? activePalette.highlight : activePalette.button | ||
| } | ||
| opacity: computeButton.hovered ? 1 : root._opacity | ||
| border.color: computeButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.3) | ||
| border.width: 1 | ||
| radius: 3 | ||
| } | ||
| onClicked: { | ||
| switch (actionHeader.computeButtonState) { | ||
| case NodeActions.ButtonState.STOPPABLE: | ||
| root.stopComputeRequest(actionHeader.selectedNode) | ||
| break | ||
| case NodeActions.ButtonState.LAUNCHABLE: | ||
| root.computeRequest(actionHeader.selectedNode) | ||
| break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Clear node | ||
| MaterialToolButton { | ||
| id: restartButton | ||
Alxiice marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| font.pointSize: 16 | ||
| text: MaterialIcons.delete_ | ||
| padding: 6 | ||
| ToolTip.text: "Delete data" | ||
| ToolTip.visible: hovered | ||
| ToolTip.delay: 1000 | ||
| visible: actionHeader.canRestartNode || actionHeader.computeButtonState == NodeActions.ButtonState.DELETABLE | ||
| enabled: visible | ||
| background: Rectangle { | ||
| color: computeButton.hovered ? Colors.red : Qt.darker(Colors.red, 1.3) | ||
| opacity: computeButton.hovered ? 1 : root._opacity | ||
| border.color: computeButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.3) | ||
| border.width: 1 | ||
| radius: 3 | ||
| } | ||
| onClicked: { | ||
| root.deleteDataRequest(actionHeader.selectedNode) | ||
| } | ||
| } | ||
|
|
||
| // Submit button | ||
| MaterialToolButton { | ||
| id: submitButton | ||
| font.pointSize: 16 | ||
| text: MaterialIcons.rocket_launch | ||
| padding: 6 | ||
| ToolTip.text: "Submit on Render Farm" | ||
| ToolTip.visible: hovered | ||
| ToolTip.delay: 1000 | ||
| visible: root.uigraph ? root.uigraph.canSubmit : false | ||
| enabled: actionHeader.submitButtonState != NodeActions.ButtonState.DISABLED | ||
| background: Rectangle { | ||
| color: { | ||
| if (!submitButton.enabled) { | ||
| if (actionHeader.nodeSubmitted) | ||
| return Qt.darker(Colors.statusColors["SUBMITTED"], 1.2) | ||
| return activePalette.button | ||
| } | ||
| return submitButton.hovered ? activePalette.highlight : activePalette.button | ||
| } | ||
| opacity: submitButton.hovered ? 1 : root._opacity | ||
| border.color: submitButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.3) | ||
| border.width: 1 | ||
| radius: 3 | ||
| } | ||
| onClicked: { | ||
| if (actionHeader.selectedNode) { | ||
| root.submitRequest(actionHeader.selectedNode) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.