TableControlPage.qml 26 KB

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