535 lines
18 KiB
QML
Executable file
535 lines
18 KiB
QML
Executable file
/****************************************************************************
|
|
** ScStw Monitor
|
|
** Copyright (C) 2020 Itsblue development
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
****************************************************************************/
|
|
|
|
import QtQuick 2.9
|
|
import QtQuick.Window 2.2
|
|
import QtQuick.Controls 2.2
|
|
import QtGraphicalEffects 1.0
|
|
import Qt.labs.settings 1.0
|
|
import QtQuick.Layouts 1.0
|
|
|
|
import de.itsblue.ScStw 2.0
|
|
import de.itsblue.ScStwMonitor 2.0
|
|
import de.itsblue.ScStw.Styling 2.0
|
|
import de.itsblue.ScStw.Styling.Components 1.0
|
|
|
|
Window {
|
|
id: window
|
|
visible: true
|
|
width: XscreenWidth
|
|
height: XscreenHeight
|
|
title: qsTr("ScStwMonitor")
|
|
|
|
visibility: Window.FullScreen
|
|
|
|
Page {
|
|
id: app
|
|
anchors.fill: parent
|
|
|
|
background: Rectangle {
|
|
color: appTheme.theme.colors.background
|
|
}
|
|
|
|
function landscape() {
|
|
return app.width > app.height
|
|
}
|
|
|
|
ScStwMonitorBackend {
|
|
id: backend
|
|
scStwClient.ipAddress: appSettings.baseStationIp
|
|
}
|
|
|
|
ScStwAppThemeManager {
|
|
id: appTheme
|
|
|
|
Component.onCompleted: {
|
|
appTheme.setTheme(darkMode ? "Dark":"Light")
|
|
}
|
|
}
|
|
|
|
Settings {
|
|
id: appSettings
|
|
property string baseStationIp: "127.0.0.1"
|
|
}
|
|
|
|
Shortcut {
|
|
sequences: ["Ctrl+Q", StandardKey.Back]
|
|
onActivated: Qt.quit()
|
|
}
|
|
|
|
Shortcut {
|
|
sequences: ["F11", "Esc"]
|
|
onActivated: {
|
|
if(window.visibility === Window.FullScreen) {
|
|
window.visibility = Window.Windowed
|
|
}
|
|
else {
|
|
window.visibility = Window.FullScreen
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: mainComponentLoader
|
|
|
|
anchors.fill: parent
|
|
|
|
sourceComponent: backend.scStwClient.state === ScStwClient.CONNECTED ? displayComp:loadingComp
|
|
}
|
|
|
|
Component {
|
|
id: displayComp
|
|
|
|
Column {
|
|
|
|
Rectangle {
|
|
id: topBanner
|
|
width: parent.width
|
|
height: !showControls && showBanners ? backend.race.state === ScStwRace.IDLE ? parent.height * 0.1:parent.height * 0.05 : 0
|
|
color: "black"
|
|
|
|
Text {
|
|
|
|
anchors.fill: parent
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
font.pixelSize: height * 0.5
|
|
|
|
visible: height > 0
|
|
|
|
color: "white"
|
|
|
|
text: qsTr("Speed Climbing Stopwatch")
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: 200
|
|
easing.type: Easing.Linear
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: displayItm
|
|
|
|
width: parent.width
|
|
height: parent.height - bottomBanner.height - topBanner.height
|
|
|
|
Image {
|
|
id: bannerImg
|
|
|
|
anchors {
|
|
top: parent.top
|
|
left: parent.left
|
|
right: parent.right
|
|
margins: app.landscape() ? app.height * 0.01:app.width * 0.1
|
|
}
|
|
|
|
height: app.landscape() ? app.height * 0.25:app.height * 0.2
|
|
|
|
visible: showControls
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
mipmap: true
|
|
|
|
source: "qrc:/Banner.png"
|
|
}
|
|
|
|
TimerColumn {
|
|
anchors.fill: parent
|
|
|
|
timers: removeDisabledTimers(backend.race.timers)
|
|
|
|
colors: appTheme.theme.colors
|
|
fontName: appTheme.theme.fonts.timers
|
|
|
|
opacity: !showControls || [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) < 0 ? 1:0
|
|
|
|
function removeDisabledTimers(timers) {
|
|
var ret = []
|
|
for(var i = 0; i < timers.length; i++) {
|
|
if(timers[i]["state"] !== ScStwTimer.DISABLED)
|
|
ret.push(timers[i])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: 200
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: controlsItm
|
|
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
bottom: parent.bottom
|
|
}
|
|
|
|
visible: showControls
|
|
|
|
Label {
|
|
id: clickHintLabel
|
|
|
|
property string implicitText:[
|
|
"tap anywhere\nto start",
|
|
"NEXT_START_ACTION",
|
|
"please wait...",
|
|
"running\ntap anywhere to stop",
|
|
"tap anywhere to reset"
|
|
][backend.race.state]
|
|
|
|
anchors.fill: parent
|
|
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
fontSizeMode: Text.Fit
|
|
|
|
font.pixelSize: height * 0.3
|
|
|
|
color: backend.race.state === ScStwRace.STARTING ? appTheme.theme.colors.warning:appTheme.theme.colors.text
|
|
|
|
text: implicitText === "NEXT_START_ACTION" ? ["", "at your \nmarks", "ready", "starting..."][backend.race.nextStartActionDetails[ScStwRace.NextStartAction]+1]:implicitText
|
|
|
|
Behavior on text {
|
|
FadeAnimation {
|
|
target: clickHintLabel
|
|
fadeDuration: 150
|
|
}
|
|
}
|
|
}
|
|
|
|
ProgressBar {
|
|
id: nextActiondelayProgressBar
|
|
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
bottom: parent.bottom
|
|
}
|
|
|
|
height: app.landscape() ? app.height * 0.1:app.width * 0.1
|
|
|
|
opacity: backend.race.nextStartActionDetails[ScStwRace.NextStartAction] < 3 && backend.race.state === ScStwRace.STARTING ? 1:0
|
|
|
|
value: backend.race.nextStartActionDetails[ScStwRace.NextStartActionDelayProgress]
|
|
|
|
|
|
background: Rectangle {
|
|
implicitWidth: 200
|
|
implicitHeight: parent.height
|
|
color: "lightgrey"
|
|
}
|
|
|
|
contentItem: Item {
|
|
implicitWidth: 200
|
|
implicitHeight: parent.height
|
|
|
|
Rectangle {
|
|
width: nextActiondelayProgressBar.visualPosition * parent.width
|
|
height: parent.height
|
|
color: "grey"
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: 200
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
|
|
parent: app
|
|
|
|
anchors.fill: parent
|
|
|
|
visible: controlsItm.visible
|
|
|
|
enabled: visible
|
|
|
|
onClicked: {
|
|
switch (backend.race.state) {
|
|
case ScStwRace.IDLE:
|
|
// IDLE
|
|
backend.race.start()
|
|
break;
|
|
case ScStwRace.STARTING:
|
|
// STARTING
|
|
backend.race.cancel()
|
|
break;
|
|
case ScStwRace.WAITING:
|
|
// WAITING
|
|
break;
|
|
case ScStwRace.RUNNING:
|
|
// RUNNING
|
|
backend.race.stop()
|
|
break;
|
|
case ScStwRace.STOPPED:
|
|
// STOPPED
|
|
backend.race.reset()
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
id: volumeSliderRow
|
|
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
bottom: parent.bottom
|
|
margins: 5
|
|
}
|
|
|
|
height: app.landscape() ? app.height * 0.1:app.width * 0.12
|
|
spacing: 0
|
|
|
|
opacity: backend.race.state === ScStwRace.IDLE ? 1:0
|
|
visible: opacity > 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: 200
|
|
}
|
|
}
|
|
|
|
Icon {
|
|
Layout.preferredHeight: parent.height * 0.7
|
|
Layout.preferredWidth: height * 0.7
|
|
Layout.alignment: Layout.Center
|
|
|
|
fontName: appTheme.theme.fonts.icons
|
|
icon: appTheme.theme.icons.volumeDown
|
|
color: appTheme.theme.colors.text
|
|
}
|
|
|
|
Slider {
|
|
id: volumeSlider
|
|
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
|
|
implicitWidth: 200
|
|
|
|
value: parseFloat(backend.scStwClient.readRemoteSetting(ScStw.SoundVolumeSetting))
|
|
|
|
onPressedChanged: {
|
|
if(!pressed){
|
|
volumeSlider.enabled = false
|
|
backend.scStwClient.writeRemoteSetting(ScStw.SoundVolumeSetting, value)
|
|
volumeSlider.enabled = true
|
|
}
|
|
}
|
|
|
|
leftPadding: width * (app.landscape() ? 0.02:0.05)
|
|
rightPadding: leftPadding
|
|
|
|
background: Rectangle {
|
|
x: volumeSlider.leftPadding
|
|
y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2
|
|
implicitWidth: 200
|
|
implicitHeight: volumeSliderRow.height * 0.1
|
|
width: volumeSlider.availableWidth
|
|
height: implicitHeight
|
|
radius: 2
|
|
color: "#bdbebf"
|
|
|
|
Rectangle {
|
|
width: volumeSlider.visualPosition * parent.width
|
|
height: parent.height
|
|
color: "grey"
|
|
radius: 2
|
|
}
|
|
}
|
|
|
|
handle: Item {
|
|
x: volumeSlider.leftPadding - width * 0.15 + volumeSlider.visualPosition * (volumeSlider.availableWidth - width * 0.55)
|
|
y: volumeSlider.topPadding + volumeSlider.availableHeight / 2 - height / 2
|
|
implicitWidth: volumeSliderRow.height
|
|
implicitHeight: implicitWidth
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
|
|
fillMode: Image.PreserveAspectFit
|
|
|
|
source: "qrc:/SpeedHold.png"
|
|
}
|
|
}
|
|
}
|
|
|
|
Icon {
|
|
Layout.preferredHeight: parent.height * 0.7
|
|
Layout.preferredWidth: height * 0.7
|
|
Layout.alignment: Layout.Center
|
|
|
|
fontName: appTheme.theme.fonts.icons
|
|
icon: appTheme.theme.icons.volumeUp
|
|
color: appTheme.theme.colors.text
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
states: [
|
|
State {
|
|
when: [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) >= 0
|
|
name: "big"
|
|
PropertyChanges {
|
|
target: clickHintLabel
|
|
|
|
anchors.margins: app.landscape() ? app.height * 0.2:app.width * 0.1
|
|
|
|
width: parent.width * 0.7
|
|
height: parent.height * 0.7
|
|
}
|
|
|
|
PropertyChanges {
|
|
target: controlsItm
|
|
|
|
height: app.height
|
|
}
|
|
},
|
|
State {
|
|
when: [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(backend.race.state) < 0
|
|
name: "small"
|
|
|
|
PropertyChanges {
|
|
target: clickHintLabel
|
|
|
|
anchors.margins: app.landscape() ? app.height * 0.01:app.width * 0.1
|
|
}
|
|
|
|
PropertyChanges {
|
|
target: controlsItm
|
|
|
|
height: app.landscape() ? app.height * 0.2:app.height * 0.4
|
|
}
|
|
}
|
|
]
|
|
|
|
transitions: [
|
|
Transition {
|
|
from: "*"
|
|
to: "*"
|
|
|
|
PauseAnimation {
|
|
duration: 150
|
|
}
|
|
}
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Rectangle {
|
|
id: bottomBanner
|
|
width: parent.width
|
|
height: !showControls && showBanners ? backend.race.state === ScStwRace.IDLE ? parent.height * 0.1:parent.height * 0.05 : 0
|
|
color: "black"
|
|
|
|
|
|
Text {
|
|
|
|
anchors.fill: parent
|
|
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
|
|
font.pixelSize: height * 0.5
|
|
|
|
visible: height > 0
|
|
|
|
color: "white"
|
|
|
|
text: qsTr("www.itsblue.de/scstw")
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: 200
|
|
easing.type: Easing.Linear
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Component {
|
|
id: loadingComp
|
|
|
|
Item {
|
|
id: loadingItm
|
|
|
|
anchors.fill: parent
|
|
|
|
BusyIndicator {
|
|
id: loadingInd
|
|
anchors.centerIn: parent
|
|
|
|
width: app.landscape() ? parent.height * 0.2 : parent.width *0.2
|
|
height: width
|
|
|
|
image: appTheme.theme.images.SpeedHold
|
|
}
|
|
|
|
MouseArea {
|
|
id: textFieldEnableMa
|
|
hoverEnabled: true
|
|
anchors.fill: parent
|
|
anchors.leftMargin: parent.width * 0.9
|
|
}
|
|
|
|
TextField {
|
|
id: ipAddrInputTf
|
|
|
|
anchors {
|
|
bottom: parent.bottom
|
|
horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
visible: !textFieldEnableMa.containsMouse
|
|
|
|
opacity: backend.scStwClient.state === ScStwClient.CONNECTED ? 0:1
|
|
|
|
text: appSettings.baseStationIp
|
|
|
|
onEditingFinished: {
|
|
appSettings.baseStationIp = text
|
|
backend.scStwClient.ipAddress = text
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|