ModernPatternListPage.qml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.15
  4. import "../components"
  5. import "../components" as Components
  6. Page {
  7. id: page
  8. property var patternModel
  9. property var backend
  10. property var stackView
  11. property bool searchExpanded: false
  12. property bool isRefreshing: false
  13. property int patternCount: patternModel ? patternModel.rowCount() : 0
  14. // Handle pattern refresh completion from backend
  15. Connections {
  16. target: backend
  17. function onPatternsRefreshCompleted(success, message) {
  18. console.log("🔄 Pattern refresh completed:", success, message)
  19. if (patternModel) {
  20. patternModel.refresh()
  21. }
  22. isRefreshing = false
  23. }
  24. }
  25. // Update pattern count when model resets (rowCount() is not reactive)
  26. Connections {
  27. target: patternModel
  28. function onModelReset() {
  29. patternCount = patternModel.rowCount()
  30. }
  31. }
  32. Rectangle {
  33. anchors.fill: parent
  34. color: Components.ThemeManager.backgroundColor
  35. }
  36. ColumnLayout {
  37. anchors.fill: parent
  38. spacing: 0
  39. // Header with integrated search
  40. Rectangle {
  41. Layout.fillWidth: true
  42. Layout.preferredHeight: 50
  43. color: Components.ThemeManager.surfaceColor
  44. // Bottom border
  45. Rectangle {
  46. anchors.bottom: parent.bottom
  47. width: parent.width
  48. height: 1
  49. color: Components.ThemeManager.borderColor
  50. }
  51. RowLayout {
  52. anchors.fill: parent
  53. anchors.leftMargin: 15
  54. anchors.rightMargin: 10
  55. ConnectionStatus {
  56. backend: page.backend
  57. Layout.rightMargin: 8
  58. visible: !searchExpanded
  59. }
  60. Label {
  61. text: "Browse Patterns"
  62. font.pixelSize: 18
  63. font.bold: true
  64. color: Components.ThemeManager.textPrimary
  65. visible: !searchExpanded
  66. }
  67. // Pattern count
  68. Label {
  69. text: patternCount + " patterns"
  70. font.pixelSize: 12
  71. color: Components.ThemeManager.textTertiary
  72. visible: !searchExpanded
  73. }
  74. // Refresh button
  75. Rectangle {
  76. Layout.preferredWidth: 32
  77. Layout.preferredHeight: 32
  78. radius: 16
  79. color: refreshMouseArea.pressed ? Components.ThemeManager.buttonBackgroundHover :
  80. (refreshMouseArea.containsMouse ? Components.ThemeManager.cardColor : "transparent")
  81. visible: !searchExpanded
  82. Text {
  83. id: refreshIcon
  84. anchors.centerIn: parent
  85. text: "↻"
  86. font.pixelSize: 16
  87. color: isRefreshing ? Components.ThemeManager.accentBlue : Components.ThemeManager.textSecondary
  88. SequentialAnimation on opacity {
  89. running: isRefreshing
  90. loops: Animation.Infinite
  91. NumberAnimation { to: 0.4; duration: 500 }
  92. NumberAnimation { to: 1.0; duration: 500 }
  93. }
  94. }
  95. MouseArea {
  96. id: refreshMouseArea
  97. anchors.fill: parent
  98. hoverEnabled: true
  99. enabled: !isRefreshing
  100. onClicked: {
  101. if (backend) {
  102. isRefreshing = true
  103. backend.refreshPatterns()
  104. }
  105. }
  106. }
  107. }
  108. Item {
  109. Layout.fillWidth: true
  110. visible: !searchExpanded
  111. }
  112. // Expandable search
  113. Rectangle {
  114. Layout.fillWidth: searchExpanded
  115. Layout.preferredWidth: searchExpanded ? parent.width - 60 : 120
  116. Layout.preferredHeight: 32
  117. radius: 16
  118. color: searchExpanded ? Components.ThemeManager.surfaceColor : Components.ThemeManager.cardColor
  119. border.color: searchExpanded ? "#2563eb" : Components.ThemeManager.borderColor
  120. border.width: 1
  121. Behavior on Layout.preferredWidth {
  122. NumberAnimation { duration: 200 }
  123. }
  124. RowLayout {
  125. anchors.fill: parent
  126. anchors.leftMargin: 10
  127. anchors.rightMargin: 10
  128. spacing: 5
  129. Text {
  130. text: "⌕"
  131. font.pixelSize: 16
  132. font.family: "sans-serif"
  133. color: searchExpanded ? "#2563eb" : Components.ThemeManager.textSecondary
  134. }
  135. TextField {
  136. id: searchField
  137. Layout.fillWidth: true
  138. placeholderText: searchExpanded ? "Search patterns... (press Enter)" : "Search"
  139. placeholderTextColor: Components.ThemeManager.textTertiary
  140. font.pixelSize: 14
  141. color: Components.ThemeManager.textPrimary
  142. visible: searchExpanded || text.length > 0
  143. property string lastSearchText: ""
  144. property bool hasUnappliedSearch: text !== lastSearchText && text.length > 0
  145. background: Rectangle {
  146. color: "transparent"
  147. border.color: searchField.hasUnappliedSearch ? "#f59e0b" : "transparent"
  148. border.width: searchField.hasUnappliedSearch ? 1 : 0
  149. radius: 4
  150. }
  151. // Remove automatic filtering on text change
  152. // onTextChanged: patternModel.filter(text)
  153. // Only filter when user presses Enter or field loses focus
  154. onAccepted: {
  155. patternModel.filter(text)
  156. lastSearchText = text
  157. Qt.inputMethod.hide()
  158. focus = false
  159. }
  160. // Enable virtual keyboard
  161. activeFocusOnPress: true
  162. selectByMouse: true
  163. inputMethodHints: Qt.ImhNoPredictiveText
  164. // Direct MouseArea for touch events
  165. MouseArea {
  166. anchors.fill: parent
  167. onPressed: {
  168. searchField.forceActiveFocus()
  169. Qt.inputMethod.show()
  170. mouse.accepted = false // Pass through to TextField
  171. }
  172. }
  173. onActiveFocusChanged: {
  174. if (activeFocus) {
  175. searchExpanded = true
  176. // Force virtual keyboard to show
  177. Qt.inputMethod.show()
  178. } else {
  179. // Apply search when focus is lost
  180. if (text !== lastSearchText) {
  181. patternModel.filter(text)
  182. lastSearchText = text
  183. }
  184. }
  185. }
  186. // Handle Enter key - triggers onAccepted
  187. Keys.onReturnPressed: {
  188. // onAccepted will be called automatically
  189. // Just hide keyboard and unfocus
  190. Qt.inputMethod.hide()
  191. focus = false
  192. }
  193. Keys.onEscapePressed: {
  194. // Clear search and hide keyboard
  195. text = ""
  196. lastSearchText = ""
  197. patternModel.filter("")
  198. Qt.inputMethod.hide()
  199. focus = false
  200. }
  201. }
  202. Text {
  203. text: searchExpanded || searchField.text.length > 0 ? "Search" : ""
  204. font.pixelSize: 12
  205. color: Components.ThemeManager.textTertiary
  206. visible: !searchExpanded && searchField.text.length === 0
  207. }
  208. }
  209. MouseArea {
  210. anchors.fill: parent
  211. enabled: !searchExpanded
  212. onClicked: {
  213. searchExpanded = true
  214. searchField.forceActiveFocus()
  215. Qt.inputMethod.show()
  216. }
  217. }
  218. }
  219. // Close button when expanded
  220. Button {
  221. text: "✕"
  222. font.pixelSize: 18
  223. flat: true
  224. visible: searchExpanded
  225. Layout.preferredWidth: 32
  226. Layout.preferredHeight: 32
  227. onClicked: {
  228. searchExpanded = false
  229. searchField.text = ""
  230. searchField.lastSearchText = ""
  231. searchField.focus = false
  232. // Clear the filter when closing search
  233. patternModel.filter("")
  234. }
  235. }
  236. }
  237. }
  238. // Content - Pattern Grid
  239. GridView {
  240. id: gridView
  241. Layout.fillWidth: true
  242. Layout.fillHeight: true
  243. cellWidth: 200
  244. cellHeight: 220
  245. model: patternModel
  246. clip: true
  247. // Add smooth scrolling
  248. ScrollBar.vertical: ScrollBar {
  249. active: true
  250. policy: ScrollBar.AsNeeded
  251. }
  252. delegate: ModernPatternCard {
  253. width: gridView.cellWidth - 10
  254. height: gridView.cellHeight - 10
  255. name: model.name
  256. preview: model.preview
  257. onClicked: {
  258. if (stackView && backend) {
  259. stackView.push("PatternDetailPage.qml", {
  260. patternName: model.name,
  261. patternPath: model.path,
  262. patternPreview: model.preview,
  263. backend: backend
  264. })
  265. }
  266. }
  267. }
  268. // Add scroll animations
  269. add: Transition {
  270. NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 300 }
  271. NumberAnimation { property: "scale"; from: 0.8; to: 1; duration: 300 }
  272. }
  273. }
  274. // Empty state
  275. Item {
  276. Layout.fillWidth: true
  277. Layout.fillHeight: true
  278. visible: patternCount === 0 && searchField.text !== ""
  279. Column {
  280. anchors.centerIn: parent
  281. spacing: 20
  282. Text {
  283. text: "⌕"
  284. font.pixelSize: 48
  285. anchors.horizontalCenter: parent.horizontalCenter
  286. color: Components.ThemeManager.placeholderText
  287. }
  288. Label {
  289. text: "No patterns found"
  290. anchors.horizontalCenter: parent.horizontalCenter
  291. color: Components.ThemeManager.textSecondary
  292. font.pixelSize: 18
  293. }
  294. Label {
  295. text: "Try a different search term"
  296. anchors.horizontalCenter: parent.horizontalCenter
  297. color: Components.ThemeManager.textTertiary
  298. font.pixelSize: 14
  299. }
  300. }
  301. }
  302. }
  303. }