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

798 lines
26 KiB
QML
Raw Normal View History

2018-08-12 20:51:57 +02:00
/*
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/>.
*/
2018-07-17 19:17:25 +02:00
import QtQuick 2.9
import QtMultimedia 5.8
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
2019-03-08 15:36:32 +01:00
import QtGraphicalEffects 1.0
import "."
import "./components"
import "./ProfilesDialog"
2018-07-22 21:08:11 +02:00
//import QtQuick.Layouts 1.11
2018-07-22 16:47:55 +02:00
import com.itsblue.speedclimbingstopwatch 2.0
2018-07-17 19:17:25 +02:00
Window {
visible: true
width: 540
height: 960
title: "Speedclimbing stw"
2018-07-17 19:17:25 +02:00
property date currentTime: new Date()
property int millis: 0
Page {
id:app
2018-07-17 19:17:25 +02:00
anchors.fill: parent
//set default state to IDLE
2018-07-22 16:47:55 +02:00
state: "IDLE"
Rectangle {
id: backgroundRect
anchors.fill: parent
2019-03-08 18:03:27 +01:00
color: appTheme.style.backgroundColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
SpeedBackend {
id: speedBackend
onStateChanged: {
var stateString
console.log("race state changed to: " + state)
switch (state){
case 0:
stateString = "IDLE"
break;
case 1:
stateString = "STARTING"
2019-03-08 15:36:32 +01:00
settingsDialog.close()
2019-08-20 22:55:37 +02:00
profilesDialog.close()
break;
case 2:
stateString = "WAITING"
2019-03-08 15:36:32 +01:00
settingsDialog.close()
2019-08-20 22:55:37 +02:00
profilesDialog.close()
break;
case 3:
stateString = "RUNNING"
settingsDialog.close()
2019-08-20 22:55:37 +02:00
profilesDialog.close()
break;
case 4:
stateString = "STOPPED"
2019-03-08 15:36:32 +01:00
settingsDialog.close()
2019-08-20 22:55:37 +02:00
profilesDialog.close()
}
app.state = stateString
}
}
2019-03-08 18:03:27 +01:00
AppTheme {
id: appTheme
}
2018-07-22 16:47:55 +02:00
/*------------------------
Timer text an upper line
------------------------*/
2019-03-08 15:36:32 +01:00
RectangularGlow {
id: effect_2
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: topContainerItm
2019-03-08 15:36:32 +01:00
scale: 1
}
2018-07-17 19:17:25 +02:00
Item {
id: topContainerItm
2018-07-17 19:17:25 +02:00
anchors {
top: parent.top
left: parent.left
right: app.landscape() ? startButt.left:parent.right
bottom: app.landscape() ? parent.bottom:startButt.top
bottomMargin: app.landscape() ? undefined:parent.height * 0.1
rightMargin: app.landscape() ? parent.width * 0.05:0
2018-07-17 19:17:25 +02:00
}
Rectangle {
anchors.fill: parent
2019-03-08 18:03:27 +01:00
color: appTheme.style.menuColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Text {
id: topLa
2018-07-17 19:17:25 +02:00
anchors.centerIn: parent
opacity: ( speedBackend.state < 3 ) ? 1:0
2019-04-27 22:50:22 +02:00
width: parent.width * 0.7
text: "Click Start to start"
2019-03-08 18:03:27 +01:00
color: appTheme.style.textColor
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.landscape() ? parent.width * 0.15 : parent.height * 0.4
2019-06-15 14:38:33 +02:00
minimumPixelSize: 1
Behavior on text {
FadeAnimation{
target: topLa
fadeDuration: 100
}
}
2018-07-17 19:17:25 +02:00
}
2018-08-29 18:33:39 +02:00
Column {
id: timerCol
anchors.fill: parent
anchors.bottomMargin: app.landscape() ? 0:parent.height * 0.1
opacity: ( speedBackend.state < 3 ) ? 0:1
spacing: height * 0.05
Repeater {
id: timerRep
model: speedBackend.timers.length
delegate: Item {
id: timerDel
width: parent.width
height: timerRep.model > 1 ? ( timerCol.height * 0.9 ) / timerRep.model:timerCol.height
Label {
id: timerTextLa
anchors.centerIn: parent
width: ( parent.width * 0.8 )
height: parent.height
elide: "ElideRight"
color: speedBackend.timers[index]["state"] === "WON" ? appTheme.style.successColor :
speedBackend.timers[index]["state"] === "LOST" ? appTheme.style.errorColor:
appTheme.style.textColor
enabled: speedBackend.timers[index]["state"] !== "DISABLED"
text: speedBackend.timers[index]["text"]
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.landscape() ? parent.width * 0.15 : parent.height * 0.4
2019-06-15 14:38:33 +02:00
minimumPixelSize: 1
Behavior on text {
enabled: app.state !== "RUNNING" && app.state !== "STOPPED"
FadeAnimation {
target: timerTextLa
}
}
}
Label {
id: react_time
property int rtime: speedBackend.timers[index]["reacttime"]
anchors {
centerIn: parent
verticalCenterOffset: timerTextLa.font.pixelSize * 0.5 + react_time.font.pixelSize * 0.5
}
width: ( parent.width * 0.6 )
height: parent.height
fontSizeMode: Text.Fit
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: "reaction time (ms): " + Math.round(rtime)
opacity: (app.state === "RUNNING" || app.state === "STOPPED") && rtime !== 0 ? 1:0
color: appTheme.style.textColor
font.pixelSize: timerTextLa.font.pixelSize * 0.5
}
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
2018-08-29 18:33:39 +02:00
}
}
2018-08-02 12:50:55 +02:00
}
Item {
id: connectionIconContainer
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: parent.bottom
bottomMargin: app.landscape() ? 0:parent.height * 0.8
rightMargin: app.landscape() ? parent.width * 0.8:0
}
ConnectionIcon {
id: baseConnConnIcon
status: speedBackend.baseStationState
source: appTheme.style.baseStationIcon
anchors {
top: parent.top
topMargin: 10
left: parent.left
leftMargin: 10
}
scale: 1.3
height: !app.landscape()? parent.height*0.3:parent.width*0.3
}
2019-03-27 22:26:45 +01:00
Row {
id: connectedExtensionsRow
anchors {
top: parent.top
topMargin: 10
left: baseConnConnIcon.right
leftMargin: 1
}
2019-03-27 22:26:45 +01:00
height: parent.height
width: parent.width
Repeater {
id: connectedExtensionsRep
anchors.fill: parent
model: speedBackend.baseStationConnections.length
delegate: ConnectionIcon {
id: buzzerConnIcon
status: speedBackend.baseStationConnections[index]["state"]
source: {
var source
switch(speedBackend.baseStationConnections[index]["type"]){
case "STARTPAD":
source = appTheme.style.startpadIcon
break
case "TOPPAD":
source = appTheme.style.buzzerIcon
break
}
}
scale: 0
height: !app.landscape()? parent.height*0.17:parent.width*0.17
width: status === "disconnected" ? 0:height
Component.onCompleted: {
scale = 1
}
Behavior on scale {
NumberAnimation {
duration: 200
}
}
Behavior on width {
NumberAnimation {
duration: 200
}
}
2019-03-27 22:26:45 +01:00
}
}
}
}
2018-08-02 12:50:55 +02:00
Rectangle {
id: upper_line
width: app.landscape() ? 1:parent.width
height: app.landscape() ? parent.height:1
2019-03-08 18:03:27 +01:00
color: appTheme.style.lineColor
anchors.left: app.landscape() ? topContainerItm.right:parent.left
anchors.top: app.landscape() ? parent.top:topContainerItm.bottom
anchors.bottom: app.landscape() ? parent.bottom:undefined
2019-03-08 15:36:32 +01:00
visible: false
2018-07-17 19:17:25 +02:00
}
// ----------------------------------
// -- Start / Stop / Reset button ---
// ----------------------------------
2019-03-08 15:36:32 +01:00
FancyButton {
id : startButt
2018-07-17 19:17:25 +02:00
text: "start"
property int size: app.landscape() ? parent.width * 0.5:parent.height * 0.5
2018-07-17 19:17:25 +02:00
anchors {
bottom: parent.bottom
bottomMargin: app.height * 0.5 - height * 0.5
2018-07-22 16:47:55 +02:00
right: parent.right
rightMargin: app.width * 0.5 - width * 0.5
}
contentItem: Text {
//make text disappear
2018-07-17 19:17:25 +02:00
}
height: app.landscape() ? (size > parent.height * 0.9 ? parent.height * 0.9:size) : (size > parent.width * 0.9 ? parent.width * 0.9:size)
2019-03-27 22:26:45 +01:00
width: height
2018-07-17 19:17:25 +02:00
2019-03-08 15:36:32 +01:00
Label {
id: startButt_text
text: startButt.text
anchors.centerIn: parent
font.pixelSize: parent.height * 0.16
font.family: "Helvetica"
2019-03-08 18:03:27 +01:00
color: enabled ? appTheme.style.textColor:appTheme.style.disabledTextColor
2018-07-17 19:17:25 +02:00
}
2018-07-22 16:47:55 +02:00
2019-03-08 18:03:27 +01:00
backgroundColor: appTheme.style.buttonColor
2019-03-08 15:36:32 +01:00
Behavior on text {
//animate a text change
enabled: true
FadeAnimation {
target: startButt_text
}
}
onClicked: {
switch(app.state) {
case "IDLE":
app.start()
break
case "RUNNING":
app.stop(0)
break
case "STOPPED":
app.reset()
break
2018-07-22 16:47:55 +02:00
}
}
}
ProgressCircle {
id: prog
anchors.fill: startButt
opacity: app.state === "STARTING" || app.state === "IDLE" ? 1:0
2019-03-08 15:36:32 +01:00
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
2018-07-22 16:47:55 +02:00
}
/*----------------------
Cancel button
----------------------*/
2019-03-08 15:36:32 +01:00
FancyButton {
2018-07-22 16:47:55 +02:00
id: cancelButt
text: "cancel"
2018-07-22 16:47:55 +02:00
anchors {
right: startButt.right
bottom: startButt.bottom
}
contentItem: Text {
//make text disappear
}
2018-07-22 16:47:55 +02:00
height: startButt.height * 0.3
scale: 0
width: height
enabled: app.state === "STARTING"
2018-07-22 16:47:55 +02:00
onClicked: {
app.stop(1)
}
2018-07-22 16:47:55 +02:00
Behavior on scale {
PropertyAnimation {
duration: 200
}
}
2019-03-08 15:36:32 +01:00
Label {
id: cancelButt_text
text: cancelButt.text
anchors.centerIn: parent
font.pixelSize: parent.height * 0.16
font.family: "Helvetica"
2019-03-08 18:03:27 +01:00
color: appTheme.style.textColor
}
2019-03-08 15:36:32 +01:00
2019-03-08 18:03:27 +01:00
backgroundColor: appTheme.style.buttonColor
}
/*------
Popups
------*/
SettingsDialog{
2018-08-02 12:50:55 +02:00
id: settingsDialog
x: startButt.x
y: startButt.y
width: startButt.width
height: startButt.height
2018-07-22 16:47:55 +02:00
}
ProfilesDialog {
id: profilesDialog
property int margin: app.landscape() ? app.height * 0.05:app.width * 0.05
x: app.landscape() ? topContainerItm.width + margin:topContainerItm.x + margin
y: !app.landscape() ? topContainerItm.height + margin:topContainerItm.x + margin
width: app.landscape() ? app.width - topContainerItm.width - menu_container.width - margin * 2 : app.width - margin * 2
height: !app.landscape() ? app.height - topContainerItm.height - menu_container.height - margin * 2 : app.height - margin * 2
}
2018-07-22 16:47:55 +02:00
/*-------------------
lower line and menu
-------------------*/
2019-03-08 15:36:32 +01:00
2018-07-22 16:47:55 +02:00
Rectangle {
2019-03-08 15:36:32 +01:00
id: lowerLine
width: app.landscape() ? 1:parent.width
height: app.landscape() ? parent.height:1
2019-03-08 18:03:27 +01:00
color: appTheme.style.lineColor
anchors.right: app.landscape() ? menu_container.left:parent.right
anchors.bottom: app.landscape() ? parent.bottom:menu_container.top
anchors.top: app.landscape() ? parent.top:undefined
2019-03-08 15:36:32 +01:00
visible: false
}
RectangularGlow {
id: effect
glowRadius: 7
spread: 0.02
color: "black"
opacity: 0.18
anchors.fill: menu_container
scale: 1
2018-07-22 16:47:55 +02:00
}
Item {
id: menu_container
2018-07-22 16:47:55 +02:00
anchors {
bottom: parent.bottom
right: parent.right
left: app.landscape() ? startButt.right:parent.left
top: app.landscape() ? parent.top:startButt.bottom
topMargin: app.landscape() ? undefined:parent.height * 0.1
leftMargin: app.landscape() ? parent.width * 0.05:0
2018-07-22 16:47:55 +02:00
}
Rectangle {
2019-03-08 15:36:32 +01:00
id: lowerMenuBackground
anchors.fill: parent
2019-03-08 18:03:27 +01:00
color: appTheme.style.menuColor
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
Grid {
id: loweMenuGrd
2018-07-22 16:47:55 +02:00
property int spacingMultiplier: 200 * (getActiveChildren() - 1)
property int activeChildren: getActiveChildren()
function getActiveChildren() {
var childrenCount = 0
for (var i = 0; i < children.length; i++)
{
if(children[i].enabled){
childrenCount ++
}
}
2018-07-22 16:47:55 +02:00
return childrenCount
2018-07-22 16:47:55 +02:00
}
anchors.centerIn: parent
2019-03-08 15:36:32 +01:00
height: childrenRect.height
width: childrenRect.width
2019-03-08 15:36:32 +01:00
rows: app.landscape() ? activeChildren:1
columns: app.landscape() ? 1:activeChildren
spacing: 0// app.landscape() ? parent.height * spacingMultiplier * 0.001:parent.width * spacingMultiplier * 0.001
2018-07-22 16:47:55 +02:00
Behavior on spacingMultiplier {
NumberAnimation {
duration: 200
}
2018-07-22 16:47:55 +02:00
}
FancyButton {
id: settingsButt
2018-07-22 16:47:55 +02:00
height: app.landscape() ? menu_container.width * 0.7:menu_container.height * 0.7
width: height
onClicked: {
settingsDialog.open()
}
image: appTheme.style.settIcon
backgroundColor: parent.pressed ? appTheme.style.buttonPressedColor:appTheme.style.buttonColor
2018-07-22 16:47:55 +02:00
}
Item {
height: profilesButt.height
width: profilesButt.height
2018-07-22 16:47:55 +02:00
}
2018-07-17 19:17:25 +02:00
FancyButton {
id: profilesButt
enabled: height > 0
2019-06-15 14:38:33 +02:00
state: speedBackend.baseStationState === "connected" ? "visible":"hidden"
width: height
onClicked: {
profilesDialog.open()
}
image: appTheme.style.profilesIcon
backgroundColor: parent.pressed ? appTheme.style.buttonPressedColor:appTheme.style.buttonColor
2019-06-15 14:38:33 +02:00
states: [
State {
name: "hidden"
PropertyChanges {
target: profilesButt
height: 0
}
},
State {
name: "visible"
PropertyChanges {
target: profilesButt
height: app.landscape() ? menu_container.width * 0.7:menu_container.height * 0.7
2019-06-15 14:38:33 +02:00
}
}
2019-06-15 14:38:33 +02:00
]
transitions: [
Transition {
NumberAnimation {
properties: "height"
}
}
]
2018-07-17 19:17:25 +02:00
}
}
}
2018-07-22 16:47:55 +02:00
/*----------------------
Timer states
----------------------*/
2018-07-17 19:17:25 +02:00
states: [
2018-07-22 16:47:55 +02:00
State {
name: "IDLE"
//state for the start page
PropertyChanges {
target: topContainerItm;
anchors.bottomMargin: app.landscape() ? undefined:parent.height * 0.1;
anchors.rightMargin: app.landscape() ? parent.height * 0.05:0
2018-07-22 16:47:55 +02:00
}
PropertyChanges {
target: startButt;
enabled: true; text: "start";
size: app.landscape() ? parent.width * 0.5:parent.height * 0.5
2018-07-22 16:47:55 +02:00
anchors.bottomMargin: parent.height * 0.5 - startButt.height * 0.5
anchors.rightMargin: parent.width * 0.5 - startButt.width * 0.5
}
PropertyChanges {
target: topLa
text: "Click Start to start"
}
2018-07-22 16:47:55 +02:00
},
State {
name: "WAITING"
//state when a false start occured and waiting for time calculation
PropertyChanges {
target: startButt; enabled: false; text: "waiting...";
anchors.rightMargin: app.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: app.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: cancelButt; scale: 0; enabled: false}
PropertyChanges { target: menu_container; }
PropertyChanges {
target: topLa
text: "please wait..."
}
},
2018-07-22 16:47:55 +02:00
State {
name: "STARTING"
//state for the start sequence
PropertyChanges { target: startButt; enabled: false; text: "starting...";
anchors.rightMargin: app.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: app.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)
2018-07-22 16:47:55 +02:00
}
PropertyChanges { target: cancelButt; scale: 1}
PropertyChanges { target: menu_container; }
PropertyChanges {
target: topLa
text: "starting..."
}
2018-07-22 16:47:55 +02:00
},
State {
name: "RUNNING"
//state when the timer is running
PropertyChanges { target: startButt; enabled: true;
text: "stop"
anchors.rightMargin: app.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: app.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)
2018-07-22 16:47:55 +02:00
}
2019-05-19 14:06:05 +02:00
PropertyChanges {
target: topLa
text: ""
}
2018-07-22 16:47:55 +02:00
},
State {
name: "STOPPED"
//state when the meassuring is over
PropertyChanges {
target: startButt;
enabled: true; text: "reset";
size: app.landscape() ? parent.height * 0.35:parent.height * 0.2;
anchors.bottomMargin: app.landscape() ? parent.height * 0.5 - startButt.height * 0.5:parent.height * 0.2 - startButt.height * 0.5
anchors.rightMargin: app.landscape() ? parent.height * 0.2 - startButt.height * 0.5:parent.width * 0.5 - startButt.width * 0.5
2018-07-22 16:47:55 +02:00
}
PropertyChanges {
target: topContainerItm;
anchors.rightMargin: app.landscape() ? 0-startButt.width/2:undefined
anchors.bottomMargin: app.landscape() ? undefined:0-startButt.height/2
2018-07-22 16:47:55 +02:00
}
2019-05-19 14:06:05 +02:00
PropertyChanges {
target: topLa
text: ""
}
2018-07-22 16:47:55 +02:00
}
]
/*----------------------
Timer animations
----------------------*/
2018-07-17 19:17:25 +02:00
transitions: [
Transition {
NumberAnimation { properties: "size,rightMargin,height,width,bottomMargin,font.pixelSize,pixelSize"; easing.type: Easing.InOutQuad; duration: 700 }
2018-07-17 19:17:25 +02:00
},
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 {
2019-03-09 15:06:48 +01:00
from: "STARTING"
to: "RUNNING"
//disable transitions for the RUNNING state
},
Transition {
from: "RUNNING"
to: "WAITING"
//disable transitions for the RUNNING state
2018-07-17 19:17:25 +02:00
}
]
2018-07-22 16:47:55 +02:00
/*----------------------
Timer functions
----------------------*/
function landscape(){
return(app.height < app.width)
2018-07-22 16:47:55 +02:00
}
/*----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)
}
}
2018-07-17 19:17:25 +02:00
}
}