| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- import QtQuick 2.15
- import QtQuick.Controls 2.15
- import QtQuick.Layouts 1.15
- import "../components"
- import "../components" as Components
- Page {
- id: page
- property var backend: null
- property string selectedPort: ""
- property bool isSerialConnected: false
- property bool autoPlayOnBoot: false
- // Backend signal connections
- Connections {
- target: backend
- function onSerialConnectionChanged(connected) {
- isSerialConnected = connected
- }
- function onCurrentPortChanged(port) {
- if (port) {
- selectedPort = port
- }
- }
- function onSettingsLoaded() {
- if (backend) {
- autoPlayOnBoot = backend.autoPlayOnBoot
- isSerialConnected = backend.serialConnected
- if (backend.currentPort) {
- selectedPort = backend.currentPort
- }
- }
- }
- }
- // backend is injected by the Loader after creation, so kick off the
- // initial mDNS browse (and settings load) when it arrives — at
- // Component.onCompleted it is still null and these would be no-ops.
- onBackendChanged: {
- if (backend) {
- refreshSerialPorts()
- loadSettings()
- }
- }
- function refreshSerialPorts() {
- if (backend) {
- backend.refreshSerialPorts()
- }
- }
- function loadSettings() {
- if (backend) {
- backend.loadControlSettings()
- }
- }
- Rectangle {
- anchors.fill: parent
- color: Components.ThemeManager.backgroundColor
- }
- ColumnLayout {
- anchors.fill: parent
- spacing: 0
- // Header
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.headerHeight
- color: Components.ThemeManager.surfaceColor
- Rectangle {
- anchors.bottom: parent.bottom
- width: parent.width
- height: 1
- color: Components.ThemeManager.borderColor
- }
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: Components.ThemeManager.spaceLg
- anchors.rightMargin: Components.ThemeManager.spaceLg
- ConnectionStatus {
- backend: page.backend
- Layout.rightMargin: Components.ThemeManager.spaceSm
- }
- Label {
- text: "Control"
- font.family: Components.ThemeManager.fontDisplay
- font.pixelSize: Components.ThemeManager.fontSizeTitle
- color: Components.ThemeManager.textPrimary
- }
- Item {
- Layout.fillWidth: true
- }
- }
- }
- // Main content — two columns: the landscape panel wastes half its
- // width on full-page cards, so connection lives left and the
- // movement/device settings live right; both scroll together.
- ScrollView {
- Layout.fillWidth: true
- Layout.fillHeight: true
- contentWidth: availableWidth
- RowLayout {
- width: parent.width
- spacing: 0
- // ---- Left column: table connection ----
- ColumnLayout {
- Layout.fillWidth: true
- Layout.maximumWidth: Math.round(page.width * 0.47)
- Layout.alignment: Qt.AlignTop
- spacing: 0
- SettingsCard {
- Layout.rightMargin: Components.ThemeManager.spaceSm
- Layout.bottomMargin: Components.ThemeManager.spaceLg
- Layout.preferredHeight: connectionColumn.implicitHeight + 2 * Components.ThemeManager.spaceLg
- ColumnLayout {
- id: connectionColumn
- anchors.fill: parent
- anchors.margins: Components.ThemeManager.spaceLg
- spacing: Components.ThemeManager.spaceMd
- SectionLabel {
- text: "Table connection"
- }
- // Connection status row — info rect + button as
- // siblings, so the button column lines up with
- // every other row's (Save, Refresh, Connect).
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 60
- radius: Components.ThemeManager.radiusSm
- color: isSerialConnected ? Components.ThemeManager.okSoft
- : Components.ThemeManager.cardColor
- border.color: isSerialConnected ? Components.ThemeManager.ok
- : Components.ThemeManager.borderColor
- border.width: 1
- Column {
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Components.ThemeManager.spaceLg
- anchors.rightMargin: Components.ThemeManager.spaceMd
- spacing: 2
- Label {
- text: isSerialConnected
- ? (backend && backend.tableName ? backend.tableName : "Connected")
- : "Not connected"
- color: isSerialConnected ? Components.ThemeManager.ok
- : Components.ThemeManager.textSecondary
- font.family: Components.ThemeManager.fontDisplay
- font.pixelSize: Components.ThemeManager.fontSizeBody
- elide: Text.ElideRight
- width: parent.width
- }
- Label {
- text: isSerialConnected
- ? selectedPort
- : (backend ? backend.reconnectStatus : "")
- visible: text !== ""
- color: Components.ThemeManager.textSecondary
- font.family: Components.ThemeManager.fontBody
- font.pixelSize: Components.ThemeManager.fontSizeCaption
- elide: Text.ElideRight
- width: parent.width
- }
- }
- }
- ModernControlButton {
- Layout.preferredWidth: 116
- Layout.preferredHeight: 44
- visible: isSerialConnected
- text: "Disconnect"
- outlined: true
- buttonColor: Components.ThemeManager.danger
- fontSize: 12
- onClicked: {
- if (backend) backend.disconnectSerial()
- }
- }
- }
- // Table password ($Sand/Password, sent as X-Sand-Key).
- // Empty + Save clears a stored password.
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- TextField {
- id: tablePasswordField
- Layout.fillWidth: true
- Layout.preferredHeight: 44
- echoMode: TextInput.Password
- placeholderText: backend && backend.hasTablePassword
- ? "Table password (saved)"
- : "Table password (if set)"
- placeholderTextColor: Components.ThemeManager.textTertiary
- font.family: Components.ThemeManager.fontBody
- font.pixelSize: Components.ThemeManager.fontSizeBody
- color: Components.ThemeManager.textPrimary
- verticalAlignment: TextInput.AlignVCenter
- leftPadding: Components.ThemeManager.spaceLg
- rightPadding: Components.ThemeManager.spaceLg
- background: Rectangle {
- color: Components.ThemeManager.backgroundColor
- radius: 22
- border.color: tablePasswordField.activeFocus ? Components.ThemeManager.accent
- : Components.ThemeManager.borderColor
- border.width: 1
- }
- }
- ModernControlButton {
- Layout.preferredWidth: 116
- Layout.preferredHeight: 44
- text: "Save"
- buttonColor: Components.ThemeManager.accent
- fontSize: 12
- onClicked: {
- if (backend) {
- backend.setTablePassword(tablePasswordField.text)
- tablePasswordField.text = ""
- }
- }
- }
- }
- }
- }
- // Tables found on the network (mDNS)
- SettingsCard {
- Layout.rightMargin: Components.ThemeManager.spaceSm
- Layout.bottomMargin: Components.ThemeManager.spaceLg
- Layout.preferredHeight: networkColumn.implicitHeight + 2 * Components.ThemeManager.spaceLg
- ColumnLayout {
- id: networkColumn
- anchors.fill: parent
- anchors.margins: Components.ThemeManager.spaceLg
- spacing: Components.ThemeManager.spaceMd
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- SectionLabel {
- text: "Tables on your network"
- Layout.fillWidth: true
- }
- ModernControlButton {
- Layout.preferredWidth: 116
- Layout.preferredHeight: 44
- text: "Refresh"
- icon: "refresh"
- outlined: true
- buttonColor: Components.ThemeManager.textSecondary
- fontSize: 12
- onClicked: refreshSerialPorts()
- }
- }
- // The connected table already sits in the status
- // card above, so it is filtered from this list.
- Repeater {
- model: backend ? backend.discoveredTables : []
- delegate: RowLayout {
- required property var modelData
- readonly property bool isCurrent: isSerialConnected && modelData.url === selectedPort
- visible: !isCurrent
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 60
- radius: Components.ThemeManager.radiusSm
- color: Components.ThemeManager.cardColor
- border.color: Components.ThemeManager.borderColor
- border.width: 1
- Column {
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: Components.ThemeManager.spaceLg
- anchors.rightMargin: Components.ThemeManager.spaceMd
- spacing: 2
- Label {
- text: modelData.name || modelData.url
- color: Components.ThemeManager.textPrimary
- font.family: Components.ThemeManager.fontDisplay
- font.pixelSize: Components.ThemeManager.fontSizeBody
- elide: Text.ElideRight
- width: parent.width
- }
- Label {
- text: modelData.url
- color: Components.ThemeManager.textSecondary
- font.family: Components.ThemeManager.fontBody
- font.pixelSize: Components.ThemeManager.fontSizeCaption
- elide: Text.ElideRight
- width: parent.width
- }
- }
- }
- ModernControlButton {
- Layout.preferredWidth: 116
- Layout.preferredHeight: 44
- text: "Connect"
- buttonColor: Components.ThemeManager.accent
- fontSize: 12
- onClicked: {
- if (backend) backend.connectSerial(modelData.url)
- }
- }
- }
- }
- Label {
- visible: !backend || backend.discoveredTables.length === 0
- text: "No tables found. Tap Refresh, or enter the address below."
- font.family: Components.ThemeManager.fontBody
- font.pixelSize: Components.ThemeManager.fontSizeCaption
- color: Components.ThemeManager.textTertiary
- Layout.fillWidth: true
- wrapMode: Text.WordWrap
- }
- // Manual address entry
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- TextField {
- id: manualAddress
- Layout.fillWidth: true
- Layout.preferredHeight: 44
- placeholderText: "IP or host address"
- placeholderTextColor: Components.ThemeManager.textTertiary
- font.family: Components.ThemeManager.fontBody
- font.pixelSize: Components.ThemeManager.fontSizeBody
- color: Components.ThemeManager.textPrimary
- verticalAlignment: TextInput.AlignVCenter
- leftPadding: Components.ThemeManager.spaceLg
- rightPadding: Components.ThemeManager.spaceLg
- inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhPreferLowercase
- background: Rectangle {
- color: Components.ThemeManager.backgroundColor
- radius: 22
- border.color: manualAddress.activeFocus ? Components.ThemeManager.accent
- : Components.ThemeManager.borderColor
- border.width: 1
- }
- }
- ModernControlButton {
- Layout.preferredWidth: 116
- Layout.preferredHeight: 44
- text: "Connect"
- buttonColor: Components.ThemeManager.accent
- fontSize: 12
- enabled: manualAddress.text.trim() !== ""
- onClicked: {
- if (backend) {
- backend.connectSerial(manualAddress.text.trim())
- manualAddress.text = ""
- }
- }
- }
- }
- }
- }
- }
- // ---- Right column: movement + device settings ----
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignTop
- spacing: 0
- // The table itself: movement + auto play
- SettingsCard {
- Layout.leftMargin: Components.ThemeManager.spaceSm
- Layout.preferredHeight: movementColumn.implicitHeight + 2 * Components.ThemeManager.spaceLg
- ColumnLayout {
- id: movementColumn
- anchors.fill: parent
- anchors.margins: Components.ThemeManager.spaceLg
- spacing: Components.ThemeManager.spaceMd
- SectionLabel {
- text: "Table"
- }
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- ModernControlButton {
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.touchTarget
- text: "Home"
- icon: "home"
- outlined: true
- buttonColor: Components.ThemeManager.accent
- fontSize: 13
- enabled: isSerialConnected
- onClicked: {
- if (backend) backend.sendHome()
- }
- }
- ModernControlButton {
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.touchTarget
- text: "Center"
- icon: "adjust"
- outlined: true
- buttonColor: Components.ThemeManager.accent
- fontSize: 13
- enabled: isSerialConnected
- onClicked: {
- if (backend) backend.moveToCenter()
- }
- }
- ModernControlButton {
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.touchTarget
- text: "Edge"
- icon: "radio_unchecked"
- outlined: true
- buttonColor: Components.ThemeManager.accent
- fontSize: 13
- enabled: isSerialConnected
- onClicked: {
- if (backend) backend.moveToPerimeter()
- }
- }
- }
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Components.ThemeManager.borderLight
- }
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 2
- Label {
- text: "Auto play"
- font.family: Components.ThemeManager.fontMedium
- font.pixelSize: Components.ThemeManager.fontSizeBody
- color: Components.ThemeManager.textPrimary
- }
- Label {
- text: "Start playing when the table powers on"
- font.family: Components.ThemeManager.fontBody
- font.pixelSize: Components.ThemeManager.fontSizeCaption
- color: Components.ThemeManager.textSecondary
- wrapMode: Text.WordWrap
- Layout.fillWidth: true
- }
- }
- DwSwitch {
- id: autoPlaySwitch
- checked: autoPlayOnBoot
- onToggled: {
- autoPlayOnBoot = checked
- if (backend) {
- backend.setAutoPlayOnBoot(checked)
- }
- }
- // A user toggle breaks the declarative binding;
- // this keeps the switch following loaded settings.
- Binding {
- target: autoPlaySwitch
- property: "checked"
- value: autoPlayOnBoot
- }
- }
- }
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Components.ThemeManager.borderLight
- }
- // Reboots the FluidNC controller ($Bye soft-reset);
- // the board re-homes on the way back up.
- ModernControlButton {
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.touchTarget
- text: "Restart table"
- icon: "restart"
- outlined: true
- buttonColor: Components.ThemeManager.textSecondary
- fontSize: 13
- enabled: isSerialConnected
- onClicked: {
- if (backend) backend.restartBackend()
- }
- }
- }
- }
- // This screen: sleep, theme, power
- SettingsCard {
- Layout.leftMargin: Components.ThemeManager.spaceSm
- Layout.bottomMargin: Components.ThemeManager.spaceLg
- Layout.preferredHeight: screenColumn.implicitHeight + 2 * Components.ThemeManager.spaceLg
- ColumnLayout {
- id: screenColumn
- anchors.fill: parent
- anchors.margins: Components.ThemeManager.spaceLg
- spacing: Components.ThemeManager.spaceMd
- SectionLabel {
- text: "This screen"
- }
- Label {
- text: "Sleeps after"
- font.family: Components.ThemeManager.fontMedium
- font.pixelSize: Components.ThemeManager.fontSizeCaption
- color: Components.ThemeManager.textSecondary
- }
- RowLayout {
- id: timeoutGrid
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- property string currentSelection: backend ? backend.getCurrentScreenTimeoutOption() : "5 minutes"
- Connections {
- target: backend
- function onScreenTimeoutChanged() {
- if (backend) {
- timeoutGrid.currentSelection = backend.getCurrentScreenTimeoutOption()
- }
- }
- }
- Repeater {
- model: [
- { label: "30 s", value: "30 seconds" },
- { label: "1 m", value: "1 minute" },
- { label: "5 m", value: "5 minutes" },
- { label: "10 m", value: "10 minutes" },
- { label: "Never", value: "Never" }
- ]
- ChoiceChip {
- required property var modelData
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.touchTarget
- label: modelData.label
- selected: timeoutGrid.currentSelection === modelData.value
- onClicked: {
- if (backend) {
- backend.setScreenTimeoutByOption(modelData.value)
- timeoutGrid.currentSelection = modelData.value
- }
- }
- }
- }
- }
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Components.ThemeManager.borderLight
- }
- RowLayout {
- Layout.fillWidth: true
- spacing: Components.ThemeManager.spaceSm
- Label {
- text: "Night mode"
- font.family: Components.ThemeManager.fontMedium
- font.pixelSize: Components.ThemeManager.fontSizeBody
- color: Components.ThemeManager.textPrimary
- Layout.fillWidth: true
- }
- DwSwitch {
- id: darkModeSwitch
- checked: Components.ThemeManager.darkMode
- onToggled: {
- Components.ThemeManager.darkMode = checked
- }
- }
- }
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Components.ThemeManager.borderLight
- }
- ModernControlButton {
- Layout.fillWidth: true
- Layout.preferredHeight: Components.ThemeManager.touchTarget
- text: "Shut down Pi"
- icon: "power"
- outlined: true
- buttonColor: Components.ThemeManager.danger
- fontSize: 13
- onClicked: {
- if (backend) backend.shutdownPi()
- }
- }
- }
- }
- }
- }
- }
- }
- }
|