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 provides a list of highscores. Each time the game is completed,
    a new entry is added to the list.
    Scrolling is deliberately deactivated on the list itself. Instead it can
    be scrolled using areas at the sides of the list.
*/

BaseView {
    id: root

    property int swipeAreaSize: Globals.useSmallTouchAreas ? Style.swipeAreaSizeSmall : Style.swipeAreaSizeDefault

    function addHighscore() {
        highscoreModel.addEntry(Globals.time, Globals.tries, Globals.score)
    }

    signal swipeTriggered()

    clip: true

    MouseArea {
        id: scrollArea

        property int startY: 0
        property int startContentY: 0

        anchors {
            top: contentFlickable.top
            bottom: contentFlickable.bottom
            left: parent.left
            right: parent.right
        }

        onPressed: {
            startY = mouseY
            startContentY = contentFlickable.contentY
        }
        onPositionChanged: {
            contentFlickable.contentY = Math.max(0, Math.min(startContentY - mouseY + startY, contentFlickable.contentHeight - contentFlickable.height))
        }

        MouseArea {
            id: blockArea

            anchors {
                fill: parent
                leftMargin: root.swipeAreaSize
                rightMargin: root.swipeAreaSize
            }
        }
        // can be activated in the config view as a guide for users
        Rectangle {
            id: leftAreaVisualizer

            anchors {
                left: parent.left
                right: blockArea.left
                top: parent.top
                bottom: parent.bottom
            }
            color: "magenta"
            opacity: 0.4
            visible: Globals.showTouchAreas
        }
        // can be activated in the config view as a guide for users
        Rectangle {
            id: rightAreaVisualizer

            anchors {
                left: blockArea.right
                right: parent.right
                top: parent.top
                bottom: parent.bottom
            }
            color: "magenta"
            opacity: 0.4
            visible: Globals.showTouchAreas
        }
    }

    HighscoreModel {
        id: highscoreModel
    }

    Flickable {
        id: contentFlickable

        property int hypothenuse: root.height * 0.5

        anchors {
            left: parent.left
            right: parent.right
            margins: root.swipeAreaSize
            verticalCenter: parent.verticalCenter
        }
        // with the width set by the swipeAreaSize value the height is calulated so its top and bottom hit the circular frame
        height: 2 * Math.sqrt((hypothenuse * hypothenuse) - ((hypothenuse - root.swipeAreaSize) * (hypothenuse - root.swipeAreaSize)))
        contentHeight: Math.max(contentColumn.height, contentFlickable.height)
        contentWidth: 0
        interactive: false

        Column {
            id: contentColumn

            function addZeros(score : int) : string {
                if (score < 10) {
                    return "00" + score
                } else if (score < 100) {
                    return "0" + score
                }
                return score
            }

            function formatTime(time : int) : string {
                if (time % 1000 === 0) {
                    return time/1000 + ".0"
                }
                return time/1000
            }

            width: contentFlickable.width
            spacing: Style.listSpacing

            Text {
                id: header

                anchors.horizontalCenter: parent.horizontalCenter
                font: Style.textFontSmall
                color: Style.colorText
                text: "Highscore"
            }

            Repeater {
                id: contentRepeater

                model: highscoreModel

                delegate: Text {
                    anchors.horizontalCenter: parent.horizontalCenter
                    font: Style.textFontSmall
                    color: Style.colorText
                    text: contentColumn.addZeros(index + 1) + ": " +
                          contentColumn.formatTime(model.time) + "s, " + model.tries + " tries: " +
                          contentColumn.addZeros(model.score)
                }
            }
        }
    }

    ScrollIndicator {
        id: leftIndicator

        anchors {
            top: contentFlickable.top
            bottom: contentFlickable.bottom
            left: contentFlickable.left
        }
        flickable: contentFlickable
        showIndicator: scrollArea.pressed
    }

    ScrollIndicator {
        id: rightIndicator

        anchors {
            verticalCenter: parent.verticalCenter
            right: contentFlickable.right
        }
        height: leftIndicator.height
        flickable: contentFlickable
        showIndicator: scrollArea.pressed
    }

    // used for navigation between views
    DirectionalSwipeArea {
        id: downArea

        anchors.fill: parent

        direction: Swipe.Direction.Down

        onTriggered: {
            root.swipeTriggered()
        }
    }

    // a mask is required to hide the flickable's content outside the circular frame, because due to the circular
    // layout of the view clipping alone woulnd't suffice.
    Image {
        id: circleMask

        anchors.fill: parent
        source: "mask_circle.svg"
    }
}