PatternDetailPage.qml 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.15
  4. import DuneWeaver 1.0
  5. import "../components"
  6. import "../components" as Components
  7. Page {
  8. id: page
  9. property string patternName: ""
  10. property string patternPath: ""
  11. property string patternPreview: ""
  12. property var backend: null
  13. property bool showAddedFeedback: false
  14. // Playlist model for selecting which playlist to add to
  15. PlaylistModel {
  16. id: playlistModel
  17. }
  18. Rectangle {
  19. anchors.fill: parent
  20. color: Components.ThemeManager.backgroundColor
  21. }
  22. ColumnLayout {
  23. anchors.fill: parent
  24. spacing: 0
  25. // Header
  26. Rectangle {
  27. Layout.fillWidth: true
  28. Layout.preferredHeight: 50
  29. color: Components.ThemeManager.surfaceColor
  30. // Bottom border
  31. Rectangle {
  32. anchors.bottom: parent.bottom
  33. width: parent.width
  34. height: 1
  35. color: Components.ThemeManager.borderColor
  36. }
  37. RowLayout {
  38. anchors.fill: parent
  39. anchors.margins: 10
  40. ConnectionStatus {
  41. backend: page.backend
  42. Layout.rightMargin: 8
  43. }
  44. Button {
  45. text: "← Back"
  46. font.pixelSize: 14
  47. flat: true
  48. onClicked: stackView.pop()
  49. contentItem: Text {
  50. text: parent.text
  51. font: parent.font
  52. color: Components.ThemeManager.textPrimary
  53. horizontalAlignment: Text.AlignHCenter
  54. verticalAlignment: Text.AlignVCenter
  55. }
  56. }
  57. Label {
  58. text: patternName
  59. Layout.fillWidth: true
  60. elide: Label.ElideRight
  61. font.pixelSize: 16
  62. font.bold: true
  63. color: Components.ThemeManager.textPrimary
  64. }
  65. }
  66. }
  67. // Content - Side by side layout
  68. Item {
  69. Layout.fillWidth: true
  70. Layout.fillHeight: true
  71. Row {
  72. anchors.fill: parent
  73. spacing: 0
  74. // Left side - Pattern Preview (60% of width)
  75. Rectangle {
  76. width: parent.width * 0.6
  77. height: parent.height
  78. color: Components.ThemeManager.previewBackground
  79. Image {
  80. anchors.fill: parent
  81. anchors.margins: 10
  82. source: patternPreview ? "file:///" + patternPreview : ""
  83. fillMode: Image.PreserveAspectFit
  84. Rectangle {
  85. anchors.fill: parent
  86. color: Components.ThemeManager.placeholderBackground
  87. visible: parent.status === Image.Error || parent.source == ""
  88. Column {
  89. anchors.centerIn: parent
  90. spacing: 10
  91. Text {
  92. text: "○"
  93. font.pixelSize: 48
  94. color: Components.ThemeManager.placeholderText
  95. anchors.horizontalCenter: parent.horizontalCenter
  96. }
  97. Text {
  98. text: "No Preview Available"
  99. color: Components.ThemeManager.textSecondary
  100. font.pixelSize: 14
  101. anchors.horizontalCenter: parent.horizontalCenter
  102. }
  103. }
  104. }
  105. }
  106. }
  107. // Divider
  108. Rectangle {
  109. width: 1
  110. height: parent.height
  111. color: Components.ThemeManager.borderColor
  112. }
  113. // Right side - Controls (40% of width)
  114. Rectangle {
  115. width: parent.width * 0.4 - 1
  116. height: parent.height
  117. color: Components.ThemeManager.surfaceColor
  118. Column {
  119. anchors.fill: parent
  120. anchors.margins: 10
  121. spacing: 15
  122. // Play Button - FIRST AND PROMINENT
  123. Rectangle {
  124. width: parent.width
  125. height: 50
  126. radius: 8
  127. color: playMouseArea.pressed ? "#1e40af" : (backend ? "#2563eb" : "#9ca3af")
  128. Text {
  129. anchors.centerIn: parent
  130. text: "▶ Play Pattern"
  131. color: "white"
  132. font.pixelSize: 16
  133. font.bold: true
  134. }
  135. MouseArea {
  136. id: playMouseArea
  137. anchors.fill: parent
  138. enabled: backend !== null
  139. onClicked: {
  140. if (backend) {
  141. var preExecution = "adaptive"
  142. if (centerRadio.checked) preExecution = "clear_center"
  143. else if (perimeterRadio.checked) preExecution = "clear_perimeter"
  144. else if (noneRadio.checked) preExecution = "none"
  145. backend.executePattern(patternName, preExecution)
  146. }
  147. }
  148. }
  149. }
  150. // Add to Playlist Button
  151. Rectangle {
  152. width: parent.width
  153. height: 45
  154. radius: 8
  155. color: addToPlaylistArea.pressed ? "#065f46" : "#059669"
  156. Row {
  157. anchors.centerIn: parent
  158. spacing: 8
  159. Text {
  160. text: showAddedFeedback ? "✓" : "♪"
  161. font.pixelSize: 16
  162. color: "white"
  163. }
  164. Text {
  165. text: showAddedFeedback ? "Added!" : "Add to Playlist"
  166. color: "white"
  167. font.pixelSize: 14
  168. font.bold: true
  169. }
  170. }
  171. MouseArea {
  172. id: addToPlaylistArea
  173. anchors.fill: parent
  174. enabled: backend !== null && !showAddedFeedback
  175. onClicked: {
  176. playlistModel.refresh() // Refresh playlist list
  177. playlistSelectorPopup.open()
  178. }
  179. }
  180. }
  181. // Pre-Execution Options
  182. Rectangle {
  183. width: parent.width
  184. height: 160 // Increased height to fit all options
  185. radius: 8
  186. color: Components.ThemeManager.cardColor
  187. border.color: Components.ThemeManager.borderColor
  188. border.width: 1
  189. Column {
  190. id: preExecColumn
  191. anchors.left: parent.left
  192. anchors.right: parent.right
  193. anchors.top: parent.top
  194. anchors.margins: 8 // Reduced margins to save space
  195. spacing: 6 // Reduced spacing
  196. Label {
  197. text: "Pre-Execution"
  198. font.pixelSize: 12
  199. font.bold: true
  200. color: Components.ThemeManager.textPrimary
  201. }
  202. RadioButton {
  203. id: adaptiveRadio
  204. text: "Adaptive"
  205. checked: true
  206. font.pixelSize: 10
  207. contentItem: Text {
  208. text: parent.text
  209. font: parent.font
  210. color: Components.ThemeManager.textPrimary
  211. verticalAlignment: Text.AlignVCenter
  212. leftPadding: parent.indicator.width + parent.spacing
  213. }
  214. }
  215. RadioButton {
  216. id: centerRadio
  217. text: "Clear Center"
  218. font.pixelSize: 10
  219. contentItem: Text {
  220. text: parent.text
  221. font: parent.font
  222. color: Components.ThemeManager.textPrimary
  223. verticalAlignment: Text.AlignVCenter
  224. leftPadding: parent.indicator.width + parent.spacing
  225. }
  226. }
  227. RadioButton {
  228. id: perimeterRadio
  229. text: "Clear Edge"
  230. font.pixelSize: 10
  231. contentItem: Text {
  232. text: parent.text
  233. font: parent.font
  234. color: Components.ThemeManager.textPrimary
  235. verticalAlignment: Text.AlignVCenter
  236. leftPadding: parent.indicator.width + parent.spacing
  237. }
  238. }
  239. RadioButton {
  240. id: noneRadio
  241. text: "None"
  242. font.pixelSize: 10
  243. contentItem: Text {
  244. text: parent.text
  245. font: parent.font
  246. color: Components.ThemeManager.textPrimary
  247. verticalAlignment: Text.AlignVCenter
  248. leftPadding: parent.indicator.width + parent.spacing
  249. }
  250. }
  251. }
  252. }
  253. // Pattern Info
  254. Rectangle {
  255. width: parent.width
  256. height: 80
  257. radius: 8
  258. color: Components.ThemeManager.cardColor
  259. border.color: Components.ThemeManager.borderColor
  260. border.width: 1
  261. Column {
  262. id: infoColumn
  263. anchors.left: parent.left
  264. anchors.right: parent.right
  265. anchors.top: parent.top
  266. anchors.margins: 10
  267. spacing: 6
  268. Label {
  269. text: "Pattern Info"
  270. font.pixelSize: 14
  271. font.bold: true
  272. color: Components.ThemeManager.textPrimary
  273. }
  274. Label {
  275. text: "Name: " + patternName
  276. font.pixelSize: 11
  277. color: Components.ThemeManager.textSecondary
  278. elide: Text.ElideRight
  279. width: parent.width
  280. }
  281. Label {
  282. text: "Type: Sand Pattern"
  283. font.pixelSize: 11
  284. color: Components.ThemeManager.textSecondary
  285. }
  286. }
  287. }
  288. }
  289. }
  290. }
  291. }
  292. }
  293. // ==================== Playlist Selector Popup ====================
  294. Popup {
  295. id: playlistSelectorPopup
  296. modal: true
  297. x: (parent.width - width) / 2
  298. y: (parent.height - height) / 2
  299. width: 320
  300. height: Math.min(400, 120 + playlistModel.rowCount() * 50)
  301. closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
  302. background: Rectangle {
  303. color: Components.ThemeManager.surfaceColor
  304. radius: 16
  305. border.color: Components.ThemeManager.borderColor
  306. border.width: 1
  307. }
  308. contentItem: ColumnLayout {
  309. anchors.fill: parent
  310. anchors.margins: 15
  311. spacing: 10
  312. Label {
  313. text: "Add to Playlist"
  314. font.pixelSize: 18
  315. font.bold: true
  316. color: Components.ThemeManager.textPrimary
  317. Layout.alignment: Qt.AlignHCenter
  318. }
  319. // Playlist list
  320. ListView {
  321. Layout.fillWidth: true
  322. Layout.fillHeight: true
  323. clip: true
  324. model: playlistModel
  325. spacing: 6
  326. delegate: Rectangle {
  327. width: ListView.view.width
  328. height: 45
  329. radius: 8
  330. color: playlistItemArea.pressed ? Components.ThemeManager.selectedBackground : Components.ThemeManager.cardColor
  331. border.color: Components.ThemeManager.borderColor
  332. border.width: 1
  333. RowLayout {
  334. anchors.fill: parent
  335. anchors.margins: 12
  336. spacing: 10
  337. Text {
  338. text: "♪"
  339. font.pixelSize: 16
  340. color: "#2196F3"
  341. }
  342. Label {
  343. text: model.name
  344. font.pixelSize: 14
  345. color: Components.ThemeManager.textPrimary
  346. Layout.fillWidth: true
  347. elide: Text.ElideRight
  348. }
  349. Label {
  350. text: model.itemCount + " patterns"
  351. font.pixelSize: 11
  352. color: Components.ThemeManager.textTertiary
  353. }
  354. }
  355. MouseArea {
  356. id: playlistItemArea
  357. anchors.fill: parent
  358. onClicked: {
  359. if (backend) {
  360. backend.addPatternToPlaylist(model.name, patternName)
  361. }
  362. playlistSelectorPopup.close()
  363. }
  364. }
  365. }
  366. }
  367. // Empty state
  368. Column {
  369. Layout.fillWidth: true
  370. Layout.fillHeight: true
  371. spacing: 10
  372. visible: playlistModel.rowCount() === 0
  373. Item { Layout.fillHeight: true }
  374. Text {
  375. text: "♪"
  376. font.pixelSize: 32
  377. color: Components.ThemeManager.placeholderText
  378. anchors.horizontalCenter: parent.horizontalCenter
  379. }
  380. Label {
  381. text: "No playlists yet"
  382. anchors.horizontalCenter: parent.horizontalCenter
  383. color: Components.ThemeManager.textSecondary
  384. font.pixelSize: 14
  385. }
  386. Label {
  387. text: "Create a playlist first"
  388. anchors.horizontalCenter: parent.horizontalCenter
  389. color: Components.ThemeManager.textTertiary
  390. font.pixelSize: 12
  391. }
  392. Item { Layout.fillHeight: true }
  393. }
  394. // Cancel button
  395. Rectangle {
  396. Layout.fillWidth: true
  397. Layout.preferredHeight: 40
  398. radius: 8
  399. color: cancelArea.pressed ? Components.ThemeManager.buttonBackgroundHover : Components.ThemeManager.cardColor
  400. border.color: Components.ThemeManager.borderColor
  401. border.width: 1
  402. Text {
  403. anchors.centerIn: parent
  404. text: "Cancel"
  405. color: Components.ThemeManager.textPrimary
  406. font.pixelSize: 14
  407. }
  408. MouseArea {
  409. id: cancelArea
  410. anchors.fill: parent
  411. onClicked: playlistSelectorPopup.close()
  412. }
  413. }
  414. }
  415. }
  416. // ==================== Backend Signal Handlers ====================
  417. Connections {
  418. target: backend
  419. function onPatternAddedToPlaylist(success, message) {
  420. if (success) {
  421. // Show feedback
  422. showAddedFeedback = true
  423. feedbackTimer.start()
  424. }
  425. }
  426. }
  427. Timer {
  428. id: feedbackTimer
  429. interval: 2000 // Show "Added!" for 2 seconds
  430. onTriggered: showAddedFeedback = false
  431. }
  432. }