1/* GCompris - melody.qml
2 *
3 * SPDX-FileCopyrightText: 2015 Bruno Coudoin <bruno.coudoin@gcompris.net>
4 *
5 * Authors:
6 *   Jose JORGE <jjorge@free.fr> (GTK+ version)
7 *   Bruno Coudoin <bruno.coudoin@gcompris.net> (Qt Quick port)
8 *
9 *   SPDX-License-Identifier: GPL-3.0-or-later
10 */
11import QtQuick 2.9
12import GCompris 1.0
13
14import "../../core"
15
16ActivityBase {
17    id: activity
18
19    onStart: focus = true
20    onStop: {}
21    isMusicalActivity: true
22
23    pageComponent: Rectangle {
24        id: background
25        anchors.fill: parent
26        color: "#ABCDEF"
27
28        // if audio is disabled, we display a dialog to tell users this activity requires audio anyway
29        property bool audioDisabled: false
30
31        signal start
32        signal stop
33
34        Component.onCompleted: {
35            activity.start.connect(start)
36            activity.stop.connect(stop)
37        }
38
39        // Add here the QML items you need to access in javascript
40        QtObject {
41            id: items
42            property string url: "qrc:/gcompris/src/activities/melody/resource/"
43            property var question
44            property var questionToPlay
45            property var answer
46            property alias questionInterval: questionPlayer.interval
47            property int numberOfLevel: 10
48            property bool running: false
49        }
50
51        onStart: {
52            bar.level = 1
53            score.numberOfSubLevels = 5
54            score.currentSubLevel = 1
55            if(!ApplicationSettings.isAudioVoicesEnabled || !ApplicationSettings.isAudioEffectsEnabled) {
56                    background.audioDisabled = true;
57            } else {
58                initLevel();
59                items.running = true;
60                introDelay.start();
61            }
62        }
63
64        onStop: {
65            knock.stop()
66            questionPlayer.stop()
67            items.running = false
68        }
69
70        Item {
71            id: layoutArea
72            width: parent.width
73            height: parent.height - bar.height * 1.5 - score.height * 1.3
74            anchors.top: score.bottom
75            anchors.left: parent.left
76        }
77
78        Image {
79            id: xylofon
80            anchors {
81                fill: layoutArea
82                margins: 10 * ApplicationInfo.ratio
83            }
84            source: items.url + 'xylofon.svg'
85            sourceSize.width: width
86            sourceSize.height: height
87            fillMode: Image.PreserveAspectFit
88        }
89
90        Repeater {
91            id: parts
92            model: 4
93            Image {
94                id: part
95                source: items.url + 'xylofon_part' + (index + 1) + '.svg'
96                rotation: - 80
97                anchors.horizontalCenter: xylofon.horizontalCenter
98                anchors.horizontalCenterOffset: (- xylofon.paintedWidth) * 0.3 + xylofon.paintedWidth * index * 0.22
99                anchors.verticalCenter: xylofon.verticalCenter
100                anchors.verticalCenterOffset: - xylofon.paintedHeight * 0.1
101                sourceSize.width: xylofon.paintedWidth * 0.5
102                fillMode: Image.PreserveAspectFit
103
104                property alias anim: anim
105
106                SequentialAnimation {
107                    id: anim
108                    NumberAnimation {
109                        target: part
110                        property: "scale"
111                        from: 1; to: 0.95
112                        duration: 150
113                        easing.type: Easing.InOutQuad
114                    }
115                    NumberAnimation {
116                        target: part
117                        property: "scale"
118                        from: 0.95; to: 1
119                        duration: 150
120                        easing.type: Easing.OutElastic
121                    }
122                }
123
124                MouseArea {
125                    anchors.fill: parent
126                    enabled: !questionPlayer.running && !knock.running && !introDelay.running
127                              && !anim.running && !bonus.isPlaying
128                    onClicked: {
129                        anim.start()
130                        background.playNote(index)
131                        items.answer.push(index)
132                        background.checkAnswer()
133                    }
134                }
135            }
136        }
137
138        function playNote(index) {
139            activity.audioEffects.play(ApplicationInfo.getAudioFilePath(items.url +
140                                       'xylofon_son' + (index + 1) + ".wav"))
141        }
142        Timer {
143            id: introDelay
144            interval: 1000
145            repeat: false
146            onTriggered: {
147                if(activity.audioVoices.playbackState == 1) {
148                    introDelay.restart()
149                }
150                else {
151                    parent.repeat()
152                }
153            }
154        }
155
156
157        Timer {
158            id: knock
159            interval: 1000
160            repeat: false
161            onTriggered: {
162                questionPlayer.start()
163            }
164        }
165
166        Timer {
167            id: questionPlayer
168            onTriggered: {
169                var partIndex = items.questionToPlay.shift()
170                if(partIndex !== undefined) {
171                    parts.itemAt(partIndex).anim.start()
172                    background.playNote(partIndex)
173                    start()
174                }
175            }
176        }
177
178        DialogHelp {
179            id: dialogHelp
180            onClose: home()
181        }
182
183        Bar {
184            id: bar
185            content: BarEnumContent { value: help | home | level | repeat }
186            onHelpClicked: {
187                displayDialog(dialogHelp)
188            }
189            onPreviousLevelClicked: {
190                score.currentSubLevel = 1
191                if(bar.level == 1) {
192                    bar.level = items.numberOfLevel
193                } else {
194                    bar.level--
195                }
196                initLevel();
197                parent.repeat();
198            }
199            onNextLevelClicked: {
200                parent.nextLevel();
201                parent.repeat();
202            }
203            onHomeClicked: activity.home()
204            onRepeatClicked: parent.repeat()
205        }
206
207        Bonus {
208            id: bonus
209            onWin: {
210                parent.nextSubLevel();
211                introDelay.restart();
212            }
213            onLoose: introDelay.restart();
214        }
215
216        Score {
217            id: score
218            anchors.bottom: undefined
219            anchors.right: parent.right
220            anchors.rightMargin: 10 * ApplicationInfo.ratio
221            anchors.top: parent.top
222        }
223
224        function initLevel() {
225            items.question = []
226            questionPlayer.stop()
227
228            var numberOfParts = 4
229            if(bar.level < 3)
230                numberOfParts = 2
231            else if(bar.level < 5)
232                numberOfParts = 3
233
234            for(var i = 0; i < bar.level + 2; ++i) {
235                items.question.push(Math.floor(Math.random() * numberOfParts))
236            }
237            items.questionInterval = 1200 - Math.min(500, 100 * bar.level)
238            items.answer = []
239        }
240
241        function nextSubLevel() {
242            if(score.currentSubLevel < score.numberOfSubLevels) {
243                score.currentSubLevel++
244                initLevel()
245                return
246            }
247            nextLevel()
248        }
249
250        function nextLevel() {
251            score.currentSubLevel = 1
252            if(items.numberOfLevel === bar.level ) {
253                bar.level = 1
254            } else {
255                bar.level++
256            }
257            initLevel();
258        }
259
260        function repeat() {
261            if(items.running == true) {
262                introDelay.stop()
263                activity.audioVoices.stop()
264                questionPlayer.stop()
265                activity.audioEffects.play(ApplicationInfo.getAudioFilePath(items.url + 'knock.wav'))
266                items.questionToPlay = items.question.slice()
267                items.answer = []
268                knock.start()
269            }
270        }
271
272        function checkAnswer() {
273            if(items.answer.join() == items.question.join())
274                bonus.good('note')
275            else if(items.answer.length >= items.question.length)
276                bonus.bad('note')
277        }
278
279        Loader {
280            id: audioNeededDialog
281            sourceComponent: GCDialog {
282                parent: activity
283                isDestructible: false
284                message: qsTr("This activity requires sound, so it will play some sounds even if the audio voices or effects are disabled in the main configuration.")
285                button1Text: qsTr("Quit")
286                button2Text: qsTr("Continue")
287                onButton1Hit: activity.home();
288                onClose: {
289                    background.audioDisabled = false;
290                    initLevel();
291                    items.running = true;
292                    introDelay.start();
293                }
294            }
295            anchors.fill: parent
296            focus: true
297            active: background.audioDisabled
298            onStatusChanged: if (status == Loader.Ready) item.start()
299        }
300    }
301}
302