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