monitor/resources/qml/main.qml

536 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
}
}
}
}
}
}