diff --git a/resources/ConnectionScreen.qml b/resources/ConnectionScreen.qml index cab1a8d7d..a11236ac5 100644 --- a/resources/ConnectionScreen.qml +++ b/resources/ConnectionScreen.qml @@ -33,9 +33,7 @@ Item { } Rectangle { - width: parent.width - height: parent.height - anchors.centerIn: parent + anchors.fill: parent Image { width: parent.width @@ -43,6 +41,13 @@ Item { source: Constants.icons.splashScreenPath } + Rectangle { + anchors.left: parent.left + height: parent.height + width: 1 + color: "white" + } + Dialog { id: dialog diff --git a/resources/Constants/Constants.qml b/resources/Constants/Constants.qml index a958b6726..84381e6ea 100644 --- a/resources/Constants/Constants.qml +++ b/resources/Constants/Constants.qml @@ -6,7 +6,7 @@ pragma Singleton QtObject { readonly property real margins: 2 readonly property real tabBarWidth: 70 - readonly property real tabBarHeight: 40 + readonly property real tabBarHeight: 45 readonly property real topLevelSpacing: 0 readonly property real logPanelPreferredHeight: 100 readonly property int loggingBarPreferredHeight: 50 @@ -50,6 +50,10 @@ QtObject { readonly property real mediumPointSize: 8 readonly property real largePointSize: 9 readonly property bool debugMode: false + readonly property color swiftGrey: "#323F48" + readonly property color swiftLightGrey: "#3C464F" + readonly property color swiftControlBackground: "#ECECEC" + readonly property color tabButtonUnselectedTextColor: "#767676" readonly property color materialGrey: "dimgrey" readonly property color swiftOrange: "#FF8300" @@ -92,8 +96,7 @@ QtObject { readonly property string updatePath: "qrc:///images/fontawesome/chevron-circle-up-solid.svg" readonly property string advancedPath: "qrc:///images/fontawesome/lock-solid.svg" readonly property real tabBarHeight: 45 - readonly property real tabBarWidth: 70 - readonly property int tabBarSpacing: 10 + readonly property real tabBarWidth: 50 readonly property int buttonPadding: 0 readonly property int buttonInset: 0 readonly property int separatorMargin: 10 diff --git a/resources/Constants/utils.js b/resources/Constants/utils.js index 7a3550887..2c20a23a5 100644 --- a/resources/Constants/utils.js +++ b/resources/Constants/utils.js @@ -34,3 +34,11 @@ function readTextFile(path, elem){ req.open("GET", path); req.send(); } + +// Dump all properties in a QML item (or any javascript object). +function listProperty(item) +{ + for (var p in item) + if (typeof item[p] != "function") + console.log(p + ": " + item[p]); +} diff --git a/resources/MainTabs.qml b/resources/MainTabs.qml index 1e87110fa..bcb9ebc44 100644 --- a/resources/MainTabs.qml +++ b/resources/MainTabs.qml @@ -4,14 +4,12 @@ import QtQuick.Layouts 1.15 import SwiftConsole 1.0 Item { + property alias currentIndex: stackLayout.currentIndex + StackLayout { id: stackLayout anchors.fill: parent - currentIndex: parent.curIndex - - Item { - } TrackingTab { } diff --git a/resources/SideNavBar.qml b/resources/SideNavBar.qml index 2bf34a5fb..0ef266782 100644 --- a/resources/SideNavBar.qml +++ b/resources/SideNavBar.qml @@ -1,11 +1,15 @@ import "Constants" -import QtQuick 2.5 -import QtQuick.Controls 2.3 +import QtQuick 2.15 +import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import "SideNavBarComponents" import SwiftConsole 1.0 -Rectangle { - property alias curIndex: tab.currentIndex +Item { + id: top + + property alias currentIndex: navButtons.currentIndex + property bool enabled: true property var tabModel: [{ "name": "Tracking", "tooltip": "Tracking", @@ -36,8 +40,6 @@ Rectangle { "source": Constants.sideNavBar.advancedPath }] - color: Constants.sideNavBar.backgroundColor - ConnectionData { id: connectionData } @@ -45,86 +47,52 @@ Rectangle { ColumnLayout { anchors.fill: parent - Button { - id: logo - - Layout.fillWidth: true - Layout.preferredHeight: Constants.sideNavBar.tabBarHeight - padding: Constants.sideNavBar.buttonPadding - icon.source: Constants.icons.swiftLogoPath - icon.color: "transparent" - ToolTip.visible: hovered - ToolTip.text: "About this application" - onClicked: { - logoPopup.open(); - } - - background: Item { - } - - } - Rectangle { - color: Constants.materialGrey - Layout.alignment: Qt.AlignHCenter - Layout.preferredHeight: Constants.sideNavBar.separatorHeight - Layout.fillWidth: true - Layout.rightMargin: Constants.sideNavBar.separatorMargin - Layout.leftMargin: Constants.sideNavBar.separatorMargin - } - - TabBar { - id: tab - Layout.fillWidth: true Layout.fillHeight: true - z: Constants.commonChart.zAboveCharts - height: parent.height - contentHeight: Constants.sideNavBar.tabBarHeight - contentWidth: Constants.sideNavBar.tabBarWidth - currentIndex: Globals.initialMainTabIndex + 1 - Component.onCompleted: { - logo.checkable = false; - } + color: Constants.swiftGrey - TabButton { - enabled: false - height: 0 + ButtonGroup { + id: navButtonGroup + + buttons: navButtons.children + onCheckedButtonChanged: { + for (var idx = 0; idx < buttons.length && buttons[idx] != checkedButton; idx++); + navButtons.currentIndex = idx; + } } - Repeater { - id: repeater + ListView { + id: navButtons + anchors.fill: parent model: tabModel + currentIndex: Globals.initialMainTabIndex + enabled: top.enabled + highlightMoveDuration: 200 + highlightResizeDuration: 0 + highlightFollowsCurrentItem: true + Component.onCompleted: { + currentItem.checked = true; + } - TabButton { - text: modelData.name - width: Constants.sideNavBar.tabBarWidth - anchors.horizontalCenter: parent.horizontalCenter - icon.source: modelData.source - icon.color: checked ? Constants.swiftOrange : Constants.materialGrey - display: AbstractButton.TextUnderIcon - font.pointSize: Constants.smallPointSize - padding: Constants.sideNavBar.buttonPadding - rightInset: Constants.sideNavBar.buttonInset - leftInset: Constants.sideNavBar.buttonInset - enabled: Globals.connected_at_least_once - ToolTip.visible: hovered - ToolTip.text: modelData.tooltip - onClicked: { - if (stack.connectionScreenVisible()) - stack.mainView(); + highlight: Item { + // TODO: This is an odd z order which depends on the Z order of some things in the buttons, refactor. + z: 6 + Rectangle { + height: 2 + width: parent.width + y: parent.height - height + color: Constants.swiftOrange } + } - } + delegate: SideNavButton { + buttonGroup: navButtonGroup + } - contentItem: ListView { - model: tab.contentModel - currentIndex: tab.currentIndex - spacing: Constants.sideNavBar.tabBarSpacing - orientation: ListView.Vertical } } @@ -134,8 +102,10 @@ Rectangle { Layout.alignment: Qt.AlignBottom Layout.preferredWidth: Constants.sideNavBar.tabBarWidth + border: false icon.source: Constants.icons.lightningBoltPath - icon.color: checked ? Constants.swiftOrange : Constants.materialGrey + icon.color: !enabled ? Constants.materialGrey : Constants.swiftOrange + backgroundColor: hovered ? Qt.darker("white", 1.1) : "white" checkable: false padding: Constants.sideNavBar.buttonPadding rightInset: Constants.sideNavBar.buttonInset diff --git a/resources/SideNavBarComponents/SideNavButton.qml b/resources/SideNavBarComponents/SideNavButton.qml new file mode 100644 index 000000000..96dd5f086 --- /dev/null +++ b/resources/SideNavBarComponents/SideNavButton.qml @@ -0,0 +1,92 @@ +import "../Constants" +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Controls.impl 2.15 + +// This defines a Side Navigation button as is specified in the Style mockup guidelines listed here: +// https://snav.slack.com/archives/C020JLK6PK8/p1637094700036500 +// To make the buttons within exclusive, the user needs to create a ButtonGroup, and assign +// all buttons to that buttongroup +Button { + id: control + + property QtObject buttonGroup + property QtObject view: ListView.view + + ButtonGroup.group: buttonGroup + width: view.width + height: implicitHeight < width ? width : implicitHeight + z: visualFocus ? 10 : control.checked || control.highlighted ? 5 : 1 + display: AbstractButton.TextUnderIcon + checkable: true + text: modelData.name + ToolTip.text: modelData.tooltip + ToolTip.delay: 1000 + ToolTip.timeout: 5000 + ToolTip.visible: ToolTip.text.length != 0 && hovered + icon.source: modelData.source + icon.width: 22 + icon.height: 22 + icon.color: control.checked || control.highlighted ? Qt.darker(Constants.swiftOrange, control.enabled ? 1 : 1.5) : control.flat && !control.down ? (control.visualFocus ? Constants.swiftOrange : control.palette.windowText) : Qt.darker("white", control.enabled ? 1 : 1.5) + font.pointSize: Constants.smallPointSize + font.capitalization: Font.MixedCase + font.letterSpacing: -1 + // No idea why the insets are set, but they need to be 0 so there are no gaps between buttons. + topInset: 0 + bottomInset: 0 + // Padding controls the padding around items within the button. We want to minimize it, so set + // to 0. Realistically if we weren't setting an explicit width (set by the SideBar width), we'd + // want more padding than 0. + padding: 0 + // Spacing controls the spacing between the icon and the text. Default is 6, we reduce this to + // 3 to match the new style mockups. + spacing: 3 + Component.onCompleted: { + console.assert(buttonGroup != undefined, "No buttonGroup assigned to SideNavButton! Undesired behavior will result."); + } + + contentItem: IconLabel { + spacing: control.spacing + mirrored: control.mirrored + display: control.display + icon: control.icon + text: control.text + font: control.font + color: control.checked || control.highlighted ? Qt.darker(control.palette.dark, control.enabled ? 1 : 1.5) : control.flat && !control.down ? (control.visualFocus ? Constants.swiftOrange : control.palette.windowText) : Qt.darker("white", control.enabled ? 1 : 1.5) + } + + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + visible: !control.flat || control.down || control.checked || control.highlighted + color: Color.blend(control.checked || control.highlighted ? Qt.darker("white", control.enabled ? 1 : 1.5) : Constants.swiftGrey, control.palette.mid, control.down ? 0.5 : 0) + border.color: Constants.swiftOrange + border.width: control.visualFocus ? 1 : 0 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 1 + color: Qt.darker("white", control.enabled ? 1 : 1.5) + visible: !control.visualFocus && !(control.view.itemAtIndex(index + 1) != null ? control.view.itemAtIndex(index + 1).visualFocus : false) + } + + Repeater { + model: 2 + + Rectangle { + anchors.left: index == 0 ? parent.left : undefined + anchors.right: index == 1 ? parent.right : undefined + y: -1 + height: parent.height + 1 + width: 1 + color: Constants.swiftGrey + visible: !control.visualFocus && (control.checked || control.highlighted) && control.enabled + } + + } + + } + +} diff --git a/resources/Theme/Theme.qml b/resources/Theme/Theme.qml new file mode 100644 index 000000000..4ccd822d5 --- /dev/null +++ b/resources/Theme/Theme.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +pragma Singleton + +QtObject { + readonly property color gray: "#b2b1b1" + readonly property color lightGray: "#dddddd" + readonly property color light: "#ffffff" + readonly property color blue: "#2d548b" + property color mainColor: "#17a81a" + readonly property color dark: "#222222" + readonly property color mainColorDarker: Qt.darker(mainColor, 1.5) + property int baseSize: 10 + readonly property int smallSize: 10 + readonly property int largeSize: 16 + property font font + + font.bold: true + font.underline: false + font.pixelSize: 14 + font.family: "arial" +} diff --git a/resources/Theme/qmldir b/resources/Theme/qmldir new file mode 100644 index 000000000..4a58c13a9 --- /dev/null +++ b/resources/Theme/qmldir @@ -0,0 +1,2 @@ +module Theme +singleton Theme 1.0 Theme.qml diff --git a/resources/TrackingTab.qml b/resources/TrackingTab.qml index eb4da422a..fb851c119 100644 --- a/resources/TrackingTab.qml +++ b/resources/TrackingTab.qml @@ -14,6 +14,8 @@ Item { TabBar { id: trackingBar + anchors.left: parent.left + anchors.right: parent.right z: Constants.commonChart.zAboveCharts currentIndex: Globals.initialMainTabIndex == 0 ? Globals.initialSubTabIndex : 0 contentHeight: Constants.tabBarHeight diff --git a/resources/console_resources.qrc b/resources/console_resources.qrc index 032027b2c..f503d316a 100644 --- a/resources/console_resources.qrc +++ b/resources/console_resources.qrc @@ -8,6 +8,7 @@ MainDialogView.qml MainTabs.qml SideNavBar.qml + SideNavBarComponents/SideNavButton.qml LogPanel.qml LoggingBar.qml StatusBar.qml @@ -100,5 +101,9 @@ BaseComponents/SmallCheckBox.qml BaseComponents/SwiftGroupBox.qml ChartLegend.qml + Theme/qmldir + Theme/Theme.qml + styles/SwiftNav/TabBar.qml + styles/SwiftNav/TabButton.qml diff --git a/resources/qtquickcontrols2.conf b/resources/qtquickcontrols2.conf index 70d12b8de..b721e116c 100644 --- a/resources/qtquickcontrols2.conf +++ b/resources/qtquickcontrols2.conf @@ -1,8 +1,12 @@ [Controls] -Style=Material +Style=SwiftNav +FallbackStyle=Material [Material] Variant=Dense +Accent="#FF8300" +# Theme=Light +# Primary= [Default] Font\Family=Roboto diff --git a/resources/styles/SwiftNav/TabBar.qml b/resources/styles/SwiftNav/TabBar.qml new file mode 100644 index 000000000..cb258f4c2 --- /dev/null +++ b/resources/styles/SwiftNav/TabBar.qml @@ -0,0 +1,76 @@ +import QtQuick 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Controls.Material.impl 2.12 +import QtQuick.Templates 2.12 as T + +T.TabBar { + id: control + + property var orientation: ListView.Horizontal + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding) + spacing: -1 + + contentItem: ListView { + model: control.contentModel + currentIndex: control.currentIndex + spacing: control.spacing + orientation: control.orientation + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.AutoFlickIfNeeded + snapMode: ListView.SnapToItem + highlightMoveDuration: 250 + highlightResizeDuration: 0 + highlightFollowsCurrentItem: true + highlightRangeMode: ListView.ApplyRange + preferredHighlightBegin: 48 + preferredHighlightEnd: width - 48 + + highlight: Item { + z: 2 + + Rectangle { + x: 1 + height: 2 + width: parent.width - 2 + y: control.position === T.TabBar.Footer ? 0 : parent.height - height + color: control.Material.accentColor + } + + } + + } + + background: Item { + Rectangle { + anchors.fill: parent + color: "white" + layer.enabled: control.Material.elevation > 0 + + layer.effect: ElevationEffect { + elevation: control.Material.elevation + fullWidth: true + } + + } + + Rectangle { + z: 2 + anchors.top: parent.top + width: parent.width + height: 1 + color: "#C2C2C2" + } + + Rectangle { + z: 2 + anchors.bottom: parent.bottom + width: parent.width + height: 1 + color: "#C2C2C2" + } + + } + +} diff --git a/resources/styles/SwiftNav/TabButton.qml b/resources/styles/SwiftNav/TabButton.qml new file mode 100644 index 000000000..2d242f3fd --- /dev/null +++ b/resources/styles/SwiftNav/TabButton.qml @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import "../Constants" +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Controls.Material.impl 2.12 +import QtQuick.Controls.impl 2.12 +import QtQuick.Templates 2.12 as T + +T.TabButton { + id: control + + property color labelColor: !control.enabled ? control.Material.hintTextColor : control.down || control.checked ? "white" : Constants.tabButtonUnselectedTextColor + property color backgroundColor: down || checked ? Constants.swiftGrey : hovered ? Qt.darker(Constants.swiftControlBackground, 1.1) : Constants.swiftControlBackground + property bool border: true + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + padding: 12 + spacing: 6 + font: Qt.font({ + "family": "Roboto", + "pointSize": Constants.largePointSize, + "bold": true, + "capitalization": Font.MixedCase + }) + icon.width: 24 + icon.height: 24 + icon.color: !enabled ? Material.hintTextColor : down || checked ? "white" : Constants.tabButtonUnselectedTextColor + + contentItem: IconLabel { + spacing: control.spacing + mirrored: control.mirrored + display: control.display + icon: control.icon + text: control.text + font: control.font + color: control.labelColor + } + + background: Rectangle { + border.width: control.border ? 1 : 0 + border.color: "#C2C2C2" + implicitHeight: control.Material.touchTarget + clip: true + color: backgroundColor + } + +} diff --git a/resources/view.qml b/resources/view.qml index 7af7fb5cf..4ab5237f8 100644 --- a/resources/view.qml +++ b/resources/view.qml @@ -33,12 +33,14 @@ ApplicationWindow { property alias stackView: dialogStack.dialogStack anchors.fill: parent + spacing: 0 SideNavBar { id: sideNavBar Layout.fillHeight: true Layout.minimumWidth: Constants.sideNavBar.tabBarWidth + enabled: stack.currentIndex != 0 } StackLayout { @@ -76,20 +78,16 @@ ApplicationWindow { orientation: Qt.Vertical Layout.fillWidth: true Layout.fillHeight: true - Layout.leftMargin: Constants.margins - Layout.rightMargin: Constants.margins Layout.alignment: Qt.AlignTop MainTabs { id: mainTabs - property alias curIndex: sideNavBar.curIndex - property alias logoPopup: logoPopup - SplitView.fillHeight: true SplitView.fillWidth: true Layout.leftMargin: Constants.margins Layout.rightMargin: Constants.margins + currentIndex: sideNavBar.currentIndex } ColumnLayout { diff --git a/swiftnav_console/main.py b/swiftnav_console/main.py index 642028a8f..625b40699 100644 --- a/swiftnav_console/main.py +++ b/swiftnav_console/main.py @@ -837,6 +837,7 @@ def handle_qml_load_errors(obj, _url): capnp_path = get_capnp_path() engine.addImportPath("PySide2") + engine.addImportPath(":/") engine.load(QUrl("qrc:/view.qml")) if not qml_object_created[0]: return 1