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

/*
    This component provides circular gesture recognition.
*/

MouseArea {
    id: root

    // NOTE: overwrite the Style and Globals value assignments if you want to use the component outside of this project

    // determines which distance a touch coordinate needs to have from the center to count as valid
    property int circleMinDistance: Style.swipeCircleMinDistance
    // determines the angle a circular gesture needs to cover to be recognized as a swipe
    property int arcThreshold: Style.swipeArcThreshold
    // the image used for visual feedback. Set to empty string if you want to try this component outside of this project
    property string highlightSource: "mask_circle_highlight_right.svg"
    // can be activated in the config view as a guide for users
    property bool showTouchArea: Globals.showTouchAreas

    signal clockwiseTriggered()
    signal counterClockwiseTriggered()

    implicitWidth: Style.appSize
    implicitHeight: Style.appSize

    onPressed: {
        var distanceFromCenter = internal.calculateDistanceFromCenter(mouseX, mouseY)
        // reject touch coordinates outside a circular strip
        if (distanceFromCenter >= internal.radius || distanceFromCenter < root.circleMinDistance) {
            mouse.accepted = false
        } else {
            internal.startAngle = internal.calculateAngle(mouseX, mouseY)
            internal.currentAngle = internal.startAngle
            highlight.visible = true
        }
    }

    onPositionChanged: {
        var distanceFromCenter = internal.calculateDistanceFromCenter(mouseX, mouseY)
        if (distanceFromCenter >= internal.radius || distanceFromCenter < root.circleMinDistance) {
            highlight.visible = false
            // reset the startAngle if the touch coordinates move outside the desired area
            internal.startAngle = internal.calculateAngle(mouseX, mouseY)
        } else {
            internal.currentAngle = internal.calculateAngle(mouseX, mouseY)
            var arcLength = internal.currentAngle - internal.startAngle
            // take care to handle the transition between 180 and -180 properly, since Math.atan2 used
            // to calculate the angle returns angles from -180 to 180
            if (arcLength < -180) {
                arcLength += 360
            } else if (arcLength > 180) {
                arcLength -= 360
            }

            // trigger a signal if the angle between the start point and the current point exceeds a given threshold
            if (arcLength >= root.arcThreshold) {
                root.clockwiseTriggered()
                internal.startAngle = internal.currentAngle
            } else if (arcLength <= -root.arcThreshold) {
                root.counterClockwiseTriggered()
                internal.startAngle = internal.currentAngle
            }
            highlight.visible = true
        }
    }

    // can be activated in the config view as a guide for users
    Rectangle {
        id: areaVisualizer

        anchors.fill: parent
        radius: width * 0.5
        color: "magenta"
        opacity: 0.4
        visible: root.showTouchArea
    }

    Rectangle {
        anchors.centerIn: areaVisualizer
        width: 2 * root.circleMinDistance
        height: width
        radius: width * 0.5
        color: "#1b1d1e"
        visible: root.showTouchArea
    }

    onReleased: {
        highlight.visible = false
    }

    Image {
        id: highlight
        anchors.verticalCenter: parent.verticalCenter
        anchors.left: parent.horizontalCenter
        height: parent.height
        width: parent.width/2
        source: root.highlightSource
        transform: Rotation {
            origin.x: 0
            origin.y: height/2
            angle: internal.currentAngle
        }
        // don't use a binding to the pressed property of the MouseArea to show the highlight since
        // we explicitly don't want to show the highlight for touch coordinates outside a specific range
        visible: false
    }

    QtObject {
        id: internal

        property int startAngle: 0
        property int currentAngle: 0
        readonly property real radius: root.width * 0.5

        // returns the angle calculated from the given x,y coordinates
        function calculateAngle(x : int, y : int) : int {
            return radianToDegree(Math.atan2(y - radius, x - radius))
        }

        // converts a given radian value to degrees
        function radianToDegree(radian : real) : int {
            return radian * 180/Math.PI
        }

        // returns the distance the given x,y coordinates have to the center
        function calculateDistanceFromCenter(x : int, y : int) : int {
            return Math.sqrt((x - radius) * (x - radius) + (y - radius) * (y - radius))
        }
    }
}