C

Qt Quick Ultralite swipe_game Demo

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

import QtQuick 2.0
import StyleModule 1.0
import SwipeModule 1.0
/*
    This view displays information about the current game status and
    controls the flow of the game.
*/

BaseView {
    id: root

    signal gameOver()
    signal gameAborted()

    // start/stop the timers which control the flow if the view is (not) visible
    onVisibleChanged: {
        if (visible) {
            restartTimer.restart()
        } else {
            responseTimer.stop()
        }
    }

    MouseArea {
        id: swipeArea
        anchors.fill: parent

        property int startX: 0
        property int startY: 0
        property bool swipeFinished: false

        onPressed: {
            startX = mouseX
            startY = mouseY
            swipeFinished = false
        }

        function guessedGreater(swipe : Swipe.Direction) : bool
        {
            return (swipe == Swipe.Direction.Right) || (swipe == Swipe.Direction.Up)
        }

        onPositionChanged: {
            if (swipeFinished) {
                return
            }
            var swipe = Swipe.detectDirection(mouseX, mouseY, startX, startY, Style.swipeThreshold)
            if ((Globals.isNumberMode() && Swipe.isHorizontal(swipe)) ||
                (!Globals.isNumberMode() && Swipe.isVertical(swipe))) {
                swipeFinished = true
                Globals.checkAnswer(guessedGreater(swipe))
                internal.checkStatus()
            }
        }
    }

    Row {
        id: failRow

        anchors {
            top: parent.top
            topMargin: Style.marginBig
            horizontalCenter: parent.horizontalCenter
        }
        spacing: Style.lineSize

        Repeater {
            model: Globals.tries

            delegate: Rectangle {
                height: Style.buttonHeight
                width: height
                radius: height * 0.5
                color: Style.colorLines

                Rectangle {
                    anchors {
                        fill: parent
                        margins: Style.lineSize
                    }

                    radius: height * 0.5
                    color: index < Globals.fails ? Style.colorWarning : Style.colorButtonBackground
                }
            }
        }
    }

    Text {
        id: currentValue

        anchors.centerIn: parent
        font: Globals.isNumberMode() ? Style.textFontBig : Style.textFontDefault
        color: Style.colorText
        text: Globals.isNumberMode() ? Globals.gameCurrentNumber : Globals.gameCurrentCountry
    }

    Text {
        id: score

        anchors {
            bottom: stopButton.top
            bottomMargin: Style.marginDefault
            horizontalCenter: parent.horizontalCenter
        }
        font: Style.textFontDefault
        color: Style.colorText
        text: "Score: " + Globals.score
    }

    Button {
        id: stopButton

        anchors {
            bottom: parent.bottom
            bottomMargin: Style.marginDefault
            horizontalCenter: parent.horizontalCenter
        }
        font: Style.textFontDefault
        text: "Stop"

        onClicked: {
            root.gameAborted()
        }
    }

    QtObject {
        id: internal

        function checkStatus() {
            if (Globals.fails >= Globals.tries) {
                root.gameOver()
            } else {
                Globals.requestNewValue()
                restartTimer.restart()
            }
        }
    }

    Timer {
        id: responseTimer

        property int steps: 12 // aim for 12 steps to visualize the timeout indicators
        property int currentStep: 0
        property int angle: currentStep * 360/steps

        interval: Globals.time / steps
        repeat: true

        onTriggered: {
            currentStep += 1
            if (currentStep >= steps) {
                stop()
                Globals.addFail()
                internal.checkStatus()
            }
        }
    }

    // restarting a timer while its onTriggered block is still active doesn't work.
    // It even crashes when the timer is set to repeat and restart is called while onTriggered is still active.
    // Use a second timer to restart the responseTimer with a small delay
    Timer {
        id: restartTimer

        interval: 10

        onTriggered: {
            responseTimer.currentStep = 0
            responseTimer.restart()
        }
    }

    // the timeoutIndicator displays a line which grows circular around the frame
    // to indicate how much time is left to respond to the current game value.
    Item {
        id: timeoutIndicator

        anchors.fill: parent

        Item {
            id: rightArcClipper

            anchors {
                left: parent.horizontalCenter
                right: parent.right
                top: parent.top
                bottom: parent.bottom
            }
            clip: true
            visible: responseTimer.running

            Image {
                id: rightArc

                anchors {
                    verticalCenter: parent.verticalCenter
                    horizontalCenter: parent.left
                }
                width: timeoutIndicator.width
                height: width
                source: "timebar.svg"
                rotation: Math.min(180, responseTimer.angle)
            }
        }

        Item {
            id: leftArcClipper

            anchors {
                left: parent.left
                right: parent.horizontalCenter
                top: parent.top
                bottom: parent.bottom
            }
            clip: true
            visible: responseTimer.running

            Image {
                id: leftArc

                width: timeoutIndicator.width
                height: width
                source: "timebar.svg"
                rotation: Math.max(180, responseTimer.angle)
            }
        }
    }
}