1
0

ModernPatternListPage.qml 14 KB

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