Warning, /education/gcompris/src/core/Bonus.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - Bonus.qml
0002  *
0003  * SPDX-FileCopyrightText: 2014 Bruno Coudoin <bruno.coudoin@gcompris.net>
0004  *
0005  * Authors:
0006  *   Bruno Coudoin <bruno.coudoin@gcompris.net>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 import QtQuick 2.12
0011 import GCompris 1.0
0012 
0013 // Requires the global property in the scope:
0014 // property GCAudio audioEffects, audioVoices
0015 
0016 /**
0017  * A QML component providing user feedback upon winning/loosing.
0018  * @ingroup components
0019  *
0020  * Usually triggered by an activity when a user has won/lost a level via the
0021  * @ref good / @ref bad methods. Bonus then provides visual and auditive
0022  * feedback to the user and emits the @ref win / @ref loose signals when
0023  * finished.
0024  *
0025  * Maintains a list of possible audio voice resources to be played back
0026  * upon winning/loosing events, and selects randomly from them when triggered.
0027  *
0028  * @inherit QtQuick.Image
0029  */
0030 Image {
0031     id: bonus
0032 
0033     /**
0034      * type:string
0035      * Url of the audio resource to be used as winning sound.
0036      */
0037     property string winSound: url + "sounds/bonus.wav"
0038 
0039     /**
0040      * type:string
0041      * Url of the audio resource to be used as loosing sound.
0042      */
0043     property string looseSound
0044 
0045     /**
0046      * type:int
0047      * Interval in milliseconds after which the bonus will be played (default is 500ms)
0048      */
0049     property alias interval: timer.interval
0050 
0051     /**
0052      * Emitted when the bonus starts
0053      */
0054     signal start
0055 
0056     /**
0057      * Emitted when the bonus stops
0058      */
0059     signal stop
0060 
0061     /**
0062      * Emitted when the win event is over.
0063      *
0064      * After the animation has finished.
0065      */
0066     signal win
0067 
0068     /**
0069      * Emitted when the loose event is over.
0070      *
0071      * After the animation has finished.
0072      */
0073     signal loose
0074 
0075     Connections {
0076         target: activity
0077         onStop: haltBonus();
0078     }
0079 
0080     /// @cond INTERNAL_DOCS
0081     property string url: "qrc:/gcompris/src/core/resource/"
0082     property bool isWin: false
0083     property var winVoices: [
0084         "voices-$CA/$LOCALE/misc/congratulation.$CA",
0085         "voices-$CA/$LOCALE/misc/great.$CA",
0086         "voices-$CA/$LOCALE/misc/good.$CA",
0087         "voices-$CA/$LOCALE/misc/awesome.$CA",
0088         "voices-$CA/$LOCALE/misc/fantastic.$CA",
0089         "voices-$CA/$LOCALE/misc/waytogo.$CA",
0090         "voices-$CA/$LOCALE/misc/super.$CA",
0091         "voices-$CA/$LOCALE/misc/perfect.$CA"
0092     ]
0093     property var looseVoices: [
0094         "voices-$CA/$LOCALE/misc/try_again.$CA",
0095         "voices-$CA/$LOCALE/misc/check_answer.$CA"
0096     ]
0097     readonly property int tryAgain: 0
0098     readonly property int checkAnswer: 1
0099     /// @endcond
0100 
0101     property int nextAudioIndex: 0
0102     /**
0103      * type:bool
0104      * True between the moment we have the win/lose signal emitted and the 
0105      * bonus image is no more displayed
0106      */
0107     property bool isPlaying: animation.running || timer.running
0108     visible: true
0109     opacity: 0
0110     anchors.fill: parent
0111     anchors.margins: 50 * ApplicationInfo.ratio
0112     fillMode: Image.PreserveAspectFit
0113     z: 1000
0114     sourceSize.width: parent.width * 0.5
0115 
0116     /**
0117      * type:bool
0118      * used in animation onStopped to check if we need to trigger win-loose signals
0119      */
0120     property bool bonusStopped: false
0121 
0122     /**
0123      *  Use this when stopping manually the bonus, like when changing level manually
0124      */
0125     function haltBonus() {
0126         bonusStopped = true;
0127         timer.stop();
0128         animation.stop();
0129         bonus.opacity = 0;
0130         bonusStopped = false;
0131     }
0132 
0133     /**
0134      * Triggers win feedback.
0135      *
0136      * Tries to play back a voice resource for winning, if not found fall back
0137      * to the winSound.
0138      *
0139      * @param name Type of win image to show.
0140      * Possible values are "flower", "gnu", "lion", "note", "smiley", "tux"
0141      */
0142     function good(name) {
0143         source = url + "bonus/" + name + "_good.svg"
0144         isWin = true
0145         timer.restart()
0146     }
0147 
0148     /**
0149      * Triggers loose feedback.
0150      *
0151      * Tries to play back a voice resource for loosing, if not found fall back
0152      * to the looseSound.
0153      *
0154      * @param name Type of win image to show.
0155      * Possible values are "flower", "gnu", "lion", "note", "smiley", "tux"
0156      */
0157     function bad(name, audioIndex) {
0158         source = url + "bonus/" + name + "_bad.svg"
0159         isWin = false;
0160         timer.restart()
0161         if(audioIndex) {
0162             nextAudioIndex = audioIndex
0163         }
0164         else {
0165             nextAudioIndex = bonus.tryAgain
0166         }
0167     }
0168 
0169     /**
0170      * Private: Triggers win feedback after the timer completion
0171      */
0172     function _good() {
0173         if(!audioVoices.play(
0174                     ApplicationInfo.getAudioFilePath(
0175                         winVoices[Math.floor(Math.random()*winVoices.length)])))
0176             if(winSound)
0177                 audioEffects.play(winSound)
0178 
0179         start()
0180         animation.start()
0181     }
0182 
0183     /**
0184      * Private: Triggers loose feedback after the timer completion.
0185      */
0186     function _bad(name) {
0187         var audio = ApplicationInfo.getAudioFilePath(
0188                         looseVoices[nextAudioIndex])
0189         // Defaults to "check answer" if required audio does not exist
0190         if(!file.exists(audio)) {
0191             audio = ApplicationInfo.getAudioFilePath(
0192                         looseVoices[bonus.checkAnswer])
0193         }
0194         if(!audioVoices.play(audio))
0195             if(looseSound)
0196                 audioEffects.play(looseSound)
0197         start()
0198         animation.start()
0199     }
0200 
0201     SequentialAnimation {
0202         id: animation
0203         NumberAnimation {
0204             target: bonus
0205             property: "opacity"
0206             from: 0; to: 1.0
0207             duration: 1000
0208             easing.type: Easing.InOutQuad
0209         }
0210         NumberAnimation {
0211             target: bonus
0212             property: "opacity"
0213             from: 1.0; to: 0
0214             duration: 500
0215             easing.type: Easing.InOutQuad
0216         }
0217         onStopped: {
0218             bonus.stop();
0219             if(!bonusStopped)
0220                 isWin ? win() : loose();
0221         }
0222     }
0223 
0224     File {
0225         id: file
0226     }
0227     // It is useful to launch the bonus after a delay to let the children
0228     // appreciate the completed level
0229     Timer {
0230         id: timer
0231         interval: 500
0232         onTriggered: isWin ? bonus._good() : bonus._bad()
0233     }
0234 }