/**************************************************************************** ** 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 . ****************************************************************************/ 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 } } } } } }