reaction-trainer/qml/main.qml

416 lines
12 KiB
QML

/*
Speed climbing reaction timer
Copyright (C) 2019 Itsblue Development | Dorian Zedler
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero 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.0
import QtMultimedia 5.15
import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.15
import com.itsblue.SpeedClimbingStartTrainer 2.0
import "./Components"
Window {
visible: true
width: 640
height: 480
title: qsTr("Speed climbing reaction trainer")
Page {
id: app
state: "IDLE"
anchors.fill: parent
property double startTime: -1
property double stopTime: -1
property double reactionTime: 0
property int highscore: settings.read("highscore")
AppSettings {
id: settings
}
AppTheme {
id: theme
}
Item {
id: soundsItm
SoundEffect {
id: readySe
source: "qrc:/sounds/ready_1.wav"
onPlayingChanged: {
if(playing) return
if(app.state === "RUNNING"){
startSe.play()
}
}
}
SoundEffect {
id: startSe
source: "qrc:/sounds/IFSC_STARTSIGNAL_SINE.wav"
onPlayingChanged: {
if(!playing) return
app.startTime = new Date().getTime()
}
}
SoundEffect {
id: failedSe
source: "qrc:/sounds/IFSC frequenzy conform false start sound.wav"
}
}
Rectangle {
anchors.fill: parent
color: theme.style.backgroundColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Item {
id: topContainerItm
anchors {
top: parent.top
left: parent.left
right: app.landscape() ? startBt.left:parent.right
bottom: app.landscape() ? parent.bottom:startBt.top
bottomMargin: app.landscape() ? undefined:parent.height * 0.1
rightMargin: app.landscape() ? parent.width * 0.05:0
}
Text {
id: topLa
anchors.centerIn: parent
width: parent.width * 0.8
text: ""
color: theme.style.textColor
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.landscape() ? parent.width * 0.1 : parent.height * 0.4
minimumPixelSize: 0
Behavior on text {
FadeAnimation{
target: topLa
}
}
}
Text {
id: highscoreLa
anchors {
top: topLa.bottom
horizontalCenter: parent.horizontalCenter
}
width: topLa.width * 0.5
height: topLa.height
visible: text !== "highscore: -1"
text: ""
color: theme.style.textColor
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.landscape() ? parent.width * 0.1 : parent.height * 0.4
minimumPixelSize: 0
Behavior on text {
FadeAnimation{
target: highscoreLa
}
}
}
Row {
anchors {
top: topLa.bottom
horizontalCenter: parent.horizontalCenter
horizontalCenterOffset: width * 0.1
}
opacity: app.state === "IDLE" ? 1:0
Switch {
anchors.verticalCenter: parent.verticalCenter
checked: settings.read("theme") === "1"
scale: 0.7
onCheckedChanged: {
if(checked){
settings.write("theme", 1)
}
else {
settings.write("theme", 0)
}
theme.refreshTheme()
}
indicator: Rectangle {
property bool checked: parent.checked
property bool down: parent.down
height: parent.height
width: height * 2
radius: height * 0.5
color: parent.checked ? "#17a81a" : "transparent"
border.color: parent.checked ? "#17a81a" : "#cccccc"
Behavior on color{
ColorAnimation{
duration: 200
}
}
Rectangle {
x: parent.checked ? parent.width - width : 0
width: parent.height
height: parent.height
radius: height * 0.5
color: parent.down ? theme.style.buttonPressedColor:theme.style.backgroundColor //"#cccccc" : "#ffffff"
border.color: parent.checked ? (parent.down ? "#17a81a" : "#21be2b") : "#999999"
Behavior on x{
NumberAnimation {
property: "x"
duration: 200
easing.type: Easing.InOutQuad
}
}
}
}
}
ToolButton {
anchors.verticalCenter: parent.verticalCenter
bottomPadding: 0
topPadding: 0
text: "\uf05a"
scale: 1.2
font.family: fontAwesome.name
font.pixelSize: height * 0.4
Material.theme: theme.style.name === "dark" ? Material.Dark:Material.Light
onClicked: infoDisclaimerDialog.open()
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}
FancyButton {
id: startBt
text: "start"
property int size: app.landscape() ? parent.width * 0.5:parent.height * 0.5
backgroundColor: theme.style.buttonColor
textColor: theme.style.textColor
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: app.landscape() ? parent.height * 0.5 - startBt.height * 0.5:parent.height * 0.1
rightMargin: app.landscape() ? parent.width * 0.05:parent.width * 0.5 - startBt.width * 0.5
}
font.pixelSize: height * 0.15
height: app.landscape() ? (size > parent.height * 0.9 ? parent.height * 0.9:size) : (size > parent.width * 0.9 ? parent.width * 0.9:size)
width: height
onPressed: {
app.start()
}
onReleased: {
app.stop()
}
}
function landscape() {
return app.height < app.width
}
function start() {
if(app.state !== "IDLE"){
app.reset()
return
}
app.state = "RUNNING"
readySe.play()
}
function stop() {
app.stopTime = new Date().getTime()
if(app.state !== "RUNNING"){
return
}
if(readySe.playing){
app.state = "IDLE"
return
}
app.state = "STOPPED"
startSe.stop()
app.reactionTime = app.stopTime - app.startTime - 3000
if(app.reactionTime < 100) {
failedSe.play()
}
app.refreshHighscore()
}
function refreshHighscore(){
const currentHighscore = parseInt(settings.read("highscore"))
// migrate old Highscores
if(currentHighscore < 100) {
settings.write("highscore", currentHighscore+100)
app.highscore = currentHighscore + 100
}
if(app.reactionTime > 100 && ( app.reactionTime < currentHighscore || settings.read("highscore") === "-1" )){
settings.write("highscore", app.reactionTime)
app.highscore = app.reactionTime
}
}
function reset() {
app.state = "IDLE"
app.startTime = 0
}
states: [
State {
name: "IDLE"
PropertyChanges {
target: topLa
text: "Hold down start to start"
}
},
State {
name: "RUNNING"
PropertyChanges {
target: topLa
text: "release to stop"
}
},
State {
name: "STOPPED"
PropertyChanges {
target: topLa
text: "Your reaction time: " + app.reactionTime
}
PropertyChanges {
target: startBt
text: "reset"
}
PropertyChanges {
target: highscoreLa
text: "highscore: " + app.highscore
}
}
]
FontLoader {
id: fontAwesome
source: "qrc:/fonts/fa5solid.woff"
Component.onCompleted: {
console.log("Font name: " + fontAwesome.name)
}
}
DisclaimerDialog {
id: infoDisclaimerDialog
Material.theme: theme.style.name === "dark" ? Material.Dark:Material.Light
title: "Speedclimbing reaction trainer v" + APP_VERSION + "<br>By <a href=\"https://itsblue.de\">Itsblue Development</a>, <a href=\"https://itsblue.de/apps/scrt\">" + qsTr("privacy policy") + "</a>"
content: qsTr("This app was built using the <a href='https://qt.io'>Qt Framework</a> licensed under the <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 license</a>.<br><br>This app is open source and licensed under the <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 license</a>, the source code can be found <a href='https://itsblue.dev/ScStw/reaction-trainer'>here</a>.")
}
}
}