ExecutionPage.qml 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.15
  4. import Qt.labs.folderlistmodel 2.15
  5. import "../components"
  6. import "../components" as Components
  7. Page {
  8. id: page
  9. property var backend: null
  10. property var stackView: null
  11. property string patternName: ""
  12. property string patternPreview: "" // Backend provides this via executionStarted signal
  13. // Debug backend connection
  14. onBackendChanged: {
  15. console.log("ExecutionPage: backend changed to", backend)
  16. if (backend) {
  17. console.log("ExecutionPage: backend.serialConnected =", backend.serialConnected)
  18. console.log("ExecutionPage: backend.isConnected =", backend.isConnected)
  19. }
  20. }
  21. Component.onCompleted: {
  22. console.log("ExecutionPage: Component completed, backend =", backend)
  23. if (backend) {
  24. console.log("ExecutionPage: initial serialConnected =", backend.serialConnected)
  25. }
  26. }
  27. // Direct connection to backend signals
  28. Connections {
  29. target: backend
  30. function onSerialConnectionChanged(connected) {
  31. console.log("ExecutionPage: received serialConnectionChanged signal:", connected)
  32. }
  33. function onConnectionChanged() {
  34. console.log("ExecutionPage: received connectionChanged signal")
  35. if (backend) {
  36. console.log("ExecutionPage: after connectionChanged, serialConnected =", backend.serialConnected)
  37. }
  38. }
  39. function onExecutionStarted(fileName, preview) {
  40. console.log("🎯 ExecutionPage: executionStarted signal received!")
  41. console.log("🎯 Pattern:", fileName)
  42. console.log("🎯 Preview path:", preview)
  43. // Update preview directly from backend signal
  44. patternName = fileName
  45. patternPreview = preview
  46. }
  47. }
  48. Rectangle {
  49. anchors.fill: parent
  50. color: Components.ThemeManager.backgroundColor
  51. }
  52. ColumnLayout {
  53. anchors.fill: parent
  54. spacing: 0
  55. // Header (consistent with other pages)
  56. Rectangle {
  57. Layout.fillWidth: true
  58. Layout.preferredHeight: 50
  59. color: Components.ThemeManager.surfaceColor
  60. // Bottom border
  61. Rectangle {
  62. anchors.bottom: parent.bottom
  63. width: parent.width
  64. height: 1
  65. color: Components.ThemeManager.borderColor
  66. }
  67. RowLayout {
  68. anchors.fill: parent
  69. anchors.leftMargin: 15
  70. anchors.rightMargin: 10
  71. ConnectionStatus {
  72. backend: page.backend
  73. Layout.rightMargin: 8
  74. }
  75. Label {
  76. text: "Pattern Execution"
  77. font.pixelSize: 18
  78. font.bold: true
  79. color: Components.ThemeManager.textPrimary
  80. }
  81. Item {
  82. Layout.fillWidth: true
  83. }
  84. }
  85. }
  86. // Content - Side by side layout
  87. Item {
  88. Layout.fillWidth: true
  89. Layout.fillHeight: true
  90. Row {
  91. anchors.fill: parent
  92. spacing: 0
  93. // Left side - Pattern Preview (60% of width)
  94. Rectangle {
  95. width: parent.width * 0.6
  96. height: parent.height
  97. color: Components.ThemeManager.previewBackground
  98. Image {
  99. anchors.fill: parent
  100. anchors.margins: 10
  101. source: {
  102. var finalSource = ""
  103. // Trust the backend's preview path - it already has recursive search
  104. if (patternPreview) {
  105. // Backend returns absolute path, just add file:// prefix
  106. finalSource = "file://" + patternPreview
  107. console.log("🖼️ Using backend patternPreview:", finalSource)
  108. } else {
  109. console.log("🖼️ No preview from backend")
  110. }
  111. return finalSource
  112. }
  113. fillMode: Image.PreserveAspectFit
  114. onStatusChanged: {
  115. console.log("📷 Image status:", status, "for source:", source)
  116. if (status === Image.Error) {
  117. console.log("❌ Image failed to load:", source)
  118. } else if (status === Image.Ready) {
  119. console.log("✅ Image loaded successfully:", source)
  120. } else if (status === Image.Loading) {
  121. console.log("🔄 Image loading:", source)
  122. }
  123. }
  124. onSourceChanged: {
  125. console.log("🔄 Image source changed to:", source)
  126. }
  127. Rectangle {
  128. anchors.fill: parent
  129. color: Components.ThemeManager.placeholderBackground
  130. visible: parent.status === Image.Error || parent.source == ""
  131. Column {
  132. anchors.centerIn: parent
  133. spacing: 10
  134. Text {
  135. text: "⚙"
  136. font.pixelSize: 48
  137. color: Components.ThemeManager.placeholderText
  138. anchors.horizontalCenter: parent.horizontalCenter
  139. }
  140. Text {
  141. text: "Pattern Preview"
  142. color: Components.ThemeManager.textTertiary
  143. font.pixelSize: 14
  144. anchors.horizontalCenter: parent.horizontalCenter
  145. }
  146. }
  147. }
  148. }
  149. }
  150. // Divider
  151. Rectangle {
  152. width: 1
  153. height: parent.height
  154. color: Components.ThemeManager.borderColor
  155. }
  156. // Right side - Controls (40% of width)
  157. Rectangle {
  158. width: parent.width * 0.4 - 1
  159. height: parent.height
  160. color: Components.ThemeManager.surfaceColor
  161. ScrollView {
  162. anchors.fill: parent
  163. anchors.margins: 10
  164. clip: true
  165. contentWidth: availableWidth
  166. Column {
  167. width: parent.width
  168. spacing: 8
  169. // Pattern Name
  170. Rectangle {
  171. width: parent.width
  172. height: 50
  173. radius: 8
  174. color: Components.ThemeManager.cardColor
  175. border.color: Components.ThemeManager.borderColor
  176. border.width: 1
  177. Column {
  178. anchors.centerIn: parent
  179. spacing: 4
  180. Label {
  181. text: "Current Pattern"
  182. font.pixelSize: 10
  183. color: Components.ThemeManager.textSecondary
  184. anchors.horizontalCenter: parent.horizontalCenter
  185. }
  186. Label {
  187. text: {
  188. // Use WebSocket current pattern first, then fallback to passed parameter
  189. var displayName = ""
  190. if (backend && backend.currentFile) displayName = backend.currentFile
  191. else if (patternName) displayName = patternName
  192. else return "No pattern running"
  193. // Clean up the name for display
  194. var parts = displayName.split('/')
  195. displayName = parts[parts.length - 1]
  196. displayName = displayName.replace('.thr', '')
  197. return displayName
  198. }
  199. font.pixelSize: 12
  200. font.bold: true
  201. color: Components.ThemeManager.textPrimary
  202. anchors.horizontalCenter: parent.horizontalCenter
  203. width: parent.parent.width - 20
  204. elide: Text.ElideMiddle
  205. horizontalAlignment: Text.AlignHCenter
  206. }
  207. }
  208. }
  209. // Progress
  210. Rectangle {
  211. width: parent.width
  212. height: 70
  213. radius: 8
  214. color: Components.ThemeManager.cardColor
  215. border.color: Components.ThemeManager.borderColor
  216. border.width: 1
  217. Column {
  218. anchors.fill: parent
  219. anchors.margins: 10
  220. spacing: 8
  221. Label {
  222. text: "Progress"
  223. font.pixelSize: 12
  224. font.bold: true
  225. color: Components.ThemeManager.textPrimary
  226. }
  227. ProgressBar {
  228. width: parent.width
  229. height: 8
  230. value: backend ? backend.progress / 100 : 0
  231. }
  232. Label {
  233. text: backend ? Math.round(backend.progress) + "%" : "0%"
  234. anchors.horizontalCenter: parent.horizontalCenter
  235. font.pixelSize: 14
  236. font.bold: true
  237. color: Components.ThemeManager.textPrimary
  238. }
  239. }
  240. }
  241. // Control Buttons
  242. Rectangle {
  243. width: parent.width
  244. height: 90
  245. radius: 8
  246. color: Components.ThemeManager.cardColor
  247. border.color: Components.ThemeManager.borderColor
  248. border.width: 1
  249. Column {
  250. anchors.fill: parent
  251. anchors.margins: 10
  252. spacing: 10
  253. Label {
  254. text: "Controls"
  255. font.pixelSize: 12
  256. font.bold: true
  257. color: Components.ThemeManager.textPrimary
  258. }
  259. // Control buttons row
  260. Row {
  261. width: parent.width
  262. height: 35
  263. spacing: 8
  264. // Pause/Resume button
  265. Rectangle {
  266. width: (parent.width - 16) / 3 // Divide width evenly with spacing
  267. height: parent.height
  268. radius: 6
  269. color: pauseMouseArea.pressed ? "#1e40af" : (backend && backend.currentFile !== "" ? "#2563eb" : "#9ca3af")
  270. Text {
  271. anchors.centerIn: parent
  272. // Show pause icon when running and not paused, play icon when paused
  273. text: (backend && backend.isRunning && !backend.isPaused) ? "||" : "▶"
  274. color: "white"
  275. font.pixelSize: 14
  276. font.bold: true
  277. }
  278. MouseArea {
  279. id: pauseMouseArea
  280. anchors.fill: parent
  281. enabled: backend && backend.currentFile !== ""
  282. onClicked: {
  283. if (backend) {
  284. // If paused, resume; otherwise pause
  285. if (backend.isPaused) {
  286. backend.resumeExecution()
  287. } else {
  288. backend.pauseExecution()
  289. }
  290. }
  291. }
  292. }
  293. }
  294. // Stop button
  295. Rectangle {
  296. width: (parent.width - 16) / 3
  297. height: parent.height
  298. radius: 6
  299. color: stopMouseArea.pressed ? "#b91c1c" : (backend && backend.currentFile !== "" ? "#dc2626" : "#9ca3af")
  300. Text {
  301. anchors.centerIn: parent
  302. text: "■"
  303. color: "white"
  304. font.pixelSize: 14
  305. font.bold: true
  306. }
  307. MouseArea {
  308. id: stopMouseArea
  309. anchors.fill: parent
  310. enabled: backend
  311. onClicked: {
  312. if (backend) {
  313. backend.stopExecution()
  314. }
  315. }
  316. }
  317. }
  318. // Skip button
  319. Rectangle {
  320. width: (parent.width - 16) / 3
  321. height: parent.height
  322. radius: 6
  323. color: skipMouseArea.pressed ? "#525252" : (backend && backend.currentFile !== "" ? "#6b7280" : "#9ca3af")
  324. Text {
  325. anchors.centerIn: parent
  326. text: "▶▶"
  327. color: "white"
  328. font.pixelSize: 14
  329. font.bold: true
  330. }
  331. MouseArea {
  332. id: skipMouseArea
  333. anchors.fill: parent
  334. enabled: backend
  335. onClicked: {
  336. if (backend) {
  337. backend.skipPattern()
  338. }
  339. }
  340. }
  341. }
  342. }
  343. }
  344. }
  345. // Speed Control Section
  346. Rectangle {
  347. width: parent.width
  348. height: 120
  349. radius: 8
  350. color: Components.ThemeManager.cardColor
  351. border.color: Components.ThemeManager.borderColor
  352. border.width: 1
  353. Column {
  354. anchors.fill: parent
  355. anchors.margins: 10
  356. spacing: 10
  357. Label {
  358. text: "Speed"
  359. font.pixelSize: 12
  360. font.bold: true
  361. color: Components.ThemeManager.textPrimary
  362. }
  363. // Touch-friendly button row for speed options
  364. Row {
  365. id: speedControlRow
  366. width: parent.width
  367. spacing: 8
  368. property string currentSelection: backend ? backend.getCurrentSpeedOption() : "200"
  369. // Speed buttons
  370. Repeater {
  371. model: ["100", "150", "200", "300", "500"]
  372. Rectangle {
  373. width: (speedControlRow.width - 32) / 5 // Distribute evenly with spacing
  374. height: 50
  375. color: speedControlRow.currentSelection === modelData ? Components.ThemeManager.selectedBackground : Components.ThemeManager.buttonBackground
  376. border.color: speedControlRow.currentSelection === modelData ? Components.ThemeManager.selectedBorder : Components.ThemeManager.buttonBorder
  377. border.width: 2
  378. radius: 8
  379. Label {
  380. anchors.centerIn: parent
  381. text: modelData
  382. font.pixelSize: 12
  383. font.bold: true
  384. color: speedControlRow.currentSelection === modelData ? "white" : Components.ThemeManager.textPrimary
  385. }
  386. MouseArea {
  387. anchors.fill: parent
  388. onClicked: {
  389. if (backend) {
  390. backend.setSpeedByOption(modelData)
  391. speedControlRow.currentSelection = modelData
  392. }
  393. }
  394. }
  395. }
  396. }
  397. // Update selection when backend changes
  398. Connections {
  399. target: backend
  400. function onSpeedChanged(speed) {
  401. if (backend) {
  402. speedControlRow.currentSelection = backend.getCurrentSpeedOption()
  403. }
  404. }
  405. }
  406. }
  407. }
  408. }
  409. }
  410. }
  411. }
  412. }
  413. }
  414. }
  415. }