diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index deb19b20..f4a2b927 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,19 +19,19 @@ jobs: - os: ubuntu-20.04 qt_host: linux - qt_version: '6.8.0' + qt_version: '6.8.1' qt_modules: 'qtmultimedia qtcharts qtwaylandcompositor' qt_arch: 'linux_gcc_64' - os: windows-2022 qt_host: windows - qt_version: '6.8.0' + qt_version: '6.8.1' qt_modules: 'qtmultimedia qtcharts' qt_arch: 'win64_msvc2022_64' - os: macos-latest qt_host: mac - qt_version: '6.7.2' + qt_version: '6.8.1' qt_modules: 'qtmultimedia qtcharts' qt_arch: 'clang_64' diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f9649f..88ff9782 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,8 +114,14 @@ set(QML_ELEMENTS dialogs/TopicView dialogs/TabNameDialog dialogs/TabSizeDialog - dialogs/WidgetConfig - dialogs/ServerDialog + + dialogs/ServerTab + dialogs/MiscTab + dialogs/AppearanceTab + dialogs/SettingsDialog + dialogs/SettingsTabButton + dialogs/AppearanceComboBox + dialogs/AccentEditor ) diff --git a/Main.qml b/Main.qml index 7de41ffa..e1f6cbb5 100644 --- a/Main.qml +++ b/Main.qml @@ -10,81 +10,18 @@ ApplicationWindow { visible: true title: titleManager.title - menuBar: MenuBar { - contentWidth: parent.width - - Menu { - contentWidth: 210 - title: qsTr("&Settings") - Action { - text: qsTr("&Server Settings...") - onTriggered: screen.serverSettings() - shortcut: "Ctrl+E" - } - MenuItem { - text: "&Load Most Recent File?" - checkable: true - checked: settings.loadRecent - onCheckedChanged: settings.loadRecent = checked - } - - Menu { - title: qsTr("&Theme") - Repeater { - model: ["Light", "Dark", "Midnight"] - - MenuItem { - text: "&" + modelData - checkable: true - checked: settings.theme === modelData.toLowerCase() - onCheckedChanged: { - if (checked) - Constants.setTheme(modelData.toLowerCase()) - } - } - } - } - - Menu { - title: qsTr("&Accent") + AccentEditor { + id: accentEditor - Repeater { - model: accents - - MenuItem { - function toTitleCase(str) { - return str.replace( - /\w\S*/g, - text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase() - ); - } + anchors.centerIn: Overlay.overlay + } - text: "&" + toTitleCase(model.name) - checkable: true - checked: settings.accent === model.name - onCheckedChanged: { - if (checked) - Constants.setAccent(model.name) - } - } - } - } + menuBar: MenuBar { + contentWidth: parent.width - Menu { - title: qsTr("&Custom Accents") - MenuItem { - text: "&Edit Accents..." - onTriggered: screen.editAccents() - } - MenuItem { - text: "Export Accents..." - onTriggered: screen.exportAccentsAction() - } - MenuItem { - text: "Import Accents..." - onTriggered: screen.importAccentsAction() - } - } + MenuBarItem { + text: qsTr("&Settings") + onTriggered: screen.settingsDialog() } Menu { @@ -112,7 +49,11 @@ ApplicationWindow { delegate: MenuItem { text: qsTr("&" + index + ". " + modelData) - onTriggered: tlm.load(modelData) + onTriggered: { + if (modelData === "" || modelData === null) return; + tlm.clear() + tlm.load(modelData) + } } } } diff --git a/dialogs/AppearanceComboBox.qml b/dialogs/AppearanceComboBox.qml new file mode 100644 index 00000000..29ba84ee --- /dev/null +++ b/dialogs/AppearanceComboBox.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Shapes 2.15 + +import QFRCDashboard + +ComboBox { + required property string label + + /** what property to bind to */ + required property string bindedProperty + + /** the target to bind the property to */ + required property var bindTarget + + required property var choices + + id: combo + model: choices + font.pixelSize: 15 + + function toTitleCase(str) { + return str.replace( + /\w\S*/g, + text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase() + ); + } + + function open() { + currentIndex = indexOfValue(toTitleCase(bindTarget[bindedProperty])) + } + + function accept() { + bindTarget[bindedProperty] = currentText.toLowerCase() + } + + delegate: ItemDelegate { + id: delegate + + width: combo.width + contentItem: Text { + text: modelData + color: "white" + font.pixelSize: 15 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + highlighted: combo.highlightedIndex === index + } + + Text { + id: floatingLabel + text: label + color: Constants.palette.text + + font.pixelSize: 15 + + anchors { + left: parent.left + bottom: parent.top + + bottomMargin: -2 + leftMargin: 10 + } + } +} diff --git a/dialogs/AppearanceTab.qml b/dialogs/AppearanceTab.qml new file mode 100644 index 00000000..ebda1779 --- /dev/null +++ b/dialogs/AppearanceTab.qml @@ -0,0 +1,110 @@ +import QtCore +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 6.6 +import QtQuick.Dialogs + +import QFRCDashboard + +ColumnLayout { + spacing: 5 + + function editAccents() { + accentEditor.open() + } + + FileDialog { + id: saveAccentDialog + currentFolder: StandardPaths.writableLocation( + StandardPaths.HomeLocation) + fileMode: FileDialog.SaveFile + defaultSuffix: "json" + selectedNameFilter.index: 0 + nameFilters: ["JSON files (*.json)", "All files (*)"] + + onAccepted: accents.exportJson(selectedFile) + } + + function exportAccents() { + saveAccentDialog.open() + } + + FileDialog { + id: loadAccentDialog + currentFolder: StandardPaths.writableLocation( + StandardPaths.HomeLocation) + fileMode: FileDialog.OpenFile + defaultSuffix: "json" + selectedNameFilter.index: 0 + nameFilters: ["JSON files (*.json)", "All files (*)"] + + onAccepted: accents.importJson(loadAccentDialog.selectedFile) + } + + function importAccents() { + loadAccentDialog.open() + } + + function accept() { + theme.accept() + accent.accept() + + Constants.setTheme(settings.theme) + Constants.setAccent(settings.accent) + } + + function open() { + theme.open() + accent.open() + } + + RowLayout { + Layout.fillWidth: true + + AppearanceComboBox { + implicitWidth: 200 + id: theme + + label: "Theme" + + bindedProperty: "theme" + bindTarget: settings + + choices: ["Light", "Dark", "Midnight"] + } + + AppearanceComboBox { + implicitWidth: 200 + id: accent + + label: "Accent" + + bindedProperty: "accent" + bindTarget: settings + + choices: accents.names() + } + } + + SectionHeader { + label: "Custom Accents" + } + + RowLayout { + uniformCellSizes: true + Layout.fillWidth: true + + Button { + text: "&Edit" + onClicked: editAccents() + } + Button { + text: "E&xport" + onClicked: exportAccents() + } + Button { + text: "I&mport" + onClicked: importAccents() + } + } +} diff --git a/dialogs/MiscTab.qml b/dialogs/MiscTab.qml new file mode 100644 index 00000000..6a907a5a --- /dev/null +++ b/dialogs/MiscTab.qml @@ -0,0 +1,26 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 6.6 +import QtQuick.Dialogs + +import QFRCDashboard + +ColumnLayout { + spacing: 5 + + function accept() { + load.accept() + } + + function open() { + load.open() + } + + LabeledCheckbox { + id: load + label: "Load Recent Files?" + + bindTarget: settings + bindedProperty: "loadRecent" + } +} diff --git a/dialogs/ServerDialog.qml b/dialogs/ServerDialog.qml deleted file mode 100644 index 24d7b920..00000000 --- a/dialogs/ServerDialog.qml +++ /dev/null @@ -1,83 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 6.6 -import QtQuick.Dialogs - -import QFRCDashboard - -Dialog { - id: serverDialog - - anchors.centerIn: parent - - onAccepted: { - settings.port = port.value - settings.useTeam = useTeam.checked - settings.ip = ip.text - } - - standardButtons: Dialog.Ok | Dialog.Cancel - - Shortcut { - onActivated: reject() - sequence: Qt.Key_Escape - } - - ColumnLayout { - anchors.fill: parent - spacing: 5 - - RowLayout { - Layout.fillWidth: true - uniformCellSizes: true - Text { - text: "Port:" - font.pixelSize: 17 - color: Constants.palette.text - } - - SpinBox { - id: port - - value: settings.port - - from: 1 - to: 65535 - - font.pixelSize: 17 - } - } - - RowLayout { - Layout.fillWidth: true - uniformCellSizes: true - Text { - text: useTeam.checked ? "Team Number:" : "IP Address:" - font.pixelSize: 17 - color: Constants.palette.text - } - - TextField { - id: ip - - text: settings.ip - - validator: RegularExpressionValidator { - regularExpression: useTeam.checked ? /[0-9]{1,5}/ : /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/ - } - - font.pixelSize: 17 - } - } - - CheckBox { - Layout.fillWidth: true - id: useTeam - text: "Use Team Number?" - - checked: settings.useTeam - - font.pixelSize: 17 - } - } -} diff --git a/dialogs/ServerTab.qml b/dialogs/ServerTab.qml new file mode 100644 index 00000000..086e4932 --- /dev/null +++ b/dialogs/ServerTab.qml @@ -0,0 +1,66 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 6.6 +import QtQuick.Dialogs + +import QFRCDashboard + +ColumnLayout { + spacing: 5 + + function accept() { + team.accept() + ip.accept() + settings.mode = mode.currentIndex + } + + function open() { + team.open() + ip.open() + mode.currentIndex = settings.mode + } + + RowLayout { + Layout.leftMargin: 4 + Layout.fillWidth: true + + LabeledSpinBox { + implicitWidth: 180 + id: team + + from: 0 + to: 99999 + + label: "Team Number" + + bindedProperty: "team" + bindTarget: settings + } + + LabeledTextField { + implicitWidth: 230 + id: ip + + label: "IP Address" + + bindedProperty: "ip" + bindTarget: settings + + validator: RegularExpressionValidator { + regularExpression: /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/ + } + } + } + + LabeledComboBox { + id: mode + implicitWidth: 250 + + label: "Connection Mode" + + bindedProperty: "mode" + bindTarget: settings + + choices: ["IP Address", "Team Number", "Driver Station"] + } +} diff --git a/dialogs/SettingsDialog.qml b/dialogs/SettingsDialog.qml new file mode 100644 index 00000000..5fd82f9e --- /dev/null +++ b/dialogs/SettingsDialog.qml @@ -0,0 +1,106 @@ +import QtQuick 6.7 +import QtQuick.Controls 6.6 +import QtQuick.Layouts 6.6 +import QtQuick.Dialogs + +import QFRCDashboard + +Dialog { + id: serverDialog + + height: 350 + width: 475 + + background: Rectangle { + color: Constants.palette.dialogBg + + radius: 12 + } + + anchors.centerIn: parent + + onAccepted: { + server.accept() + appearance.accept() + misc.accept() + } + + standardButtons: Dialog.Ok | Dialog.Cancel + + Shortcut { + onActivated: reject() + sequence: Qt.Key_Escape + } + + function openDialog() { + open() + server.open() + appearance.open() + misc.open() + } + + SwipeView { + id: swipe + currentIndex: tabBar.currentIndex + clip: true + + anchors { + top: tabBar.bottom + bottom: parent.bottom + left: parent.left + right: parent.right + } + + ServerTab { + anchors.leftMargin: 5 + + id: server + clip: true + } + + AppearanceTab { + anchors.leftMargin: 5 + + id: appearance + clip: true + } + + MiscTab { + anchors.leftMargin: 5 + + id: misc + clip: true + } + } + + TabBar { + id: tabBar + currentIndex: swipe.currentIndex + position: TabBar.Header + + background: Rectangle { + color: "transparent" + } + + anchors { + top: parent.top + left: parent.left + right: parent.right + + leftMargin: -18 + rightMargin: -18 + topMargin: -16 + } + + Repeater { + model: ["Network", "Appearance", "Miscellaneous"] + + SettingsTabButton { + required property string modelData + required property int index + + label: modelData + } + } + } +} diff --git a/dialogs/SettingsTabButton.qml b/dialogs/SettingsTabButton.qml new file mode 100644 index 00000000..3736218c --- /dev/null +++ b/dialogs/SettingsTabButton.qml @@ -0,0 +1,39 @@ +import QtQuick 6.7 +import QtQuick.Controls 6.6 +import QtQuick.Layouts 6.6 +import QtQuick.Dialogs + +import QFRCDashboard + +TabButton { + required property string label + + id: button + + height: 40 + + contentItem: Row { + Image { + source: "qrc:/" + label + (index === tabBar.currentIndex ? "Light" : "Dark") + width: 25 + height: 25 + } + + Label { + font.pixelSize: 18 + text: label + + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + + color: index === tabBar.currentIndex ? Constants.tab : "white" + } + } + + background: Rectangle { + implicitWidth: parent.width + topLeftRadius: 12 + topRightRadius: 12 + color: index !== tabBar.currentIndex ? Constants.tab : "white" + } +} diff --git a/dialogs/WidgetConfig.qml b/dialogs/WidgetConfig.qml deleted file mode 100644 index 5c8ce248..00000000 --- a/dialogs/WidgetConfig.qml +++ /dev/null @@ -1,916 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs -import QtQuick.Layouts - -import Qt.labs.qmlmodels -import QFRCDashboard - -Dialog { - id: widgetConf - - anchors.centerIn: parent - - width: parent.width / 1.6 - height: parent.height - 60 - background: Rectangle { - color: Constants.palette.dialogBg - } - - standardButtons: Dialog.Ok | Dialog.Cancel - - onAccepted: getValues() - - Shortcut { - onActivated: reject() - sequence: Qt.Key_Escape - } - - ColorDialog { - id: colorDialog - } - - property var item - - function openUp(item) { - lm.clear() - for (var p in item) { - if (p.startsWith("item_") && typeof item[p] !== "function") { - let sub = p.substr(5) - let typeName = MetaObjectHelper.typeName(item, p) - var choices = [] - let obj = {} - - if (typeName === "QVariant") { - choices = item[sub + "Choices"] - - typeName = "list" - } else if (typeName === "QVariantList") { - obj["valueType"] = item[sub + "ValueType"] - obj["valueName"] = item[sub + "ValueName"] - } else if (typeName === "int" || typeName === "double") { - let min = item[sub + "Min"] - let max = item[sub + "Max"] - - obj["min"] = (typeof min === "undefined") ? 0 : min - obj["max"] = (typeof max === "undefined") ? 100000 : max - } - - lm.append({ - "name": p, - "type": typeName, - "itemValue": item[p], - "choices": choices, - "item": item, - "map": obj - }) - } - } - - this.item = item - - open() - } - - ListModel { - id: lm - dynamicRoles: true - } - - function displayText(text) { - const result = text.substring(5).replace(/([A-Z])/g, " $1") - return result.charAt(0).toUpperCase() + result.slice(1) - } - - function getValues() { - for (var i = 0; i < rep.count; ++i) { - let idx = lm.get(i) - item[idx.name] = idx.itemValue - } - } - - Flow { - anchors { - top: parent.top - bottom: parent.bottom - left: parent.left - right: parent.right - - margins: 8 - } - - spacing: 8 - - id: flow - - Repeater { - id: rep - - clip: true - - // boundsBehavior: Flickable.StopAtBounds - model: lm - - component FieldLabel: Label { - Layout.fillWidth: true - - text: displayText(model.name) - font.pixelSize: 15 - color: Constants.palette.text - } - - delegate: DelegateChooser { - role: "type" - - // TODO: may want to find a way to avoid all this repeat code - DelegateChoice { - roleValue: "QString" - - RowLayout { - clip: true - - FieldLabel {} - - TextField { - id: txt - Layout.fillWidth: true - - font.pixelSize: 15 - text: model.itemValue - - onTextEdited: model.itemValue = text - } - } - } - - DelegateChoice { - roleValue: "bool" - - RowLayout { - clip: true - - FieldLabel {} - - CheckBox { - checked: model.itemValue - - onClicked: model.itemValue = checked - - indicator.implicitHeight: 20 - indicator.implicitWidth: 20 - } - } - } - - DelegateChoice { - roleValue: "int" - - RowLayout { - clip: true - - FieldLabel {} - - SpinBox { - from: map.min - to: map.max - - id: sb - - Layout.fillWidth: true - - font.pixelSize: 15 - value: model.itemValue - - onValueModified: model.itemValue = value - } - } - } - - DelegateChoice { - roleValue: "double" - - RowLayout { - clip: true - - FieldLabel {} - - DoubleSpinBox { - from: map.min - to: map.max - - id: dsb - font.pixelSize: 15 - Layout.fillWidth: true - - value: model.itemValue - - stepSize: 0.1 - - onValueModified: model.itemValue = value - } - } - } - - DelegateChoice { - roleValue: "QColor" - - RowLayout { - clip: true - - FieldLabel {} - - TextField { - id: colorField - font.pixelSize: 15 - Layout.fillWidth: true - - text: model.itemValue - - onTextEdited: model.itemValue = text - } - - Button { - Layout.fillWidth: true - text: "Pick" - - function setColor() { - colorField.text = colorDialog.selectedColor - model.itemValue = colorDialog.selectedColor - colorDialog.accepted.disconnect(setColor) - } - - onClicked: { - colorDialog.selectedColor = colorField.text - colorDialog.accepted.connect(setColor) - colorDialog.open() - } - } - } - } - - DelegateChoice { - roleValue: "list" - - RowLayout { - clip: true - - FieldLabel {} - - function setValue(v) { - model.itemValue = v - } - - ComboBox { - model: choices - - delegate: ItemDelegate { - id: delegate - - width: cb.width - contentItem: Text { - text: modelData - color: "white" - font.pixelSize: 15 - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - highlighted: choices.highlightedIndex === index - } - - id: cb - font.pixelSize: 15 - Layout.fillWidth: true - - Component.onCompleted: { - currentIndex = choices.indexOf(itemValue) - currentIndexChanged.connect(updateValue) - } - - function updateValue() { - setValue(valueAt(currentIndex)) - } - } - } - } - - DelegateChoice { - roleValue: "QVariantList" - - GridLayout { - clip: true - width: parent.width - - rowSpacing: 0 - - rows: 4 - columns: 2 - - FieldLabel {} - - function setValue(v) { - model.itemValue = v - } - - function getValue() { - // stupid workaround because of cancer - // itemValue gets corrupted to a QQmlListModel for some reason - // kill me - return model.item[model.name] - } - - HorizontalHeaderView { - Layout.minimumWidth: parent.width - 90 - Layout.row: 0 - Layout.column: 1 - - Layout.fillWidth: true - - Layout.minimumHeight: 30 - Layout.columnSpan: 2 - id: horizontalHeader - syncView: tbl - clip: true - } - - TableView { - Layout.minimumWidth: parent.width - 90 - Layout.minimumHeight: 40 - columnSpacing: 1 - rowSpacing: 1 - clip: true - - id: tbl - - Layout.fillWidth: true - - Layout.row: 1 - Layout.column: 1 - - Layout.rowSpan: 3 - - interactive: false - - function resetHeight() { - Layout.minimumHeight = 42 * tblModel.rowCount() - } - - Component.onCompleted: { - let vl = map.valueName - let iv = getValue() - - for (var i = 0; i < iv.length; ++i) { - tblModel.add(iv[i]["Value"], iv[i][vl]) - tbl.resetHeight() - } - - setValue(tblModel.asList()) - tblModel.dataChanged.connect( - () => setValue(tblModel.asList())) - } - - model: MapModel { - id: tblModel - valueName: map.valueName - } - - selectionModel: ItemSelectionModel {} - selectionBehavior: TableView.SelectRows - selectionMode: TableView.SingleSelection - - delegate: Rectangle { - border { - color: Constants.palette.text - width: 2 - } - - required property bool selected - required property bool current - - implicitWidth: tbl.width / 2 - implicitHeight: 40 - - color: current ? "blue" : "black" - - Text { - font.pixelSize: 15 - anchors.centerIn: parent - text: display - color: Constants.palette.text - } - - TableView.editDelegate: TextField { - font.pixelSize: 15 - anchors.fill: parent - text: display - horizontalAlignment: TextInput.AlignHCenter - verticalAlignment: TextInput.AlignVCenter - Component.onCompleted: selectAll() - - TableView.onCommit: { - display = text - } - } - } - } - - Button { - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.row: 1 - Layout.column: 0 - text: "Add" - - onClicked: { - tblModel.add("", "") - setValue(tblModel.asList()) - tbl.resetHeight() - } - } - - Button { - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.row: 2 - Layout.column: 0 - text: "Delete" - - onClicked: { - tblModel.remove(tbl.currentRow) - setValue(tblModel.asList()) - tbl.resetHeight() - } - } - } - } - - DelegateChoice { - roleValue: "QSizeF" - - RowLayout { - clip: true - - FieldLabel { - Layout.rowSpan: 4 - } - - Label { - text: "Width:" - font.pixelSize: 15 - } - - SpinBox { - from: 0 - to: 2000 - - id: width - - Layout.fillWidth: true - - font.pixelSize: 15 - value: model.itemValue.width - - onValueModified: model.itemValue.width = value - } - - Label { - text: "Height:" - font.pixelSize: 15 - } - - SpinBox { - from: 0 - to: 2000 - - id: height - - Layout.fillWidth: true - - font.pixelSize: 15 - value: model.itemValue.height - - onValueModified: model.itemValue.height = value - } - } - } - } - } - } - - // ListView { - // anchors { - // top: parent.top - // bottom: parent.bottom - // left: parent.left - // right: parent.right - - // margins: 8 - // } - - // spacing: 8 - - // id: listView - - // clip: true - - // boundsBehavior: Flickable.StopAtBounds - - // model: lm - - // component FieldLabel: Label { - // Layout.fillWidth: true - - // text: displayText(model.name) - // font.pixelSize: 15 - // color: Constants.palette.text - // } - - // delegate: DelegateChooser { - // role: "type" - - // // TODO: may want to find a way to avoid all this repeat code - // DelegateChoice { - // roleValue: "QString" - - // RowLayout { - // clip: true - // width: parent.width - - // uniformCellSizes: true - - // FieldLabel {} - - // TextField { - // id: txt - // Layout.fillWidth: true - - // font.pixelSize: 15 - // text: model.itemValue - - // onTextEdited: model.itemValue = text - // } - // } - // } - - // DelegateChoice { - // roleValue: "bool" - - // RowLayout { - // clip: true - // width: parent.width - - // uniformCellSizes: true - - // FieldLabel {} - - // CheckBox { - // checked: model.itemValue - - // onClicked: model.itemValue = checked - - // indicator.implicitHeight: 20 - // indicator.implicitWidth: 20 - // } - // } - // } - - // DelegateChoice { - // roleValue: "int" - - // RowLayout { - // clip: true - // width: parent.width - - // uniformCellSizes: true - - // FieldLabel {} - - // SpinBox { - // from: map.min - // to: map.max - - // id: sb - - // Layout.fillWidth: true - - // font.pixelSize: 15 - // value: model.itemValue - - // onValueModified: model.itemValue = value - // } - // } - // } - - // DelegateChoice { - // roleValue: "double" - - // RowLayout { - // clip: true - // width: parent.width - - // uniformCellSizes: true - - // FieldLabel {} - - // DoubleSpinBox { - // from: map.min - // to: map.max - - // id: dsb - // font.pixelSize: 15 - // Layout.fillWidth: true - - // value: model.itemValue - - // stepSize: 0.1 - - // onValueModified: model.itemValue = value - // } - // } - // } - - // DelegateChoice { - // roleValue: "QColor" - - // RowLayout { - // clip: true - // width: parent.width - - // uniformCellSizes: true - - // FieldLabel {} - - // TextField { - // id: colorField - // font.pixelSize: 15 - // Layout.fillWidth: true - - // text: model.itemValue - - // onTextEdited: model.itemValue = text - // } - - // Button { - // Layout.fillWidth: true - // text: "Pick" - - // function setColor() { - // colorField.text = colorDialog.selectedColor - // model.itemValue = colorDialog.selectedColor - // colorDialog.accepted.disconnect(setColor) - // } - - // onClicked: { - // colorDialog.selectedColor = colorField.text - // colorDialog.accepted.connect(setColor) - // colorDialog.open() - // } - // } - // } - // } - - // DelegateChoice { - // roleValue: "list" - - // RowLayout { - // clip: true - // width: parent.width - - // uniformCellSizes: true - - // FieldLabel {} - - // function setValue(v) { - // model.itemValue = v - // } - - // ComboBox { - // model: choices - - // delegate: ItemDelegate { - // id: delegate - - // width: choices.width - // contentItem: Text { - // text: modelData - // color: "white" - // font.pixelSize: 15 - // elide: Text.ElideRight - // verticalAlignment: Text.AlignVCenter - // } - // highlighted: choices.highlightedIndex === index - // } - - // id: cb - // font.pixelSize: 15 - // Layout.fillWidth: true - - // Component.onCompleted: { - // currentIndex = choices.indexOf(itemValue) - // currentIndexChanged.connect(updateValue) - // } - - // function updateValue() { - // setValue(valueAt(currentIndex)) - // } - // } - // } - // } - - // DelegateChoice { - // roleValue: "QVariantList" - - // GridLayout { - // clip: true - // width: parent.width - - // rowSpacing: 0 - - // rows: 4 - // columns: 2 - - // FieldLabel {} - - // function setValue(v) { - // model.itemValue = v - // } - - // function getValue() { - // // stupid workaround because of cancer - // // itemValue gets corrupted to a QQmlListModel for some reason - // // kill me - // return model.item[model.name] - // } - - // HorizontalHeaderView { - // Layout.minimumWidth: parent.width - 90 - // Layout.row: 0 - // Layout.column: 1 - - // Layout.fillWidth: true - - // Layout.minimumHeight: 30 - // Layout.columnSpan: 2 - // id: horizontalHeader - // syncView: tbl - // clip: true - // } - - // TableView { - // Layout.minimumWidth: parent.width - 90 - // Layout.minimumHeight: 40 - // columnSpacing: 1 - // rowSpacing: 1 - // clip: true - - // id: tbl - - // Layout.fillWidth: true - - // Layout.row: 1 - // Layout.column: 1 - - // Layout.rowSpan: 3 - - // interactive: false - - // function resetHeight() { - // Layout.minimumHeight = 42 * tblModel.rowCount() - // } - - // Component.onCompleted: { - // let vl = map.valueName - // let iv = getValue() - - // for (let i = 0; i < iv.length; ++i) { - // tblModel.add(iv[i]["Value"], iv[i][vl]) - // tbl.resetHeight() - // } - - // setValue(tblModel.asList()) - // tblModel.dataChanged.connect(() => setValue(tblModel.asList())) - // } - - // model: MapModel { - // id: tblModel - // valueName: map.valueName - // } - - // selectionModel: ItemSelectionModel {} - // selectionBehavior: TableView.SelectRows - // selectionMode: TableView.SingleSelection - - // delegate: Rectangle { - // border { - // color: Constants.palette.text - // width: 2 - // } - - // required property bool selected - // required property bool current - - // implicitWidth: tbl.width / 2 - // implicitHeight: 40 - - // color: current ? "blue" : "black" - - // Text { - // font.pixelSize: 15 - // anchors.centerIn: parent - // text: display - // color: Constants.palette.text - // } - - // TableView.editDelegate: TextField { - // font.pixelSize: 15 - // anchors.fill: parent - // text: display - // horizontalAlignment: TextInput.AlignHCenter - // verticalAlignment: TextInput.AlignVCenter - // Component.onCompleted: selectAll() - - // TableView.onCommit: { - // display = text - // } - // } - // } - // } - - // Button { - // Layout.alignment: Qt.AlignTop | Qt.AlignLeft - // Layout.row: 1 - // Layout.column: 0 - // text: "Add" - - // onClicked: { - // tblModel.add("", "") - // setValue(tblModel.asList()) - // tbl.resetHeight() - // } - // } - - // Button { - // Layout.alignment: Qt.AlignTop | Qt.AlignLeft - // Layout.row: 2 - // Layout.column: 0 - // text: "Delete" - - // onClicked: { - // tblModel.remove(tbl.currentRow) - // setValue(tblModel.asList()) - // tbl.resetHeight() - // } - // } - // } - // } - - // DelegateChoice { - // roleValue: "QSizeF" - - // RowLayout { - // clip: true - // width: parent.width - - // FieldLabel { - // Layout.rowSpan: 4 - // } - - // Label { - // text: "Width:" - // font.pixelSize: 15 - // } - - // SpinBox { - // from: 0 - // to: 2000 - - // id: width - - // Layout.fillWidth: true - - // font.pixelSize: 15 - // value: model.itemValue.width - - // onValueModified: model.itemValue.width = value - // } - - // Label { - // text: "Height:" - // font.pixelSize: 15 - // } - - // SpinBox { - // from: 0 - // to: 2000 - - // id: height - - // Layout.fillWidth: true - - // font.pixelSize: 15 - // value: model.itemValue.height - - // onValueModified: model.itemValue.height = value - // } - // } - // } - // } - // } -} diff --git a/icons.qrc b/icons.qrc index 0f014b9b..76a2c7b7 100644 --- a/icons.qrc +++ b/icons.qrc @@ -3,5 +3,11 @@ icons/warn.svg icons/info.svg icons/crit.svg + icons/netLight.svg + icons/netDark.svg + icons/colorLight.svg + icons/colorDark.svg + icons/miscLight.svg + icons/miscDark.svg diff --git a/icons/colorDark.svg b/icons/colorDark.svg new file mode 100644 index 00000000..a195a699 --- /dev/null +++ b/icons/colorDark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/colorLight.svg b/icons/colorLight.svg new file mode 100644 index 00000000..1bd2ade3 --- /dev/null +++ b/icons/colorLight.svg @@ -0,0 +1,8 @@ + + + + diff --git a/icons/miscDark.svg b/icons/miscDark.svg new file mode 100644 index 00000000..9f1609c2 --- /dev/null +++ b/icons/miscDark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/miscLight.svg b/icons/miscLight.svg new file mode 100644 index 00000000..562c3ae6 --- /dev/null +++ b/icons/miscLight.svg @@ -0,0 +1,15 @@ + + + + + + + diff --git a/icons/netDark.svg b/icons/netDark.svg new file mode 100644 index 00000000..0d67d3a7 --- /dev/null +++ b/icons/netDark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/icons/netLight.svg b/icons/netLight.svg new file mode 100644 index 00000000..3a2876d8 --- /dev/null +++ b/icons/netLight.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/include/Globals.h b/include/Globals.h index b37ff016..a08078bb 100644 --- a/include/Globals.h +++ b/include/Globals.h @@ -6,10 +6,9 @@ // STRUCTS // typedef struct { - bool teamNumber; - std::string server; - int port; - QString switchTopic; + int team; + std::string ip; + int mode; } ServerData; // NAMESPACES // diff --git a/include/SettingsManager.h b/include/SettingsManager.h index 4637c0b8..6299bb91 100644 --- a/include/SettingsManager.h +++ b/include/SettingsManager.h @@ -13,26 +13,24 @@ class SettingsManager : public QObject Q_PROPERTY(bool loadRecent READ loadRecent WRITE setLoadRecent NOTIFY loadRecentChanged FINAL) Q_PROPERTY(QStringList recentFiles READ recentFiles WRITE setRecentFiles NOTIFY recentFilesChanged FINAL) Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged FINAL) + Q_PROPERTY(QString accent READ accent WRITE setAccent NOTIFY accentChanged FINAL) - Q_PROPERTY(bool useTeam READ useTeam WRITE setUseTeam NOTIFY useTeamChanged FINAL) + Q_PROPERTY(int team READ team WRITE setTeam NOTIFY teamChanged FINAL) + Q_PROPERTY(int mode READ mode WRITE setMode NOTIFY modeChanged FINAL) Q_PROPERTY(QString ip READ ip WRITE setIp NOTIFY ipChanged FINAL) - Q_PROPERTY(int port READ getPort WRITE setPort NOTIFY portChanged FINAL) - Q_PROPERTY(QString switchTopic READ switchTopic WRITE setSwitchTopic NOTIFY switchTopicChanged FINAL) + public: explicit SettingsManager(QObject *parent = nullptr); // NT - bool useTeam() const; - void setUseTeam(bool newUseTeam); - QString ip() const; void setIp(const QString &newIp); - int getPort() const; - void setPort(int newPort); + int team() const; + void setTeam(int newTeam); - QString switchTopic() const; - void setSwitchTopic(const QString &newTopic); + int mode() const; + void setMode(int newMode); // other bool loadRecent() const; @@ -52,17 +50,14 @@ class SettingsManager : public QObject void setAccent(const QString &newAccent); signals: - void useTeamChanged(); - void ipChanged(); - void portChanged(); - void switchTopicChanged(); - void recentFilesChanged(); void loadRecentChanged(); void themeChanged(); void accentChanged(); -private: - Q_PROPERTY(QString accent READ accent WRITE setAccent NOTIFY accentChanged FINAL) + + void ipChanged(); + void teamChanged(); + void modeChanged(); }; #endif // SETTINGSMANAGER_H diff --git a/include/models/AccentsListModel.h b/include/models/AccentsListModel.h index f33e259a..32c6a3c4 100644 --- a/include/models/AccentsListModel.h +++ b/include/models/AccentsListModel.h @@ -55,6 +55,8 @@ class AccentsListModel : public QAbstractListModel Q_INVOKABLE void exportJson(const QString filename); Q_INVOKABLE void importJson(const QString filename); + Q_INVOKABLE QStringList names() const; + protected: QHash roleNames() const override; diff --git a/include/models/TabListModel.h b/include/models/TabListModel.h index 390bbd9f..98979b31 100644 --- a/include/models/TabListModel.h +++ b/include/models/TabListModel.h @@ -57,6 +57,8 @@ class TabListModel : public QAbstractListModel Q_INVOKABLE void loadObject(const QJsonDocument &doc); Q_INVOKABLE void load(const QString &fileName = ""); + Q_INVOKABLE void clear(); + int selectedTab() const; void selectTab(const QString &tab); diff --git a/include/models/TabWidgetsModel.h b/include/models/TabWidgetsModel.h index 24f2770a..7aa50e33 100644 --- a/include/models/TabWidgetsModel.h +++ b/include/models/TabWidgetsModel.h @@ -58,7 +58,6 @@ class TabWidgetsModel : public QAbstractListModel Q_INVOKABLE Widget copy(int idx); Q_INVOKABLE void add(Widget w); Q_INVOKABLE void add(QString title, QString topic, QString type); - Q_INVOKABLE void addCamera(QString name, QString source, QVariantList urls); Q_INVOKABLE void setEqualTo(TabWidgetsModel *w); diff --git a/items/MainScreen.qml b/items/MainScreen.qml index 940d8f3a..b5059481 100644 --- a/items/MainScreen.qml +++ b/items/MainScreen.qml @@ -26,10 +26,6 @@ Rectangle { onActivated: swipe.decrementCurrentIndex() } - function openConf(item) { - widgetConf.openUp(item) - } - function setTab() { swipe.setCurrentIndex(tlm.selectedTab) } @@ -42,6 +38,9 @@ Rectangle { } tlm.onSelectedTabChanged.connect(setTab) + + Constants.setTheme(settings.theme) + Constants.setAccent(settings.accent) } function drag(pos, fromList) { @@ -134,10 +133,6 @@ Rectangle { id: tabSizeDialog } - WidgetConfig { - id: widgetConf - } - /** SAVE */ FileDialog { id: saveDialog @@ -249,59 +244,12 @@ Rectangle { } /** SERVER SETTINGS */ - ServerDialog { - id: serverDialog - } - - function serverSettings() { - serverDialog.open() - } - - /** CUSTOM ACCENTS */ - AccentEditor { - id: accentEditor - } - - function editAccents() { - accentEditor.open() - } - - FileDialog { - id: saveAccentDialog - currentFolder: StandardPaths.writableLocation( - StandardPaths.HomeLocation) - fileMode: FileDialog.SaveFile - defaultSuffix: "json" - selectedNameFilter.index: 0 - nameFilters: ["JSON files (*.json)", "All files (*)"] - } - - function exportAccents() { - accents.exportJson(saveAccentDialog.selectedFile) - } - - function exportAccentsAction() { - saveAccentDialog.accepted.connect(exportAccents) - saveAccentDialog.open() - } - - FileDialog { - id: loadAccentDialog - currentFolder: StandardPaths.writableLocation( - StandardPaths.HomeLocation) - fileMode: FileDialog.OpenFile - defaultSuffix: "json" - selectedNameFilter.index: 0 - nameFilters: ["JSON files (*.json)", "All files (*)"] - } - - function importAccents() { - accents.importJson(loadAccentDialog.selectedFile) + SettingsDialog { + id: settingsDialog } - function importAccentsAction() { - loadAccentDialog.accepted.connect(importAccents) - loadAccentDialog.open() + function settingsDialog() { + settingsDialog.openDialog() } /** CONTENT */ diff --git a/main.cpp b/main.cpp index e30f9fb5..3080b3fc 100644 --- a/main.cpp +++ b/main.cpp @@ -51,8 +51,7 @@ int main(int argc, char *argv[]) }); Globals::inst.StartClient4(BuildConfig.APP_NAME.toStdString()); - Globals::inst.SetServer(Globals::server.server.c_str(), NT_DEFAULT_PORT4); - Globals::inst.StartDSClient(); + Globals::inst.StartDSClient(NT_DEFAULT_PORT4); Globals::inst.AddListener({{""}}, nt::EventFlags::kTopic, [topics] (const nt::Event &event) { std::string topicName(event.GetTopicInfo()->name); diff --git a/src/Globals.cpp b/src/Globals.cpp index 296bf545..6aee0393 100644 --- a/src/Globals.cpp +++ b/src/Globals.cpp @@ -3,4 +3,4 @@ #include nt::NetworkTableInstance Globals::inst = nt::NetworkTableInstance::GetDefault(); -ServerData Globals::server{false, "0.0.0.0", NT_DEFAULT_PORT4}; +ServerData Globals::server{0, "0.0.0.0", 0}; diff --git a/src/SettingsManager.cpp b/src/SettingsManager.cpp index bf60a0fe..33a58faa 100644 --- a/src/SettingsManager.cpp +++ b/src/SettingsManager.cpp @@ -7,33 +7,28 @@ SettingsManager::SettingsManager(QObject *parent) {} void SettingsManager::reconnectServer() { - std::string server = Globals::server.server; - bool isTeamNumber = Globals::server.teamNumber; - int port = Globals::server.port; - QString switchTopic = Globals::server.switchTopic; - - if (server.empty()) return; - - if (isTeamNumber) { - int team; - try { - team = std::stoi(server); - } catch (std::invalid_argument const &) { - return; - } - - Globals::inst.SetServerTeam(team, port); - } else { - Globals::inst.SetServer(server.c_str(), port); + std::string server = Globals::server.ip; + int team = Globals::server.team; + int mode = Globals::server.mode; + + switch (mode) { + // IP Address + case 0: + Globals::inst.SetServer(server.c_str(), NT_DEFAULT_PORT4); + break; + // Team Number + case 1: + Globals::inst.SetServerTeam(team, NT_DEFAULT_PORT4); + break; + // DS + case 2: + Globals::inst.StartDSClient(NT_DEFAULT_PORT4); + break; + default: + break; } Globals::inst.Disconnect(); - - QString serverTopic = Globals::server.switchTopic; - - if (serverTopic != switchTopic) { - emit switchTopicChanged(); - } } void SettingsManager::addRecentFile(QFile &file) { @@ -102,50 +97,39 @@ void SettingsManager::setAccent(const QString &newAccent) emit accentChanged(); } -int SettingsManager::getPort() const +QString SettingsManager::ip() const { - return Globals::server.port; + return QString::fromStdString(Globals::server.ip); } -void SettingsManager::setPort(int newPort) +void SettingsManager::setIp(const QString &newIp) { - Globals::server.port = newPort; - emit portChanged(); + Globals::server.ip = newIp.toStdString(); + emit ipChanged(); reconnectServer(); } -QString SettingsManager::switchTopic() const -{ - return Globals::server.switchTopic; -} - -void SettingsManager::setSwitchTopic(const QString &newTopic) -{ - Globals::server.switchTopic = newTopic; - emit switchTopicChanged(); - reconnectServer(); -} -QString SettingsManager::ip() const +int SettingsManager::team() const { - return QString::fromStdString(Globals::server.server); + return Globals::server.team; } -void SettingsManager::setIp(const QString &newIp) +void SettingsManager::setTeam(int newTeam) { - Globals::server.server = newIp.toStdString(); - emit ipChanged(); + Globals::server.team = newTeam; + emit teamChanged(); reconnectServer(); } -bool SettingsManager::useTeam() const +int SettingsManager::mode() const { - return Globals::server.teamNumber; + return Globals::server.mode; } -void SettingsManager::setUseTeam(bool newUseTeam) +void SettingsManager::setMode(int newMode) { - Globals::server.teamNumber = newUseTeam; - emit useTeamChanged(); + Globals::server.mode = newMode; + emit modeChanged(); reconnectServer(); } diff --git a/src/models/AccentsListModel.cpp b/src/models/AccentsListModel.cpp index 6eb22bdf..b3b07e1e 100644 --- a/src/models/AccentsListModel.cpp +++ b/src/models/AccentsListModel.cpp @@ -246,6 +246,20 @@ void AccentsListModel::importJson(const QString filename) save(); } +QStringList AccentsListModel::names() const +{ + QStringList list; + for (const Accent &a : m_data) { + // convert to title case + QString name = a.name; + QChar first = name.at(0).toUpper(); + + list.append(first + name.last(name.size() - 1)); + } + + return list; +} + QHash AccentsListModel::roleNames() const { QHash rez; diff --git a/src/models/TabListModel.cpp b/src/models/TabListModel.cpp index a8ababd4..45054a9f 100644 --- a/src/models/TabListModel.cpp +++ b/src/models/TabListModel.cpp @@ -123,8 +123,8 @@ void TabListModel::save(const QString &filename) QJsonDocument TabListModel::saveObject() const { QJsonObject doc; - doc.insert("useTeamNumber", m_settings->useTeam()); - doc.insert("port", m_settings->getPort()); + doc.insert("mode", m_settings->mode()); + doc.insert("team", m_settings->team()); doc.insert("ip", m_settings->ip()); QJsonArray arr; @@ -149,9 +149,9 @@ void TabListModel::loadObject(const QJsonDocument &doc) { QJsonObject ob = doc.object(); - Globals::server.server = ob.value("ip").toString().toStdString(); - Globals::server.port = ob.value("port").toInt(); - Globals::server.teamNumber = ob.value("useTeamNumber").toBool(); + Globals::server.ip = ob.value("ip").toString().toStdString(); + Globals::server.team = ob.value("team").toInteger(); + Globals::server.mode = ob.value("mode").toInteger(); m_settings->reconnectServer(); @@ -190,14 +190,20 @@ void TabListModel::load(const QString &filename) QByteArray data = stream.readAll().toUtf8(); QJsonDocument doc = QJsonDocument::fromJson(data); - beginResetModel(); - m_data.clear(); - endResetModel(); + + clear(); loadObject(doc); file.close(); } +void TabListModel::clear() +{ + beginResetModel(); + m_data.clear(); + endResetModel(); +} + int TabListModel::selectedTab() const { return m_selectedTab; diff --git a/src/models/TabWidgetsModel.cpp b/src/models/TabWidgetsModel.cpp index 55bfc838..558d0f61 100644 --- a/src/models/TabWidgetsModel.cpp +++ b/src/models/TabWidgetsModel.cpp @@ -131,54 +131,6 @@ void TabWidgetsModel::add(QString title, QString topic, QString type) emit unoccupiedCellsChanged(); } -void TabWidgetsModel::addCamera(QString name, QString source, QVariantList urls) -{ - Widget w; - w.title = name; - w.type = "camera"; - - w.properties.insert("name", name); - w.properties.insert("source", source); - - QUrl url; - if (urls.empty()) { - url = source; - } else { - for (const QVariant &u : urls) { - // pure, unfaltered cancer - QString str = u.value().toString(); - - if (str.contains(".local")) { - continue; - } - - url = str; - } - - if (url.isEmpty()) { - url = urls.at(0).value().toString(); - } - } - - if (!url.isEmpty()) { - QStringList split = url.toString().split(":"); - w.properties.insert("host", (split.at(0) + ":" + split.at(1)));; - w.properties.insert("port", split.at(2).split("/").at(0).toInt()); - } - - w.row = -1; - w.col = -1; - - w.rowSpan = 1; - w.colSpan = 1; - - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_data << w; - endInsertRows(); - - emit unoccupiedCellsChanged(); -} - void TabWidgetsModel::setEqualTo(TabWidgetsModel *w) { beginResetModel(); diff --git a/widgets/BaseWidget.qml b/widgets/BaseWidget.qml index c32b70d1..7ab30085 100644 --- a/widgets/BaseWidget.qml +++ b/widgets/BaseWidget.qml @@ -106,7 +106,6 @@ Rectangle { MenuItem { text: "Configure" - // onTriggered: openConf(rcMenu.parent) onTriggered: config.openDialog() } diff --git a/widgets/config/LabeledComboBox.qml b/widgets/config/LabeledComboBox.qml index cd719e47..642b22a3 100644 --- a/widgets/config/LabeledComboBox.qml +++ b/widgets/config/LabeledComboBox.qml @@ -21,6 +21,8 @@ ComboBox { model: choices font.pixelSize: 15 + height: 50 + function open() { currentIndex = indexOfValue(bindTarget[bindedProperty]) } diff --git a/widgets/config/LabeledDoubleSpinBox.qml b/widgets/config/LabeledDoubleSpinBox.qml index e7a6f3f7..84ce768d 100644 --- a/widgets/config/LabeledDoubleSpinBox.qml +++ b/widgets/config/LabeledDoubleSpinBox.qml @@ -19,6 +19,8 @@ DoubleSpinBox { from: -1E9 to: 1E9 + height: 50 + font.pixelSize: 18 function open() { diff --git a/widgets/misc/CameraView.qml b/widgets/misc/CameraView.qml index 09809112..14a2226c 100644 --- a/widgets/misc/CameraView.qml +++ b/widgets/misc/CameraView.qml @@ -46,8 +46,6 @@ BaseWidget { --i } } - - console.log(value) } function updateTopic(ntTopic, ntValue) { diff --git a/widgets/primitive/IntDialWidget.qml b/widgets/primitive/IntDialWidget.qml index 8437d3ca..cd519b71 100644 --- a/widgets/primitive/IntDialWidget.qml +++ b/widgets/primitive/IntDialWidget.qml @@ -1,9 +1,12 @@ import QtQuick import QtQuick.Controls 6.6 +import QtQuick.Layouts 6.6 import QFRCDashboard BaseWidget { + id: widget + property string item_topic property int item_fontSize: 15 @@ -159,4 +162,172 @@ BaseWidget { spin.value = topicStore.getValue(item_topic) dial.value = topicStore.getValue(item_topic) } + + BaseConfigDialog { + id: config + + height: 600 + + function openDialog() { + topicField.open() + titleFontField.open() + fontField.open() + + upField.open() + lowField.open() + stepField.open() + startField.open() + endField.open() + + open() + } + + onAccepted: { + topicField.accept() + titleFontField.accept() + fontField.accept() + upField.accept() + lowField.accept() + stepField.accept() + startField.accept() + endField.accept() + } + + ColumnLayout { + id: layout + spacing: 25 + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + right: parent.right + + topMargin: config.headerHeight + 12 + bottomMargin: 45 + + leftMargin: 5 + rightMargin: 5 + } + + SectionHeader { + label: "Font Settings" + } + + RowLayout { + uniformCellSizes: true + + LabeledSpinBox { + Layout.fillWidth: true + + id: titleFontField + + label: "Title Font Size" + + bindedProperty: "item_titleFontSize" + bindTarget: widget + } + + LabeledSpinBox { + Layout.fillWidth: true + + id: fontField + + label: "Font Size" + + bindedProperty: "item_fontSize" + bindTarget: widget + } + } + + SectionHeader { + label: "Spin Box Settings" + } + + RowLayout { + uniformCellSizes: true + + LabeledSpinBox { + Layout.fillWidth: true + + id: lowField + + label: "Lower Bound" + + bindedProperty: "item_lowerBound" + bindTarget: widget + } + + LabeledSpinBox { + Layout.fillWidth: true + + id: upField + + label: "Upper Bound" + + bindedProperty: "item_upperBound" + bindTarget: widget + } + } + + LabeledSpinBox { + Layout.fillWidth: true + + id: stepField + + label: "Step Size" + + bindedProperty: "item_stepSize" + bindTarget: widget + + from: 0 + } + + SectionHeader { + label: "Dial Settings" + } + + RowLayout { + uniformCellSizes: true + + LabeledDoubleSpinBox { + Layout.fillWidth: true + + id: startField + + label: "Start Angle" + + bindedProperty: "item_startAngle" + bindTarget: widget + } + + LabeledDoubleSpinBox { + Layout.fillWidth: true + + id: endField + + label: "End Angle" + + bindedProperty: "item_endAngle" + bindTarget: widget + } + } + + SectionHeader { + label: "NT Settings" + } + + LabeledTextField { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + + id: topicField + + label: "Topic" + + bindedProperty: "item_topic" + bindTarget: widget + } + } + } }