TableControlPage.qml 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.15
  4. // import QtQuick.Effects // Disabled - causes issues on linuxfb
  5. import "../components"
  6. import "../components" as Components
  7. Page {
  8. id: page
  9. property var backend: null
  10. property var serialPorts: []
  11. property string selectedPort: ""
  12. property bool isSerialConnected: false
  13. property bool autoPlayOnBoot: false
  14. // Backend signal connections
  15. Connections {
  16. target: backend
  17. function onSerialPortsUpdated(ports) {
  18. console.log("Serial ports updated:", ports)
  19. serialPorts = ports
  20. }
  21. function onSerialConnectionChanged(connected) {
  22. console.log("Serial connection changed:", connected)
  23. isSerialConnected = connected
  24. }
  25. function onCurrentPortChanged(port) {
  26. console.log("Current port changed:", port)
  27. if (port) {
  28. selectedPort = port
  29. }
  30. }
  31. function onSettingsLoaded() {
  32. console.log("Settings loaded")
  33. if (backend) {
  34. autoPlayOnBoot = backend.autoPlayOnBoot
  35. isSerialConnected = backend.serialConnected
  36. // Screen timeout is now managed by button selection, no need to convert
  37. if (backend.currentPort) {
  38. selectedPort = backend.currentPort
  39. }
  40. }
  41. }
  42. }
  43. // Refresh serial ports on page load
  44. Component.onCompleted: {
  45. refreshSerialPorts()
  46. loadSettings()
  47. }
  48. function refreshSerialPorts() {
  49. if (backend) {
  50. backend.refreshSerialPorts()
  51. }
  52. }
  53. function loadSettings() {
  54. if (backend) {
  55. backend.loadControlSettings()
  56. }
  57. }
  58. Rectangle {
  59. anchors.fill: parent
  60. color: Components.ThemeManager.backgroundColor
  61. }
  62. ColumnLayout {
  63. anchors.fill: parent
  64. spacing: 0
  65. // Header
  66. Rectangle {
  67. Layout.fillWidth: true
  68. Layout.preferredHeight: 50
  69. color: Components.ThemeManager.surfaceColor
  70. Rectangle {
  71. anchors.bottom: parent.bottom
  72. width: parent.width
  73. height: 1
  74. color: Components.ThemeManager.borderColor
  75. }
  76. RowLayout {
  77. anchors.fill: parent
  78. anchors.leftMargin: 15
  79. anchors.rightMargin: 10
  80. ConnectionStatus {
  81. backend: page.backend
  82. Layout.rightMargin: 8
  83. }
  84. Label {
  85. text: "Table Control"
  86. font.pixelSize: 18
  87. font.bold: true
  88. color: Components.ThemeManager.textPrimary
  89. }
  90. Item {
  91. Layout.fillWidth: true
  92. }
  93. }
  94. }
  95. // Main Content
  96. ScrollView {
  97. Layout.fillWidth: true
  98. Layout.fillHeight: true
  99. contentWidth: availableWidth
  100. ColumnLayout {
  101. width: parent.width
  102. anchors.margins: 5
  103. spacing: 2
  104. // Serial Connection Section
  105. Rectangle {
  106. Layout.fillWidth: true
  107. Layout.preferredHeight: 160
  108. Layout.margins: 5
  109. radius: 8
  110. color: Components.ThemeManager.surfaceColor
  111. ColumnLayout {
  112. anchors.fill: parent
  113. anchors.margins: 15
  114. spacing: 10
  115. Label {
  116. text: "Serial Connection"
  117. font.pixelSize: 14
  118. font.bold: true
  119. color: Components.ThemeManager.textPrimary
  120. }
  121. RowLayout {
  122. Layout.fillWidth: true
  123. spacing: 10
  124. Rectangle {
  125. Layout.fillWidth: true
  126. Layout.preferredHeight: 40
  127. radius: 6
  128. color: isSerialConnected ? (Components.ThemeManager.darkMode ? "#1b4332" : "#e8f5e8") : Components.ThemeManager.cardColor
  129. border.color: isSerialConnected ? "#4CAF50" : Components.ThemeManager.borderColor
  130. border.width: 1
  131. RowLayout {
  132. anchors.fill: parent
  133. anchors.margins: 8
  134. Label {
  135. text: isSerialConnected ?
  136. (selectedPort ? `Connected: ${selectedPort}` : "Connected") :
  137. (selectedPort || "No port selected")
  138. color: isSerialConnected ? "#4CAF50" : (selectedPort ? Components.ThemeManager.textPrimary : Components.ThemeManager.textTertiary)
  139. font.pixelSize: 12
  140. font.bold: isSerialConnected
  141. Layout.fillWidth: true
  142. }
  143. Text {
  144. text: "▼"
  145. color: Components.ThemeManager.textSecondary
  146. font.pixelSize: 10
  147. visible: !isSerialConnected
  148. }
  149. }
  150. MouseArea {
  151. anchors.fill: parent
  152. enabled: !isSerialConnected
  153. onClicked: portMenu.open()
  154. }
  155. Menu {
  156. id: portMenu
  157. y: parent.height
  158. // Note: Menu rotation for Pi 5 handled differently
  159. // transform property not well supported on Menu
  160. Repeater {
  161. model: serialPorts
  162. MenuItem {
  163. text: modelData
  164. onTriggered: {
  165. selectedPort = modelData
  166. }
  167. }
  168. }
  169. MenuSeparator {}
  170. MenuItem {
  171. text: "Refresh Ports"
  172. onTriggered: refreshSerialPorts()
  173. }
  174. }
  175. }
  176. ModernControlButton {
  177. Layout.preferredWidth: 150
  178. Layout.preferredHeight: 40
  179. text: isSerialConnected ? "Disconnect" : "Connect"
  180. icon: isSerialConnected ? "◉" : "○"
  181. iconSize: 20
  182. buttonColor: isSerialConnected ? "#dc2626" : "#059669"
  183. fontSize: 11
  184. enabled: isSerialConnected || selectedPort !== ""
  185. onClicked: {
  186. if (backend) {
  187. if (isSerialConnected) {
  188. backend.disconnectSerial()
  189. } else {
  190. backend.connectSerial(selectedPort)
  191. }
  192. }
  193. }
  194. }
  195. }
  196. RowLayout {
  197. Layout.fillWidth: true
  198. spacing: 8
  199. visible: !isSerialConnected
  200. ModernControlButton {
  201. Layout.fillWidth: true
  202. Layout.preferredHeight: 35
  203. text: "Refresh Ports"
  204. icon: "↻"
  205. iconSize: 18
  206. buttonColor: "#6b7280"
  207. fontSize: 10
  208. onClicked: refreshSerialPorts()
  209. }
  210. }
  211. }
  212. }
  213. // Hardware Movement Section
  214. Rectangle {
  215. Layout.fillWidth: true
  216. Layout.preferredHeight: 100
  217. Layout.margins: 5
  218. radius: 8
  219. color: Components.ThemeManager.surfaceColor
  220. ColumnLayout {
  221. anchors.fill: parent
  222. anchors.margins: 15
  223. spacing: 10
  224. Label {
  225. text: "Table Movement"
  226. font.pixelSize: 14
  227. font.bold: true
  228. color: Components.ThemeManager.textPrimary
  229. }
  230. GridLayout {
  231. Layout.fillWidth: true
  232. columns: 3
  233. rowSpacing: 8
  234. columnSpacing: 8
  235. ModernControlButton {
  236. Layout.fillWidth: true
  237. Layout.preferredHeight: 45
  238. text: "Home"
  239. icon: "⌂"
  240. iconSize: 20
  241. buttonColor: "#2563eb"
  242. fontSize: 12
  243. enabled: isSerialConnected
  244. onClicked: {
  245. if (backend) backend.sendHome()
  246. }
  247. }
  248. ModernControlButton {
  249. Layout.fillWidth: true
  250. Layout.preferredHeight: 45
  251. text: "Center"
  252. icon: "◎"
  253. iconSize: 20
  254. buttonColor: "#2563eb"
  255. fontSize: 12
  256. enabled: isSerialConnected
  257. onClicked: {
  258. if (backend) backend.moveToCenter()
  259. }
  260. }
  261. ModernControlButton {
  262. Layout.fillWidth: true
  263. Layout.preferredHeight: 45
  264. text: "Perimeter"
  265. icon: "○"
  266. iconSize: 20
  267. buttonColor: "#2563eb"
  268. fontSize: 12
  269. enabled: isSerialConnected
  270. onClicked: {
  271. if (backend) backend.moveToPerimeter()
  272. }
  273. }
  274. }
  275. }
  276. }
  277. // Auto Play on Boot Section
  278. Rectangle {
  279. Layout.fillWidth: true
  280. Layout.preferredHeight: 200 // Reduced from 280 for single row layout
  281. Layout.margins: 5
  282. radius: 8
  283. color: Components.ThemeManager.surfaceColor
  284. ColumnLayout {
  285. anchors.fill: parent
  286. anchors.margins: 15
  287. spacing: 10
  288. Label {
  289. text: "Auto Play Settings"
  290. font.pixelSize: 14
  291. font.bold: true
  292. color: Components.ThemeManager.textPrimary
  293. }
  294. RowLayout {
  295. Layout.fillWidth: true
  296. spacing: 10
  297. Label {
  298. text: "Auto play on boot:"
  299. font.pixelSize: 12
  300. color: Components.ThemeManager.textSecondary
  301. Layout.fillWidth: true
  302. }
  303. Switch {
  304. id: autoPlaySwitch
  305. checked: autoPlayOnBoot
  306. onToggled: {
  307. autoPlayOnBoot = checked
  308. if (backend) {
  309. backend.setAutoPlayOnBoot(checked)
  310. }
  311. }
  312. }
  313. }
  314. ColumnLayout {
  315. Layout.fillWidth: true
  316. spacing: 15
  317. Label {
  318. text: "Screen timeout:"
  319. font.pixelSize: 14
  320. font.bold: true
  321. color: Components.ThemeManager.textPrimary
  322. Layout.alignment: Qt.AlignLeft
  323. }
  324. // Touch-friendly button row for timeout options
  325. RowLayout {
  326. id: timeoutGrid
  327. Layout.fillWidth: true
  328. spacing: 8
  329. property string currentSelection: backend ? backend.getCurrentScreenTimeoutOption() : "5 minutes"
  330. // 30 seconds button
  331. Rectangle {
  332. Layout.preferredWidth: 100
  333. Layout.preferredHeight: 50
  334. color: timeoutGrid.currentSelection === "30 seconds" ? Components.ThemeManager.selectedBackground : Components.ThemeManager.buttonBackground
  335. border.color: timeoutGrid.currentSelection === "30 seconds" ? Components.ThemeManager.selectedBorder : Components.ThemeManager.buttonBorder
  336. border.width: 2
  337. radius: 8
  338. Label {
  339. anchors.centerIn: parent
  340. text: "30s"
  341. font.pixelSize: 14
  342. font.bold: true
  343. color: timeoutGrid.currentSelection === "30 seconds" ? "white" : Components.ThemeManager.textPrimary
  344. }
  345. MouseArea {
  346. anchors.fill: parent
  347. onClicked: {
  348. if (backend) {
  349. backend.setScreenTimeoutByOption("30 seconds")
  350. timeoutGrid.currentSelection = "30 seconds"
  351. }
  352. }
  353. }
  354. }
  355. // 1 minute button
  356. Rectangle {
  357. Layout.preferredWidth: 100
  358. Layout.preferredHeight: 50
  359. color: timeoutGrid.currentSelection === "1 minute" ? Components.ThemeManager.selectedBackground : Components.ThemeManager.buttonBackground
  360. border.color: timeoutGrid.currentSelection === "1 minute" ? Components.ThemeManager.selectedBorder : Components.ThemeManager.buttonBorder
  361. border.width: 2
  362. radius: 8
  363. Label {
  364. anchors.centerIn: parent
  365. text: "1min"
  366. font.pixelSize: 14
  367. font.bold: true
  368. color: timeoutGrid.currentSelection === "1 minute" ? "white" : Components.ThemeManager.textPrimary
  369. }
  370. MouseArea {
  371. anchors.fill: parent
  372. onClicked: {
  373. if (backend) {
  374. backend.setScreenTimeoutByOption("1 minute")
  375. timeoutGrid.currentSelection = "1 minute"
  376. }
  377. }
  378. }
  379. }
  380. // 5 minutes button
  381. Rectangle {
  382. Layout.preferredWidth: 100
  383. Layout.preferredHeight: 50
  384. color: timeoutGrid.currentSelection === "5 minutes" ? Components.ThemeManager.selectedBackground : Components.ThemeManager.buttonBackground
  385. border.color: timeoutGrid.currentSelection === "5 minutes" ? Components.ThemeManager.selectedBorder : Components.ThemeManager.buttonBorder
  386. border.width: 2
  387. radius: 8
  388. Label {
  389. anchors.centerIn: parent
  390. text: "5min"
  391. font.pixelSize: 14
  392. font.bold: true
  393. color: timeoutGrid.currentSelection === "5 minutes" ? "white" : Components.ThemeManager.textPrimary
  394. }
  395. MouseArea {
  396. anchors.fill: parent
  397. onClicked: {
  398. if (backend) {
  399. backend.setScreenTimeoutByOption("5 minutes")
  400. timeoutGrid.currentSelection = "5 minutes"
  401. }
  402. }
  403. }
  404. }
  405. // 10 minutes button
  406. Rectangle {
  407. Layout.preferredWidth: 100
  408. Layout.preferredHeight: 50
  409. color: timeoutGrid.currentSelection === "10 minutes" ? Components.ThemeManager.selectedBackground : Components.ThemeManager.buttonBackground
  410. border.color: timeoutGrid.currentSelection === "10 minutes" ? Components.ThemeManager.selectedBorder : Components.ThemeManager.buttonBorder
  411. border.width: 2
  412. radius: 8
  413. Label {
  414. anchors.centerIn: parent
  415. text: "10min"
  416. font.pixelSize: 14
  417. font.bold: true
  418. color: timeoutGrid.currentSelection === "10 minutes" ? "white" : Components.ThemeManager.textPrimary
  419. }
  420. MouseArea {
  421. anchors.fill: parent
  422. onClicked: {
  423. if (backend) {
  424. backend.setScreenTimeoutByOption("10 minutes")
  425. timeoutGrid.currentSelection = "10 minutes"
  426. }
  427. }
  428. }
  429. }
  430. // Never button
  431. Rectangle {
  432. Layout.preferredWidth: 100
  433. Layout.preferredHeight: 50
  434. color: timeoutGrid.currentSelection === "Never" ? "#FF9800" : "#f0f0f0"
  435. border.color: timeoutGrid.currentSelection === "Never" ? "#F57C00" : "#ccc"
  436. border.width: 2
  437. radius: 8
  438. Label {
  439. anchors.centerIn: parent
  440. text: "Never"
  441. font.pixelSize: 14
  442. font.bold: true
  443. color: timeoutGrid.currentSelection === "Never" ? "white" : "#333"
  444. }
  445. MouseArea {
  446. anchors.fill: parent
  447. onClicked: {
  448. if (backend) {
  449. backend.setScreenTimeoutByOption("Never")
  450. timeoutGrid.currentSelection = "Never"
  451. }
  452. }
  453. }
  454. }
  455. // Update selection when backend changes
  456. Connections {
  457. target: backend
  458. function onScreenTimeoutChanged() {
  459. if (backend) {
  460. timeoutGrid.currentSelection = backend.getCurrentScreenTimeoutOption()
  461. }
  462. }
  463. }
  464. }
  465. }
  466. }
  467. }
  468. // Theme Settings Section
  469. Rectangle {
  470. Layout.fillWidth: true
  471. Layout.preferredHeight: 100
  472. Layout.margins: 5
  473. radius: 8
  474. color: Components.ThemeManager.surfaceColor
  475. ColumnLayout {
  476. anchors.fill: parent
  477. anchors.margins: 15
  478. spacing: 10
  479. Label {
  480. text: "Appearance"
  481. font.pixelSize: 14
  482. font.bold: true
  483. color: Components.ThemeManager.textPrimary
  484. }
  485. RowLayout {
  486. Layout.fillWidth: true
  487. spacing: 10
  488. Label {
  489. text: "Dark mode:"
  490. font.pixelSize: 12
  491. color: Components.ThemeManager.textSecondary
  492. Layout.fillWidth: true
  493. }
  494. Switch {
  495. id: darkModeSwitch
  496. checked: Components.ThemeManager.darkMode
  497. onToggled: {
  498. Components.ThemeManager.darkMode = checked
  499. }
  500. }
  501. }
  502. }
  503. }
  504. // System Controls Section
  505. Rectangle {
  506. Layout.fillWidth: true
  507. Layout.preferredHeight: 100
  508. Layout.margins: 5
  509. radius: 8
  510. color: Components.ThemeManager.surfaceColor
  511. ColumnLayout {
  512. anchors.fill: parent
  513. anchors.margins: 15
  514. spacing: 10
  515. Label {
  516. text: "System Controls"
  517. font.pixelSize: 14
  518. font.bold: true
  519. color: Components.ThemeManager.textPrimary
  520. }
  521. RowLayout {
  522. Layout.fillWidth: true
  523. spacing: 8
  524. ModernControlButton {
  525. Layout.fillWidth: true
  526. Layout.preferredHeight: 45
  527. text: "Restart Backend"
  528. icon: "↻"
  529. iconSize: 20
  530. buttonColor: "#f59e0b"
  531. fontSize: 11
  532. onClicked: {
  533. if (backend) backend.restartBackend()
  534. }
  535. }
  536. ModernControlButton {
  537. Layout.fillWidth: true
  538. Layout.preferredHeight: 45
  539. text: "Shutdown Pi"
  540. icon: ""
  541. buttonColor: "#dc2626"
  542. fontSize: 11
  543. onClicked: {
  544. if (backend) backend.shutdownPi()
  545. }
  546. }
  547. }
  548. }
  549. }
  550. // Add some bottom spacing for better scrolling
  551. Item {
  552. Layout.preferredHeight: 20
  553. }
  554. }
  555. }
  556. }
  557. }