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 }