/**************************************************************************** ** 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.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 } ScStwClient { id: scStwClient ipAddress: appSettings.baseStationIp } Timer { running: scStwClient.state !== ScStwClient.Connected interval: 10 onTriggered: scStwClient.connectToHost() } ScStwRemoteRace { id: scStwRemoteRace autoRefreshTimerText: true scStwClient: scStwClient onStateChanged: { if(scStwRemoteRace.state !== ScStwRace.IDLE) { settingsDialog.close() profilesDialog.close() } } } 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: scStwClient.state === ScStwClient.CONNECTED ? displayComp:loadingComp } Component { id: displayComp Column { Rectangle { id: topBanner width: parent.width height: !showControls && showBanners ? scStwRemoteRace.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(scStwRemoteRace.timers) colors: appTheme.theme.colors fontName: appTheme.theme.fonts.timers opacity: !showControls || [ScStwRace.IDLE,ScStwRace.STARTING].indexOf(scStwRemoteRace.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" ][scStwRemoteRace.state] anchors.fill: parent verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter fontSizeMode: Text.Fit font.pixelSize: height * 0.3 color: scStwRemoteRace.state === ScStwRace.STARTING ? appTheme.theme.colors.warning:appTheme.theme.colors.text text: implicitText === "NEXT_START_ACTION" ? ["", "at your \nmarks", "ready", "starting..."][scStwRemoteRace.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: scStwRemoteRace.nextStartActionDetails[ScStwRace.NextStartAction] < 3 && scStwRemoteRace.state === ScStwRace.STARTING ? 1:0 value: scStwRemoteRace.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 (scStwRemoteRace.state) { case ScStwRace.IDLE: // IDLE scStwRemoteRace.start() break; case ScStwRace.STARTING: // STARTING scStwRemoteRace.cancel() break; case ScStwRace.WAITING: // WAITING break; case ScStwRace.RUNNING: // RUNNING scStwRemoteRace.stop() break; case ScStwRace.STOPPED: // STOPPED scStwRemoteRace.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: scStwRemoteRace.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(scStwRemoteRace.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(scStwRemoteRace.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 ? scStwRemoteRace.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: scStwClient.state === ScStwClient.CONNECTED ? 0:1 text: appSettings.baseStationIp onEditingFinished: { appSettings.baseStationIp = text backend.scStwClient.ipAddress = text } } } } } }