ExecutionPage.qml 21 KB

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