Skip to content

Commit d993e9a

Browse files
committed
[ui] Rework NodeActions to handle signals better + combine recompute with compute button
1 parent d0bc1ab commit d993e9a

File tree

2 files changed

+121
-113
lines changed

2 files changed

+121
-113
lines changed

meshroom/ui/qml/Controls/NodeActions.qml

Lines changed: 121 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Item {
99
id: root
1010

1111
// Settings
12-
property real headerOffset: 10 // Distance above the node in screen pixels
13-
property real _opacity: 0.9
12+
readonly property real headerOffset: 10 // Distance above the node in screen pixels
13+
readonly property real _opacity: 0.9
1414

1515
// Objects passed from the graph editor
1616
property var uigraph: null
@@ -22,7 +22,6 @@ Item {
2222
signal stopComputeRequest(var node)
2323
signal reComputeRequest(var node)
2424
signal submitRequest(var node)
25-
signal reSubmitRequest(var node)
2625

2726
SystemPalette { id: activePalette }
2827

@@ -32,15 +31,20 @@ Item {
3231
function nodeDelegate(node) {
3332
if (!nodeRepeater)
3433
return null
35-
3634
for (var i = 0; i < nodeRepeater.count; ++i) {
3735
if (nodeRepeater.itemAt(i).node === node)
3836
return nodeRepeater.itemAt(i)
3937
}
40-
4138
return null
4239
}
43-
40+
41+
enum ButtonState {
42+
LAUNCHABLE,
43+
STOPPABLE,
44+
RELAUNCHABLE,
45+
DISABLED
46+
}
47+
4448
Rectangle {
4549
id: actionHeader
4650

@@ -53,19 +57,9 @@ Item {
5357
width: actionItemsRow.width
5458
height: actionItemsRow.height
5559

56-
// State properties
57-
property string currentExecMode: selectedNode ? selectedNode.globalExecMode : "NONE"
58-
property string currentStatus: selectedNode ? selectedNode.globalStatus : "NONE"
59-
property bool nodeCanBeStopped: selectedNode ? selectedNode.canBeStopped() : false
60-
property bool nodeIsExternal: selectedNode ? selectedNode.isExternal : false
61-
property bool nodeLocked: selectedNode ? selectedNode.locked : false
62-
63-
// Derived values
64-
readonly property bool runningExternally: currentExecMode === "EXTERN"
65-
&& ["SUBMITTED", "RUNNING"].includes(currentStatus)
66-
readonly property bool stoppable: selectedNode && currentStatus === "RUNNING" && nodeCanBeStopped
67-
readonly property bool launchable: selectedNode &&
68-
(nodeCanBeStopped || ["NONE","STOPPED","KILLED","ERROR"].includes(currentStatus))
60+
//
61+
// ===== Manage NodeActions position =====
62+
//
6963

7064
// Prevents losing focus on the node when we click on buttons of the actionItems
7165
MouseArea {
@@ -80,11 +74,11 @@ Item {
8074
// Update position
8175
function updatePosition() {
8276
if (!selectedNodeDelegate || !draggable) return
83-
77+
8478
// Calculate node position in screen coordinates
8579
const nodeScreenX = selectedNodeDelegate.x * draggable.scale + draggable.x
8680
const nodeScreenY = selectedNodeDelegate.y * draggable.scale + draggable.y
87-
81+
8882
// Position header above the node (fixed offset in screen pixels)
8983
x = nodeScreenX + (selectedNodeDelegate.width * draggable.scale - width) / 2
9084
y = nodeScreenY - height - headerOffset
@@ -97,36 +91,104 @@ Item {
9791
function onYChanged() { actionHeader.updatePosition() }
9892
function onScaleChanged() { actionHeader.updatePosition() }
9993
}
100-
94+
10195
// Update position when nodes are moved
10296
Connections {
10397
target: actionHeader.selectedNodeDelegate
10498
function onXChanged() { actionHeader.updatePosition() }
10599
function onYChanged() { actionHeader.updatePosition() }
106100
ignoreUnknownSignals: true
107101
}
102+
103+
//
104+
// ===== Manage buttons =====
105+
//
106+
107+
property bool nodeIsLocked: false
108+
property bool submittedExternally: false
109+
property int computeButtonState: NodeActions.ButtonState.LAUNCHABLE
110+
property string computeButtonIcon: {
111+
switch (computeButtonState) {
112+
case NodeActions.ButtonState.STOPPABLE: return MaterialIcons.cancel_schedule_send
113+
case NodeActions.ButtonState.RELAUNCHABLE: return MaterialIcons.autorenew
114+
default: return MaterialIcons.send
115+
}
116+
}
117+
property int submitButtonState: NodeActions.ButtonState.LAUNCHABLE
118+
119+
function getComputeButtonState(node) {
120+
if (node.canBeStopped()) return NodeActions.ButtonState.STOPPABLE
121+
if (node.canBeCanceled()) return NodeActions.ButtonState.STOPPABLE
122+
if (actionHeader.nodeIsLocked) return NodeActions.ButtonState.DISABLED
123+
switch (node.globalStatus) {
124+
case "NONE":
125+
case "ERROR":
126+
case "STOPPED":
127+
case "KILLED":
128+
return NodeActions.ButtonState.LAUNCHABLE
129+
case "SUCCESS":
130+
return NodeActions.ButtonState.RELAUNCHABLE
131+
}
132+
return NodeActions.ButtonState.DISABLED
133+
}
134+
135+
function getSubmitButtonState(node) {
136+
if (actionHeader.nodeIsLocked || node.canBeStopped()) {
137+
return NodeActions.ButtonState.DISABLED
138+
}
139+
switch (node.globalStatus) {
140+
case "NONE":
141+
case "ERROR":
142+
case "STOPPED":
143+
case "KILLED":
144+
case "SUCCESS":
145+
return NodeActions.ButtonState.LAUNCHABLE
146+
return NodeActions.ButtonState.LAUNCHABLE
147+
break
148+
// SUBMITTED / RUNNING / INPUT -> DISABLED
149+
}
150+
return NodeActions.ButtonState.DISABLED
151+
}
108152

109-
// Set initial position
110-
onSelectedNodeDelegateChanged: updatePosition()
153+
function isSubmittedExternally(node) {
154+
if (node.globalExecMode == "EXTERN" && node.globalStatus == "SUBMITTED")
155+
return true
156+
return false
157+
}
158+
159+
function updateProperties(node) {
160+
actionHeader.nodeIsLocked = node.locked
161+
actionHeader.computeButtonState = getComputeButtonState(node)
162+
actionHeader.submitButtonState = getSubmitButtonState(node)
163+
actionHeader.submittedExternally = isSubmittedExternally(node)
164+
}
165+
166+
167+
// Set initial state & position
168+
onSelectedNodeDelegateChanged: {
169+
updatePosition()
170+
if (actionHeader.selectedNode) {
171+
actionHeader.updateProperties(actionHeader.selectedNode)
172+
}
173+
}
111174

112175
// Listen to updates to status
113176
Connections {
114177
target: actionHeader.selectedNode
115-
function onGlobalStatusChanged() { actionHeader.currentStatus = target.globalStatus }
116-
function onGlobalExecModeChanged() { actionHeader.currentExecMode = target.globalExecMode }
117-
function onIsComputedChanged() { actionHeader.nodeCanBeStopped = target.canBeStopped() }
118-
function onLockedChanged() { actionHeader.nodeLocked = target.locked }
178+
function onGlobalStatusChanged() {
179+
actionHeader.updateProperties(target)
180+
}
181+
function onLockedChanged() {
182+
actionHeader.nodeIsLocked = target.locked
183+
}
119184
ignoreUnknownSignals: true
120185
}
121186

122-
// Also listen to uigraph for status updates
187+
// Listen to updates from nodes that are not selected
123188
Connections {
124189
target: root.uigraph
125-
function onComputationStatusChanged() {
126-
actionHeader.currentStatus = actionHeader.selectedNode ? actionHeader.selectedNode.globalStatus : "NONE"
127-
}
128-
function onNodeStatusUpdated() {
129-
actionHeader.currentStatus = actionHeader.selectedNode ? actionHeader.selectedNode.globalStatus : "NONE"
190+
function onComputingChanged() {
191+
actionHeader.updateProperties(actionHeader.selectedNode)
130192
}
131193
ignoreUnknownSignals: true
132194
}
@@ -135,66 +197,46 @@ Item {
135197
id: actionItemsRow
136198
anchors.centerIn: parent
137199
spacing: 2
138-
200+
139201
// Compute button
140202
MaterialToolButton {
141203
id: computeButton
142204
font.pointSize: 16
143-
text: actionHeader.stoppable ? MaterialIcons.cancel_schedule_send : MaterialIcons.send
205+
text: actionHeader.computeButtonIcon
144206
padding: 6
145207
ToolTip.text: "Start/Stop Compute"
146208
ToolTip.visible: hovered
147209
ToolTip.delay: 1000
148-
enabled: actionHeader.selectedNode && actionHeader.launchable
210+
enabled: actionHeader.computeButtonState != NodeActions.ButtonState.DISABLED
149211
background: Rectangle {
150212
color: {
151213
if (!computeButton.enabled) return activePalette.button
152-
if (actionHeader.currentStatus === "RUNNING") {
153-
if (computeButton.hovered) return Colors.statusColors["STOPPED"]
154-
return Qt.darker(Colors.statusColors["STOPPED"], 1.3)
155-
} else {
156-
if (computeButton.hovered) return activePalette.highlight
157-
return activePalette.button
214+
switch (actionHeader.computeButtonState) {
215+
case NodeActions.ButtonState.STOPPABLE:
216+
if (computeButton.hovered) return Colors.statusColors["STOPPED"]
217+
return Qt.darker(Colors.statusColors["STOPPED"], 1.3)
218+
default: break
158219
}
220+
if (computeButton.hovered) return activePalette.highlight
221+
return activePalette.button
159222
}
160223
opacity: computeButton.hovered ? 1 : root._opacity
161224
border.color: computeButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.3)
162225
border.width: 1
163226
radius: 3
164227
}
165228
onClicked: {
166-
if (actionHeader.selectedNode && !actionHeader.nodeIsExternal && actionHeader.currentStatus === "RUNNING") {
167-
root.stopComputeRequest(actionHeader.selectedNode)
168-
} else {
169-
root.computeRequest(actionHeader.selectedNode)
170-
}
171-
}
172-
}
173-
174-
// Re-compute button : stop local process and relaunch locally
175-
MaterialToolButton {
176-
id: reComputeButton
177-
font.pointSize: 16
178-
text: MaterialIcons.autorenew
179-
padding: 6
180-
ToolTip.text: "Re-compute"
181-
ToolTip.visible: hovered
182-
ToolTip.delay: 1000
183-
enabled: actionHeader.selectedNode && !actionHeader.runningExternally
184-
background: Rectangle {
185-
color: {
186-
if (!reComputeButton.enabled) return activePalette.button
187-
if (reComputeButton.hovered) return activePalette.highlight
188-
return activePalette.button
189-
}
190-
opacity: reComputeButton.hovered ? 1 : root._opacity
191-
border.color: reComputeButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.3)
192-
border.width: 1
193-
radius: 3
194-
}
195-
onClicked: {
196-
if (actionHeader.selectedNode) {
197-
root.reComputeRequest(actionHeader.selectedNode)
229+
switch (actionHeader.computeButtonState) {
230+
case NodeActions.ButtonState.STOPPABLE:
231+
root.stopComputeRequest(actionHeader.selectedNode)
232+
break
233+
case NodeActions.ButtonState.LAUNCHABLE:
234+
root.computeRequest(actionHeader.selectedNode)
235+
break
236+
case NodeActions.ButtonState.RELAUNCHABLE:
237+
root.reComputeRequest(actionHeader.selectedNode)
238+
break
239+
default: break
198240
}
199241
}
200242
}
@@ -209,11 +251,13 @@ Item {
209251
ToolTip.visible: hovered
210252
ToolTip.delay: 1000
211253
visible: root.uigraph ? root.uigraph.canSubmit : false
212-
enabled: actionHeader.selectedNode ? !actionHeader.nodeLocked : false
254+
enabled: actionHeader.submitButtonState != NodeActions.ButtonState.DISABLED
255+
// enabled: actionHeader.selectedNode ? !actionHeader.nodeLocked : false
213256
background: Rectangle {
214257
color: {
258+
if (actionHeader.submittedExternally)
259+
return Qt.darker(Colors.statusColors["SUBMITTED"], 1.2)
215260
if (!submitButton.enabled) return activePalette.button
216-
if (actionHeader.runningExternally) return Colors.statusColors["SUBMITTED"]
217261
if (submitButton.hovered) return activePalette.highlight
218262
return activePalette.button
219263
}
@@ -228,36 +272,6 @@ Item {
228272
}
229273
}
230274
}
231-
232-
// Re-submit button : stop everything and relaunch on farm
233-
// TODO : disabled for now because we can't stop jobs submitted on farm
234-
MaterialToolButton {
235-
id: reSubmitButton
236-
font.pointSize: 16
237-
text: MaterialIcons.double_arrow
238-
padding: 6
239-
ToolTip.text: "Re-submit on Render Farm"
240-
ToolTip.visible: hovered
241-
ToolTip.delay: 1000
242-
visible: false // root.uigraph ? root.uigraph.canSubmit : false
243-
enabled: actionHeader.selectedNode !== null
244-
background: Rectangle {
245-
color: {
246-
if (!actionHeader.selectedNode) return activePalette.button
247-
if (reSubmitButton.hovered) return activePalette.highlight
248-
return activePalette.button
249-
}
250-
opacity: reSubmitButton.hovered ? 1 : root._opacity
251-
border.color: reSubmitButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.3)
252-
border.width: 1
253-
radius: 3
254-
}
255-
onClicked: {
256-
if (actionHeader.selectedNode) {
257-
root.reSubmitRequest(actionHeader.selectedNode)
258-
}
259-
}
260-
}
261275
}
262276
}
263277
}

meshroom/ui/qml/GraphEditor/GraphEditor.qml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,12 +1102,6 @@ Item {
11021102
if (node.isComputed) uigraph.clearSelectedNodesData();
11031103
root.submitRequest([node])
11041104
}
1105-
1106-
// TODO : If we want this, we should add the possibility to stop job on farm first
1107-
// onReSubmitRequest: function(node) {
1108-
// uigraph.clearSelectedNodesData();
1109-
// root.submitRequest([node])
1110-
// }
11111105
}
11121106

11131107
MessageDialog {

0 commit comments

Comments
 (0)