Warning, /education/gcompris/src/activities/railroad/Railroad.qml is written in an unsupported language. File is not indexed.
0001 /* GCompris - railroad.qml 0002 * 0003 * SPDX-FileCopyrightText: 2016 Utkarsh Tiwari <iamutkarshtiwari@kde.org> 0004 * SPDX-FileCopyrightText: 2018 Amit Sagtani <asagtani06@gmail.com> 0005 * 0006 * Authors: 0007 * Pascal Georges (GTK+ version) 0008 * Utkarsh Tiwari <iamutkarshtiwari@kde.org> (Qt Quick port) 0009 * Amit Sagtani <asagtani06@gmail.com> (Qt Quick port) 0010 * Timothée Giet <animtim@gmail.com> (controls refactoring and bugfixes) 0011 * 0012 * SPDX-License-Identifier: GPL-3.0-or-later 0013 */ 0014 import QtQuick 2.12 0015 import GCompris 1.0 0016 import "../../core" 0017 import "railroad.js" as Activity 0018 0019 ActivityBase { 0020 id: activity 0021 0022 onStart: focus = true 0023 onStop: {} 0024 property bool isHorizontal: background.width >= background.height 0025 0026 pageComponent: Image { 0027 id: background 0028 source: Activity.resourceURL + "railroad-bg.svg" 0029 sourceSize.height: background.height 0030 fillMode: Image.PreserveAspectCrop 0031 anchors.horizontalCenter: parent.horizontalCenter 0032 0033 signal start 0034 signal stop 0035 0036 Component.onCompleted: { 0037 activity.start.connect(start) 0038 activity.stop.connect(stop) 0039 } 0040 0041 // Add here the QML items you need to access in javascript 0042 QtObject { 0043 id: items 0044 property Item main: activity.main 0045 property alias background: background 0046 property int currentLevel: activity.currentLevel 0047 property alias bonus: bonus 0048 property alias score: score 0049 property GCSfx audioEffects: activity.audioEffects 0050 property alias trainAnimationTimer: trainAnimationTimer 0051 property alias sampleList: sampleList 0052 property alias listModel: listModel 0053 property alias answerZone: answerZone 0054 property alias animateFlow: animateFlow 0055 property alias introMessage: introMessage 0056 property alias errorRectangle: errorRectangle 0057 property bool memoryMode: false 0058 property bool mouseEnabled: true 0059 property bool controlsEnabled: false 0060 property var currentKeyZone: sampleList 0061 property bool keyNavigationMode: false 0062 // stores height of sampleGrid images to set rail bar support position 0063 property int sampleImageHeight: 0 0064 property int sampleModel: Activity.dataset["noOfLocos"][currentLevel] + Activity.dataset["noOfWagons"][currentLevel] 0065 property var uniqueId: [] 0066 } 0067 0068 onStart: { Activity.start(items) } 0069 onStop: { Activity.stop() } 0070 // Needed to get keyboard focus on IntroMessage 0071 Keys.forwardTo: introMessage 0072 0073 Keys.onPressed: { 0074 items.keyNavigationMode = true; 0075 items.currentKeyZone.handleKeys(event); 0076 } 0077 0078 function playSoundFX() { 0079 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); 0080 } 0081 0082 // Countdown timer 0083 Timer { 0084 id: trainAnimationTimer 0085 repeat: false 0086 interval: 4000 0087 onTriggered: { 0088 items.animateFlow.start() 0089 activity.audioEffects.play(Activity.resourceURL + 'sounds/train.wav') 0090 } 0091 } 0092 0093 // Intro message 0094 IntroMessage { 0095 id: introMessage 0096 y: background.height / 4.7 0097 anchors { 0098 right: parent.right 0099 rightMargin: 5 0100 left: parent.left 0101 leftMargin: 5 0102 } 0103 z: score.z + 1 0104 onIntroDone: { 0105 trainAnimationTimer.start() 0106 } 0107 intro: [ 0108 qsTr("Observe and remember the train before the timer ends and then drag the items to set up a similar train."), 0109 qsTr("If you forgot the model, you can click on the Hint button to view it again.") 0110 ] 0111 } 0112 0113 // Top Display Area 0114 Rectangle { 0115 id: topDisplayArea 0116 width: background.width 0117 height: background.height * 0.2 0118 anchors.bottom: sampleList.top 0119 color: 'transparent' 0120 z: 1 0121 0122 GridView { 0123 id: answerZone 0124 readonly property int levelCellWidth: isHorizontal ? background.width / (listModel.count > 5 ? 7.2 : 5.66) : 0125 background.width / ((listModel.count > 5) ? 7.1 : 5) 0126 readonly property int levelCellHeight: levelCellWidth * 0.42 0127 width: parent.width 0128 height: levelCellHeight 0129 cellWidth: levelCellWidth 0130 cellHeight: levelCellHeight 0131 anchors.bottom: parent.bottom 0132 interactive: false 0133 model: listModel 0134 0135 delegate: Image { 0136 id: wagon 0137 source: Activity.resourceURL + modelData + ".svg" 0138 fillMode: Image.PreserveAspectFit 0139 width: answerZone.cellWidth 0140 sourceSize.width: wagon.width 0141 function checkDrop(dragItem) { 0142 // Checks the drop location of this wagon 0143 var globalCoordinates = dragItem.mapToItem(answerZone, 0, 0) 0144 if(globalCoordinates.y <= ((background.height / 12.5) + (background.height / 8))) { 0145 var dropIndex = Activity.getDropIndex(globalCoordinates.x) 0146 0147 if(dropIndex > (listModel.count - 1)) { 0148 // Handles index overflow 0149 dropIndex = listModel.count - 1 0150 } 0151 listModel.move(listModel.count - 1, dropIndex, 1) 0152 opacity = 1 0153 } 0154 if(globalCoordinates.y > (background.height / 8)) { 0155 // Remove it if dropped in the lower section 0156 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') 0157 listModel.remove(listModel.count - 1) 0158 } 0159 } 0160 0161 function createNewItem() { 0162 var component = Qt.createComponent("Loco.qml"); 0163 if(component.status === Component.Ready) { 0164 var newItem = component.createObject(parent, {"x": x, "y": y, "z": 10, "imageURL": source}); 0165 } 0166 return newItem 0167 } 0168 0169 MouseArea { 0170 id: displayWagonMouseArea 0171 hoverEnabled: true 0172 enabled: !introMessage.visible && items.mouseEnabled 0173 anchors.fill: parent 0174 0175 onPressed: { 0176 if(items.memoryMode) { 0177 drag.target = parent.createNewItem(); 0178 parent.opacity = 0 0179 listModel.move(index, listModel.count - 1, 1) 0180 } 0181 answerZone.selectedSwapIndex = -1; 0182 } 0183 onReleased: { 0184 if(items.memoryMode) { 0185 var dragItem = drag.target 0186 parent.checkDrop(dragItem) 0187 dragItem.destroy(); 0188 parent.Drag.cancel() 0189 } 0190 } 0191 0192 onClicked: { 0193 // skips memorization time 0194 if(!items.memoryMode) { 0195 bar.hintClicked() 0196 } 0197 else { 0198 items.currentKeyZone = answerZone 0199 if(items.keyNavigationMode) { 0200 answerZone.currentIndex = index 0201 } 0202 } 0203 answerZone.selectedSwapIndex = -1; 0204 } 0205 } 0206 states: State { 0207 name: "wagonHover" 0208 when: displayWagonMouseArea.containsMouse && (items.memoryMode === true) 0209 PropertyChanges { 0210 target: wagon 0211 scale: 1.1 0212 } 0213 } 0214 } 0215 0216 onXChanged: { 0217 if(answerZone.x >= background.width) { 0218 trainAnimationTimer.stop() 0219 animateFlow.stop(); 0220 listModel.clear(); 0221 items.memoryMode = true; 0222 items.controlsEnabled = true; 0223 } 0224 } 0225 0226 PropertyAnimation { 0227 id: animateFlow 0228 target: answerZone 0229 properties: "x" 0230 from: answerZone.x 0231 to: background.width 0232 duration: 4000 0233 easing.type: Easing.InExpo 0234 loops: 1 0235 onStopped: answerZone.x = 2; 0236 } 0237 0238 function handleKeys(event) { 0239 // Checks answer. 0240 if(event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { 0241 okButton.clicked(); 0242 } 0243 if(trainAnimationTimer.running || animateFlow.running) { 0244 return; 0245 } 0246 if(event.key === Qt.Key_Tab) 0247 bar.hintClicked(); 0248 if(!items.controlsEnabled) 0249 return; 0250 if(event.key === Qt.Key_Down) { 0251 playSoundFX(); 0252 items.currentKeyZone = sampleList; 0253 answerZone.currentIndex = -1; 0254 sampleList.currentIndex = 0; 0255 } 0256 if(event.key === Qt.Key_Up) { 0257 playSoundFX(); 0258 items.currentKeyZone = sampleList; 0259 answerZone.currentIndex = -1; 0260 sampleList.currentIndex = 0; 0261 } 0262 if(event.key === Qt.Key_Left) { 0263 playSoundFX(); 0264 answerZone.moveCurrentIndexLeft(); 0265 } 0266 if(event.key === Qt.Key_Right) { 0267 playSoundFX(); 0268 answerZone.moveCurrentIndexRight(); 0269 } 0270 // Remove a wagon via Delete/Return key. 0271 if((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && listModel.count > 0) { 0272 playSoundFX(); 0273 listModel.remove(answerZone.currentIndex); 0274 if(listModel.count < 2) { 0275 answerZone.selectedSwapIndex = -1; 0276 } 0277 } 0278 // Swaps two wagons with help of Space/Enter keys. 0279 if(event.key === Qt.Key_Space) { 0280 if(selectedSwapIndex === -1 && listModel.count > 1) { 0281 playSoundFX(); 0282 answerZone.selectedSwapIndex = answerZone.currentIndex; 0283 swapHighlight.x = answerZone.currentItem.x; 0284 swapHighlight.anchors.top = answerZone.top; 0285 } 0286 else if(answerZone.currentIndex != selectedSwapIndex && listModel.count > 1){ 0287 playSoundFX(); 0288 var min = Math.min(selectedSwapIndex, answerZone.currentIndex); 0289 var max = Math.max(selectedSwapIndex, answerZone.currentIndex); 0290 listModel.move(min, max, 1); 0291 listModel.move(max-1, min, 1); 0292 answerZone.selectedSwapIndex = -1; 0293 } 0294 } 0295 } 0296 // variable for storing the index of wagons to be swapped via key navigations. 0297 property int selectedSwapIndex: -1 0298 0299 Keys.enabled: true 0300 focus: true 0301 keyNavigationWraps: true 0302 highlightRangeMode: GridView.ApplyRange 0303 highlight: Rectangle { 0304 width: answerZone.cellWidth 0305 height: answerZone.cellHeight 0306 color: "blue" 0307 opacity: 0.3 0308 radius: 5 0309 visible: (items.currentKeyZone === answerZone) && (!trainAnimationTimer.running && !animateFlow.running) 0310 && items.keyNavigationMode && items.controlsEnabled 0311 x: (visible && answerZone.currentItem) ? answerZone.currentItem.x : 0 0312 y: (visible && answerZone.currentItem) ? answerZone.currentItem.y : 0 0313 Behavior on x { 0314 SpringAnimation { 0315 spring: 3 0316 damping: 0.2 0317 } 0318 } 0319 Behavior on y { 0320 SpringAnimation { 0321 spring: 3 0322 damping: 0.2 0323 } 0324 } 0325 } 0326 highlightFollowsCurrentItem: false 0327 } 0328 0329 // Used to highlight a wagon selected for swaping via key navigations 0330 Rectangle { 0331 id: swapHighlight 0332 width: answerZone.cellWidth 0333 height: answerZone.cellHeight 0334 visible: answerZone.selectedSwapIndex != -1 ? true : false 0335 color: "#AA41AAC4" 0336 opacity: 0.8 0337 radius: 5 0338 } 0339 0340 ListModel { 0341 id: listModel 0342 } 0343 } 0344 0345 // Lower Sample Wagon Display Area 0346 GridView { 0347 id: sampleList 0348 visible: items.memoryMode 0349 y: background.height * 0.2 0350 z: 5 0351 width: background.width 0352 height: background.height * 0.8 0353 anchors.margins: 20 0354 cellWidth: width / columnCount 0355 cellHeight: isHorizontal ? background.height / 7 : background.height / 7.5 0356 model: items.uniqueId 0357 interactive: false 0358 0359 // No. of wagons in a row 0360 readonly property int columnCount: isHorizontal ? Activity.dataset["columnsInHorizontalMode"][items.currentLevel] : 0361 Activity.dataset["columsInVerticalMode"][items.currentLevel] 0362 0363 readonly property int rowCount: columnCount > 0 ? model.length / columnCount : 0 0364 0365 delegate: Image { 0366 id: loco 0367 readonly property string uniqueID: modelData 0368 property real originX 0369 property real originY 0370 source: Activity.resourceURL + uniqueID + ".svg" 0371 width: isHorizontal ? background.width / 5.66 : background.width / 4.2 0372 sourceSize.width: width 0373 fillMode: Image.PreserveAspectFit 0374 visible: true 0375 onHeightChanged: items.sampleImageHeight = height 0376 onVisibleChanged: items.sampleImageHeight = height 0377 function initDrag() { 0378 originX = x 0379 originY = y 0380 } 0381 0382 function replace() { 0383 x = originX 0384 y = originY 0385 } 0386 0387 function checkDrop() { 0388 // Checks the drop location of this wagon 0389 var globalCoordinates = loco.mapToItem(answerZone, 0, 0) 0390 // checks if the wagon is dropped in correct zone and no. of wagons in answer row are less than 0391 // total no. of wagons in correct answer + 2, before dropping the wagon 0392 if(globalCoordinates.y <= (background.height / 12.5) && 0393 listModel.count < Activity.dataset["WagonsInCorrectAnswers"][items.currentLevel] + 2) { 0394 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') 0395 var dropIndex = Activity.getDropIndex(globalCoordinates.x) 0396 Activity.addWagon(uniqueID, dropIndex); 0397 } 0398 } 0399 0400 MouseArea { 0401 id: mouseArea 0402 hoverEnabled: true 0403 anchors.fill: parent 0404 drag.target: parent 0405 enabled: items.mouseEnabled 0406 onClicked: { 0407 items.currentKeyZone = sampleList 0408 if(items.keyNavigationMode) { 0409 sampleList.currentIndex = index 0410 } 0411 } 0412 onPressed: { 0413 parent.initDrag() 0414 } 0415 onReleased: { 0416 parent.Drag.cancel() 0417 parent.checkDrop() 0418 parent.replace() 0419 } 0420 } 0421 0422 Component.onCompleted: initDrag(); 0423 0424 states: State { 0425 name: "carHover" 0426 when: mouseArea.containsMouse 0427 PropertyChanges { 0428 target: loco 0429 scale: 1.1 0430 } 0431 } 0432 } 0433 0434 function handleKeys(event) { 0435 if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { 0436 okButton.clicked(); 0437 } 0438 if(trainAnimationTimer.running || animateFlow.running) { 0439 return; 0440 } 0441 if(event.key === Qt.Key_Tab) 0442 bar.hintClicked(); 0443 if(!items.controlsEnabled) 0444 return; 0445 0446 if(event.key === Qt.Key_Up) { 0447 playSoundFX(); 0448 // Checks if current highlighted element is in first row of the grid. 0449 if(sampleList.currentIndex < columnCount && listModel.count > 0) { 0450 items.currentKeyZone = answerZone; 0451 answerZone.currentIndex = 0; 0452 sampleList.currentIndex = -1; 0453 } 0454 else { 0455 sampleList.moveCurrentIndexUp(); 0456 } 0457 } 0458 if(event.key === Qt.Key_Down) { 0459 playSoundFX(); 0460 // Checks if current highlighted element is in last row of the grid. 0461 if(sampleList.model - columnCount <= sampleList.currentIndex && listModel.count > 0) { 0462 items.currentKeyZone = answerZone; 0463 answerZone.currentIndex = 0; 0464 sampleList.currentIndex = -1; 0465 } 0466 else { 0467 sampleList.moveCurrentIndexDown(); 0468 } 0469 } 0470 if(event.key === Qt.Key_Left) { 0471 playSoundFX(); 0472 sampleList.moveCurrentIndexLeft(); 0473 } 0474 if(event.key === Qt.Key_Right) { 0475 playSoundFX(); 0476 sampleList.moveCurrentIndexRight(); 0477 } 0478 if(event.key === Qt.Key_Space) { 0479 var imageId = items.uniqueId[sampleList.currentIndex]; 0480 // At most (current level + 2) wagons are allowed in answer row at a time. 0481 if(listModel.count < Activity.dataset["WagonsInCorrectAnswers"][items.currentLevel] + 2) { 0482 playSoundFX(); 0483 Activity.addWagon(imageId, listModel.count); 0484 } 0485 } 0486 } 0487 0488 Keys.enabled: true 0489 focus: true 0490 keyNavigationWraps: true 0491 highlightRangeMode: GridView.ApplyRange 0492 highlight: Rectangle { 0493 width: isHorizontal ? background.width / 5.66 : background.width / 4.2 0494 height: isHorizontal ? sampleList.cellHeight : sampleList.cellHeight / 1.65 0495 color: "#AA41AAC4" 0496 opacity: 0.8 0497 radius: 5 0498 visible: items.currentKeyZone === sampleList && items.keyNavigationMode 0499 x: (sampleList.currentIndex >= 0 && sampleList.currentItem) ? sampleList.currentItem.x : 0 0500 y: (sampleList.currentIndex >= 0 && sampleList.currentItem) ? sampleList.currentItem.y : 0 0501 Behavior on x { 0502 SpringAnimation { 0503 spring: 3 0504 damping: 0.2 0505 } 0506 } 0507 Behavior on y { 0508 SpringAnimation { 0509 spring: 3 0510 damping: 0.2 0511 } 0512 } 0513 } 0514 highlightFollowsCurrentItem: false 0515 } 0516 0517 // Lower level wagons shelves 0518 Repeater { 0519 id: railSupporter 0520 model: sampleList.rowCount 0521 Rectangle { 0522 x: 0 0523 y: sampleList.y + (sampleList.cellHeight * (index + 1)) - (sampleList.cellHeight - items.sampleImageHeight) 0524 z: 1 0525 width: background.width 0526 height: isHorizontal ? 6 : 3 0527 border.color: "#808180" 0528 color: "transparent" 0529 border.width: 4 0530 } 0531 } 0532 0533 ErrorRectangle { 0534 id: errorRectangle 0535 anchors.fill: topDisplayArea 0536 z: score.z 0537 imageSize: okButton.width 0538 function releaseControls() { items.mouseEnabled = true; } 0539 } 0540 0541 // Answer Submission button 0542 BarButton { 0543 id: okButton 0544 source: "qrc:/gcompris/src/core/resource/bar_ok.svg" 0545 height: score.height 0546 width: height 0547 sourceSize.width: width 0548 sourceSize.height: height 0549 anchors.top: score.top 0550 z: score.z 0551 enabled: items.mouseEnabled 0552 anchors { 0553 right: score.left 0554 rightMargin: 10 0555 } 0556 ParticleSystemStarLoader { 0557 id: okButtonParticles 0558 clip: false 0559 } 0560 onClicked: { 0561 if(trainAnimationTimer.running || animateFlow.running) 0562 bar.hintClicked(); 0563 else if(listModel.count > 0 && visible) 0564 Activity.checkAnswer(); 0565 } 0566 } 0567 0568 DialogHelp { 0569 id: dialogHelp 0570 onClose: home() 0571 } 0572 0573 Score { 0574 id: score 0575 height: bar.height * 0.8 0576 width: height 0577 anchors.top: parent.top 0578 anchors.topMargin: 10 * ApplicationInfo.ratio 0579 anchors.right: parent.right 0580 anchors.leftMargin: 10 * ApplicationInfo.ratio 0581 anchors.bottom: undefined 0582 anchors.left: undefined 0583 onStop: Activity.nextSubLevel() 0584 } 0585 0586 Bar { 0587 id: bar 0588 level: items.currentLevel + 1 0589 content: BarEnumContent { value: help | home | level | hint } 0590 onHelpClicked: { 0591 displayDialog(dialogHelp) 0592 } 0593 z: introMessage.z 0594 onPreviousLevelClicked: Activity.previousLevel() 0595 onNextLevelClicked: Activity.nextLevel() 0596 onHomeClicked: home() 0597 onHintClicked: { 0598 if(!introMessage.visible && items.mouseEnabled) { 0599 if(items.memoryMode == false) { 0600 trainAnimationTimer.stop() 0601 animateFlow.stop(); 0602 listModel.clear(); 0603 for(var index = 0; index < Activity.backupListModel.length; index++) { 0604 Activity.addWagon(Activity.backupListModel[index], index); 0605 } 0606 items.memoryMode = true; 0607 okButton.visible = true; 0608 items.controlsEnabled = true; 0609 } else { 0610 Activity.restoreLevel(); 0611 okButton.visible = false; 0612 items.controlsEnabled = false; 0613 } 0614 } 0615 } 0616 } 0617 0618 Bonus { 0619 id: bonus 0620 onWin: Activity.nextLevel() 0621 } 0622 } 0623 }