1+ import QtQuick
2+ import QtQuick.Controls
3+ import QtQuick.Layouts
4+
5+ import MaterialIcons 2.2
6+ import Utils 1.0
7+
8+
9+ Item {
10+ id: root
11+
12+ // Settings
13+ property real headerOffset: 10 // Distance above the node in screen pixels
14+ property real _opacity: 0.9
15+
16+ // Objects passed from the graph editor
17+ property var uigraph: null
18+ property var draggable: null // The draggable container from GraphEditor
19+ property var nodeRepeater: null // Reference to nodeRepeater to find delegates
20+
21+ // Signals
22+ signal computeRequest (var node)
23+ signal stopComputeRequest (var node)
24+ signal reComputeRequest (var node)
25+ signal submitRequest (var node)
26+ signal reSubmitRequest (var node)
27+
28+ SystemPalette { id: activePalette }
29+
30+ /**
31+ * Get the node delegate
32+ */
33+ function nodeDelegate (node ) {
34+ if (! nodeRepeater)
35+ return null
36+
37+ for (var i = 0 ; i < nodeRepeater .count ; ++ i) {
38+ if (nodeRepeater .itemAt (i).node === node)
39+ return nodeRepeater .itemAt (i)
40+ }
41+
42+ return null
43+ }
44+
45+ Rectangle {
46+ id: actionHeader
47+
48+ function hasSelectedNode () {
49+ return uigraph && uigraph .nodeSelection .selectedIndexes .length === 1
50+ }
51+
52+ readonly property var selectedNode: hasSelectedNode () ? uigraph .selectedNode : null
53+ readonly property var selectedNodeDelegate: selectedNode ? root .nodeDelegate (selectedNode) : null
54+
55+ visible: selectedNodeDelegate !== null
56+ color: " transparent"
57+ width: actionItemsRow .width
58+ height: actionItemsRow .height
59+
60+ // Properties depending on selectedNode
61+ readonly property string currentExecMode: selectedNode ? selectedNode .globalExecMode : " NONE"
62+ readonly property string currentStatus: selectedNode ? selectedNode .globalStatus : " NONE"
63+ readonly property bool nodeCanBeStopped: selectedNode ? selectedNode .canBeStopped () : false
64+ readonly property bool nodeIsExternal: selectedNode ? selectedNode .isExternal : false
65+ readonly property bool nodeLocked: selectedNode ? selectedNode .locked : false
66+
67+ Connections {
68+ target: actionHeader .selectedNode
69+ function onGlobalStatusChanged () {
70+ actionHeader .currentStatusChanged ()
71+ }
72+ function onGlobalExecModeChanged () {
73+ actionHeader .currentExecModeChanged ()
74+ }
75+ ignoreUnknownSignals: true
76+ }
77+
78+ // Prevents losing focus on the node when we click on buttons of the actionItems
79+ MouseArea {
80+ anchors .fill : parent
81+ // Consume all mouse events to prevent propagation to GraphEditor
82+ onPressed : function (mouse ) { mouse .accepted = true }
83+ onReleased : function (mouse ) { mouse .accepted = true }
84+ onClicked : function (mouse ) { mouse .accepted = true }
85+ onDoubleClicked : function (mouse ) { mouse .accepted = true }
86+ // Allow the buttons to receive hover events
87+ hoverEnabled: false
88+ }
89+
90+ // Update position
91+ function updatePosition () {
92+ if (! selectedNodeDelegate || ! draggable) return
93+
94+ // Calculate node position in screen coordinates
95+ const nodeScreenX = selectedNodeDelegate .x * draggable .scale + draggable .x
96+ const nodeScreenY = selectedNodeDelegate .y * draggable .scale + draggable .y
97+
98+ // Position header above the node (fixed offset in screen pixels)
99+ x = nodeScreenX + (selectedNodeDelegate .width * draggable .scale - width) / 2
100+ y = nodeScreenY - height - headerOffset
101+ }
102+
103+ // Update position when the user moves on the graph
104+ Connections {
105+ target: root .draggable
106+ function onXChanged () { actionHeader .updatePosition () }
107+ function onYChanged () { actionHeader .updatePosition () }
108+ function onScaleChanged () { actionHeader .updatePosition () }
109+ }
110+
111+ // Update position when nodes are moved
112+ Connections {
113+ target: actionHeader .selectedNodeDelegate
114+ function onXChanged () { actionHeader .updatePosition () }
115+ function onYChanged () { actionHeader .updatePosition () }
116+ ignoreUnknownSignals: true
117+ }
118+
119+ // Initial position update
120+ onSelectedNodeDelegateChanged: updatePosition ()
121+
122+ function isRunningExternally () {
123+ return actionHeader .currentExecMode === " EXTERN" && [" SUBMITTED" , " RUNNING" ].includes (actionHeader .currentStatus )
124+ }
125+
126+ function isRunningLocally () {
127+ if (! actionHeader .selectedNode ) return false
128+ if (actionHeader .nodeIsExternal ) return false
129+ return actionHeader .currentStatus === " RUNNING"
130+ }
131+
132+ function canBeStopped () {
133+ if (! actionHeader .selectedNode ) return false
134+ if (actionHeader .currentStatus !== " RUNNING" ) return false
135+ return actionHeader .nodeCanBeStopped
136+ }
137+
138+ function canBeLaunched () {
139+ if (! actionHeader .selectedNode ) return false
140+ if (actionHeader .nodeCanBeStopped ) return true
141+ return [" NONE" , " STOPPED" , " KILLED" , " ERROR" ].includes (actionHeader .currentStatus )
142+ }
143+
144+ Row {
145+ id: actionItemsRow
146+ anchors .centerIn : parent
147+ spacing: 2
148+
149+ // Compute button
150+ MaterialToolButton {
151+ id: computeButton
152+ font .pointSize : 16
153+ text: actionHeader .canBeStopped () ? MaterialIcons .cancel_schedule_send : MaterialIcons .send
154+ padding: 6
155+ ToolTip .text : " Start/Stop Compute"
156+ ToolTip .visible : hovered
157+ ToolTip .delay : 1000
158+ enabled: actionHeader .selectedNode && actionHeader .canBeLaunched ()
159+ background: Rectangle {
160+ color: {
161+ if (! computeButton .enabled ) return activePalette .button
162+ if (actionHeader .currentStatus === " RUNNING" ) {
163+ if (computeButton .hovered ) return Colors .statusColors [" STOPPED" ]
164+ return Qt .darker (Colors .statusColors [" STOPPED" ], 1.3 )
165+ } else {
166+ if (computeButton .hovered ) return activePalette .highlight
167+ return activePalette .button
168+ }
169+ }
170+ opacity: computeButton .hovered ? 1 : root ._opacity
171+ border .color : computeButton .hovered ? activePalette .highlight : Qt .darker (activePalette .window , 1.3 )
172+ border .width : 1
173+ radius: 3
174+ }
175+ onClicked: {
176+ if (actionHeader .isRunningLocally ()) {
177+ root .stopComputeRequest (actionHeader .selectedNode )
178+ } else {
179+ root .computeRequest (actionHeader .selectedNode )
180+ }
181+ }
182+ }
183+
184+ // Re-compute button : stop local process and relaunch locally
185+ MaterialToolButton {
186+ id: reComputeButton
187+ font .pointSize : 16
188+ text: MaterialIcons .autorenew
189+ padding: 6
190+ ToolTip .text : " Re-compute"
191+ ToolTip .visible : hovered
192+ ToolTip .delay : 1000
193+ enabled: actionHeader .selectedNode && ! actionHeader .isRunningExternally ()
194+ background: Rectangle {
195+ color: {
196+ if (! reComputeButton .enabled ) return activePalette .button
197+ if (reComputeButton .hovered ) return activePalette .highlight ;
198+ return activePalette .button ;
199+ }
200+ opacity: reComputeButton .hovered ? 1 : root ._opacity
201+ border .color : reComputeButton .hovered ? activePalette .highlight : Qt .darker (activePalette .window , 1.3 )
202+ border .width : 1
203+ radius: 3
204+ }
205+ onClicked: {
206+ if (actionHeader .selectedNode ) {
207+ root .reComputeRequest (actionHeader .selectedNode )
208+ }
209+ }
210+ }
211+
212+ // Submit button
213+ MaterialToolButton {
214+ id: submitButton
215+ font .pointSize : 16
216+ text: MaterialIcons .rocket_launch
217+ padding: 6
218+ ToolTip .text : " Submit on Render Farm"
219+ ToolTip .visible : hovered
220+ ToolTip .delay : 1000
221+ visible: root .uigraph ? root .uigraph .canSubmit : false
222+ enabled: actionHeader .selectedNode ? ! actionHeader .selectedNode .locked : false
223+
224+ background: Rectangle {
225+ color: {
226+ if (! submitButton .enabled ) return activePalette .button
227+ if (actionHeader .isRunningExternally ()) return Colors .statusColors [" SUBMITTED" ];
228+ if (submitButton .hovered ) return activePalette .highlight ;
229+ return activePalette .button ;
230+ }
231+ opacity: submitButton .hovered ? 1 : root ._opacity
232+ border .color : submitButton .hovered ? activePalette .highlight : Qt .darker (activePalette .window , 1.3 )
233+ border .width : 1
234+ radius: 3
235+ }
236+ onClicked: {
237+ if (actionHeader .selectedNode ) {
238+ root .submitRequest (actionHeader .selectedNode )
239+ }
240+ }
241+ }
242+
243+ // Re-submit button : stop everything and relaunch on farm
244+ // TODO : disabled for now because we can't stop jobs submitted on farm
245+ MaterialToolButton {
246+ id: reSubmitButton
247+ font .pointSize : 16
248+ text: MaterialIcons .double_arrow
249+ padding: 6
250+ ToolTip .text : " Re-submit on Render Farm"
251+ ToolTip .visible : hovered
252+ ToolTip .delay : 1000
253+ visible: false // root.uigraph ? root.uigraph.canSubmit : false
254+ enabled: actionHeader .selectedNode !== null
255+ background: Rectangle {
256+ color: {
257+ if (! actionHeader .selectedNode ) return activePalette .button
258+ if (reSubmitButton .hovered ) return activePalette .highlight ;
259+ return activePalette .button ;
260+ }
261+ opacity: reSubmitButton .hovered ? 1 : root ._opacity
262+ border .color : reSubmitButton .hovered ? activePalette .highlight : Qt .darker (activePalette .window , 1.3 )
263+ border .width : 1
264+ radius: 3
265+ }
266+ onClicked: {
267+ if (actionHeader .selectedNode ) {
268+ root .reSubmitRequest (actionHeader .selectedNode )
269+ }
270+ }
271+ }
272+ }
273+ }
274+ }
0 commit comments