This repository has been archived on 2024-06-03. You can view files and clone it, but cannot push or open issues or pull requests.
app/qml/main.qml

606 lines
20 KiB
QML

/*
Speed Climbing Stopwatch - Simple Stopwatch for Climbers
Copyright (C) 2018 Itsblue Development - Dorian Zeder
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, version 3 of the License.
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 <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
import "."
import "./components"
//import QtQuick.Layouts 1.11
import com.itsblue.speedclimbingstopwatch 2.0
Window {
visible: true
width: 540
height: 960
title: qsTr("Speedclimbing stw")
property date currentTime: new Date()
property int millis: 0
Page {
id:root
anchors.fill: parent
property double startTime: 0
property double stoppedTime: 0
property double currTime
//set default state to IDLE
state: "IDLE"
Rectangle {
id: backgroundRect
anchors.fill: parent
color: appTheme.style.backgroundColor
}
SpeedBackend {
id: speedBackend
onStateChanged: {
var stateString
switch (state){
case 0:
stateString = "IDLE"
break;
case 1:
stateString = "STARTING"
settingsDialog.close()
break;
case 2:
stateString = "RUNNING"
settingsDialog.close()
break;
case 3:
stateString = "STOPPED"
settingsDialog.close()
}
root.state = stateString
}
}
AppTheme {
id: appTheme
}
/*------------------------
Timer text an upper line
------------------------*/
RectangularGlow {
id: effect_2
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: time_container
scale: 1
}
Item {
id: time_container
anchors {
top: parent.top
left: parent.left
right: root.landscape() ? startButt.left:parent.right
bottom: root.landscape() ? parent.bottom:startButt.top
bottomMargin: root.landscape() ? undefined:parent.height * 0.1
rightMargin: root.landscape() ? parent.width * 0.05:0
}
Rectangle {
anchors.fill: parent
color: appTheme.style.menuColor
}
Label {
id: timer_1
anchors.centerIn: parent
elide: "ElideRight"
color: appTheme.style.textColor
font.pixelSize: 100
text: speedBackend.timers[0]["text"]
Behavior on text {
enabled: root.state !== "RUNNING"
FadeAnimation {
target: timer_1
}
}
}
Label {
id: react_time
property int rtime: speedBackend.timers[0]["reacttime"]
text: qsTr("reaction time (ms): ") + Math.round(rtime)
opacity: (root.state === "RUNNING" || root.state === "STOPPED") && rtime !== 0 ? 1:0
color: appTheme.style.textColor
anchors {
horizontalCenter: parent.horizontalCenter
top: timer_1.bottom
topMargin: parent.height * 0.1
}
}
}
Item {
id: connectionIconContainer
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: parent.bottom
bottomMargin: root.landscape() ? 0:parent.height * 0.8
rightMargin: root.landscape() ? parent.width * 0.8:0
}
ConnectionIcon {
id: baseConnConnIcon
status: speedBackend.baseStationState
source: "qrc:/graphics/icons/BaseStation_black.png"
anchors {
top: parent.top
topMargin: 10
left: parent.left
leftMargin: 10
}
scale: 1.3
height: !root.landscape()? parent.height*0.3:parent.width*0.3
}
ConnectionIcon {
id: buzzerConnIcon
status: speedBackend.baseStationConnections[1]["state"]
source: "qrc:/graphics/icons/buzzer_black.png"
anchors {
top: parent.top
topMargin: 10
left: baseConnConnIcon.right
leftMargin: 1
}
height: !root.landscape()? parent.height*0.17:parent.width*0.17
}
ConnectionIcon {
id: startpadConnIcon
status: speedBackend.baseStationConnections[0]["state"]
source: "qrc:/graphics/icons/startpad_black.png"
anchors {
top: parent.top
topMargin: 10
left: buzzerConnIcon.right
leftMargin: 0
}
height: !root.landscape() ? parent.height*0.17:parent.width*0.17
}
}
Rectangle {
id: upper_line
width: root.landscape() ? 1:parent.width
height: root.landscape() ? parent.height:1
color: appTheme.style.lineColor
anchors.left: root.landscape() ? time_container.right:parent.left
anchors.top: root.landscape() ? parent.top:time_container.bottom
anchors.bottom: root.landscape() ? parent.bottom:undefined
visible: false
}
/*----------------------
Start / Stop / Reset button
----------------------*/
FancyButton {
id : startButt
text: qsTr("start")
property int size: root.landscape() ? parent.width * 0.5:parent.height * 0.5
anchors {
bottom: parent.bottom
bottomMargin: root.height * 0.5 - height * 0.5
right: parent.right
rightMargin: root.width * 0.5 - width * 0.5
}
contentItem: Text {
//make text disappear
}
height: root.landscape() ? size > parent.height * 0.9 ? parent.height * 0.9:size : size
width: root.landscape() ? size : size > parent.width * 0.9 ? parent.width * 0.9:size
Label {
id: startButt_text
text: startButt.text
anchors.centerIn: parent
font.pixelSize: parent.height * 0.16
font.family: "Helvetica"
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
}
backgroundColor: appTheme.style.buttonColor
Behavior on text {
//animate a text change
enabled: true
FadeAnimation {
target: startButt_text
}
}
onClicked: {
switch(root.state) {
case "IDLE":
root.start()
break
case "RUNNING":
root.stop(0)
break
case "STOPPED":
root.reset()
break
}
}
}
ProgressCircle {
id: prog
anchors.fill: startButt
opacity: root.state === "STARTING" || root.state === "IDLE" ? 1:0
scale: startButt.scale
lineWidth: 5
arcBegin: 0
arcEnd: 360 * speedBackend.nextStartActionDelayProgress
colorCircle: "grey"
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
animationDuration: speedBackend.mode === 1 ? 150:0
}
/*----------------------
Cancel button
----------------------*/
FancyButton {
id: cancelButt
text: qsTr("cancel")
anchors {
right: startButt.right
bottom: startButt.bottom
}
contentItem: Text {
//make text disappear
}
height: startButt.height * 0.3
scale: 0
width: height
enabled: root.state === "STARTING"
onClicked: {
root.stop(1)
}
Behavior on scale {
PropertyAnimation {
duration: 200
}
}
Label {
id: cancelButt_text
text: cancelButt.text
anchors.centerIn: parent
font.pixelSize: parent.height * 0.16
font.family: "Helvetica"
color: appTheme.style.textColor
}
backgroundColor: appTheme.style.buttonColor
}
/*------
Popups
------*/
SettingsDialog{
id: settingsDialog
connections: root.connections
onConnectRequested: {
switch(type){
case "buzzer":
buzzerConn.connect()
break
case "startpad":
startpadConn.connect()
break
case "baseStation":
baseConn.connectToHost()
break
}
}
}
ErrorDialog {
id: errorDialog
}
// ProfilesDialog {
// id: profilesDialog
// }
/*-------------------
lower line and menu
-------------------*/
Rectangle {
id: lowerLine
width: root.landscape() ? 1:parent.width
height: root.landscape() ? parent.height:1
color: appTheme.style.lineColor
anchors.right: root.landscape() ? menu_container.left:parent.right
anchors.bottom: root.landscape() ? parent.bottom:menu_container.top
anchors.top: root.landscape() ? parent.top:undefined
visible: false
}
RectangularGlow {
id: effect
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: menu_container
scale: 1
}
Item {
id: menu_container
anchors {
bottom: parent.bottom
right: parent.right
left: root.landscape() ? startButt.right:parent.left
top: root.landscape() ? parent.top:startButt.bottom
topMargin: root.landscape() ? undefined:parent.height * 0.1
leftMargin: root.landscape() ? parent.width * 0.05:0
}
Rectangle {
id: lowerMenuBackground
anchors.fill: parent
color: appTheme.style.menuColor
}
FancyButton {
id: settingsButt
anchors {
//center
verticalCenter: root.landscape() ? undefined:parent.verticalCenter
horizontalCenter: root.landscape() ? parent.horizontalCenter:undefined
//set anchors
left: root.landscape() ? undefined:parent.left
top: root.landscape() ? parent.top:undefined
//align in landscape mode
//for two buttons: topMargin: root.landscape() ? (parent.height - (height * 2)) / 3:undefined
topMargin: root.landscape() ? (parent.height * 0.5 - (height * 0.5)):undefined
//align in portrait mode
//for two buttons: leftMargin: root.landscape() ? undefined:(parent.width - width * 2) / 3
leftMargin: root.landscape() ? undefined:(parent.width * 0.5 - width * 0.5)
}
height: root.landscape() ? parent.width * 0.7:parent.height * 0.7
width: height
onClicked: {
settingsDialog.open()
}
image: appTheme.style.settIcon
backgroundColor: parent.pressed ? appTheme.style.buttonPressedColor:appTheme.style.buttonColor
}
/*
RoundButton {
id: profilesButt
anchors {
verticalCenter: root.landscape() ? undefined:parent.verticalCenter
horizontalCenter: root.landscape() ? parent.horizontalCenter:undefined
left: root.landscape() ? undefined:settingsButt.right
top: root.landscape() ? settingsButt.bottom:undefined
topMargin: root.landscape() ? (parent.height - (height * 2)) / 3:undefined
leftMargin: root.landscape() ? undefined:(parent.width - width * 2) / 3
}
height: root.landscape() ? parent.width * 0.7:parent.height * 0.7
width: height
onPressedChanged: {
if(pressed){
background.color = "lightgrey"
}
else {
background.color = "white"
}
}
onClicked: {
profilesDialog.open()
}
background: Rectangle {
color: "white"
border.color: "grey"
border.width: 1
radius: width / 2
Image {
id: profilesButt_Image
source: "qrc:/graphics/icons/user.png"
anchors.centerIn: parent
height: parent.height * 0.5
width: parent.width * 0.5
mipmap: true
}
}
}
*/
}
/*----------------------
Timer states
----------------------*/
states: [
State {
name: "IDLE"
//state for the start page
PropertyChanges { target: timer_1; font.pixelSize: root.landscape() ? parent.width * 0.1:parent.height * 0.3; scale: 1 }
PropertyChanges {
target: time_container;
anchors.bottomMargin: root.landscape() ? undefined:parent.height * 0.1;
anchors.rightMargin: root.landscape() ? parent.height * 0.05:0
}
PropertyChanges {
target: startButt;
enabled: true; text: qsTr("start");
size: root.landscape() ? parent.width * 0.5:parent.height * 0.5
anchors.bottomMargin: parent.height * 0.5 - startButt.height * 0.5
anchors.rightMargin: parent.width * 0.5 - startButt.width * 0.5
}
},
State {
name: "STARTING"
//state for the start sequence
PropertyChanges { target: startButt; enabled: false; text: qsTr("starting...");
anchors.rightMargin: root.landscape() ? parent.width * 0.05:parent.width * 0.5 - startButt.width * 0.5 //put the button more to the right to hide the menu (only in landscape mode)
anchors.bottomMargin: root.landscape() ? parent.height * 0.5 - startButt.height * 0.5:parent.height * 0.1 //put the button lower to hide the menu (only in portrait mode)
}
PropertyChanges { target: timer_1; font.pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: cancelButt; scale: 1}
PropertyChanges { target: menu_container; }
},
State {
name: "RUNNING"
//state when the timer is running
PropertyChanges { target: timer_1; font.pixelSize: root.landscape() ? parent.width * 0.2:parent.height * 0.3; scale: 1 }
PropertyChanges { target: startButt; enabled: true;
text: "stop"
anchors.rightMargin: root.landscape() ? parent.width * 0.05:parent.width * 0.5 - startButt.width * 0.5 //put the button more to the right to hide the menu (only in landscape mode)
anchors.bottomMargin: root.landscape() ? parent.height * 0.5 - startButt.height * 0.5:parent.height * 0.1 //put the button lower to hide the menu (only in portrait mode)
}
},
State {
name: "STOPPED"
//state when the meassuring is over
PropertyChanges {
target: timer_1;
font.pixelSize: root.landscape() ? parent.width * 0.15:parent.height * 0.1;
scale: 1
}
PropertyChanges {
target: startButt;
enabled: true; text: qsTr("reset");
size: root.landscape() ? parent.height * 0.35:parent.height * 0.2;
anchors.bottomMargin: root.landscape() ? parent.height * 0.5 - startButt.height * 0.5:parent.height * 0.2 - startButt.height * 0.5
anchors.rightMargin: root.landscape() ? parent.height * 0.2 - startButt.height * 0.5:parent.width * 0.5 - startButt.width * 0.5
}
PropertyChanges {
target: time_container;
anchors.rightMargin: root.landscape() ? 0-startButt.width/2:undefined
anchors.bottomMargin: root.landscape() ? undefined:0-startButt.height/2
}
}
]
/*----------------------
Timer animations
----------------------*/
transitions: [
Transition {
NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize,pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
},
Transition {
to: "STOPPED"
NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize,pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
},
Transition {
to: "IDLE"
NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize,pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
},
Transition {
from: "STARTING"
to: "RUNNING"
//disable transitions for the RUNNING state
}
]
/*----------------------
Timer functions
----------------------*/
function landscape(){
return(root.height < root.width)
}
/*----Functions to control the stopwatch----*/
function start(){
var ret = speedBackend.startRace()
if(ret !== 200){
console.log("+ --- error starting race: "+ret)
}
}
function stop(type){
var ret = speedBackend.stopRace(type)
if(ret !== 200){
console.log("+ --- error stopping race: "+ret)
}
}
function reset(){
var ret = speedBackend.resetRace()
if(ret !== 200){
console.log("+ --- error resetting race: "+ret)
}
}
}
}