Warning, /education/gcompris/src/activities/balancebox/Balancebox.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - balancebox.qml
0002  *
0003  * SPDX-FileCopyrightText: 2014-2016 Holger Kaelberer <holger.k@elberer.de>
0004  *
0005  * Authors:
0006  *   Holger Kaelberer <holger.k@elberer.de>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 import QtQuick 2.12
0011 import QtQuick.Window 2.12
0012 import QtSensors 5.0
0013 import QtGraphicalEffects 1.0
0014 import GCompris 1.0
0015 import Box2D 2.0
0016 
0017 
0018 import "../../core"
0019 import "editor/"
0020 import "balancebox.js" as Activity
0021 import "qrc:/gcompris/src/core/core.js" as Core
0022 
0023 ActivityBase {
0024     id: activity
0025 
0026     property string mode: "play"  // "play" or "test"
0027     property string levelSet: "builtin"   // "builtin" or "user"
0028     // When the user launches the activity in "user" mode by default(due to previously save config mode) for the first time after updating GCompris, default user file must be loaded as they must be having created levels in it.
0029     // From next time onwards, the saved file path is loaded. Refer to line 567.
0030     property string loadedFilePath: Activity.builtinFile
0031     property var testLevel
0032     property bool inForeground: false   // to avoid unneeded reconfigurations
0033 
0034     property bool alwaysStart: true     // enforce start signal for editor-to-testing- and returning from config-transition
0035     property bool needRestart: true
0036 
0037     onStart: {
0038         inForeground = true;
0039         focus = true;
0040     }
0041     onStop: inForeground = false;
0042 
0043     Keys.onPressed: Activity.processKeyPress(event.key)
0044     Keys.onReleased: Activity.processKeyRelease(event.key)
0045 
0046     pageComponent: Image {
0047         id: background
0048         source: "qrc:/gcompris/src/activities/maze/resource/maze_bg.svg"
0049         sourceSize.width: parent.width
0050         anchors.fill: parent
0051         signal start
0052         signal stop
0053 
0054         function startEditor() {
0055             editorLoader.active = true;
0056             if (activity.mode == "test")
0057                 displayDialogs([dialogActivityConfig, editorLoader.item]);
0058             else
0059                 displayDialog(editorLoader.item);
0060         }
0061 
0062         function handleBackEvent() {
0063             if (activity.mode == "test") {
0064                 startEditor();
0065                 return true;
0066             } else
0067                 return false;
0068         }
0069 
0070         function noUserLevelSelected() {
0071             Core.showMessageDialog(activity,
0072                                   qsTr("You selected the user-defined level set, but you have not yet loaded any user level!") + "<br/>" +
0073                                   Activity.createLevelsMsg,
0074                                   qsTr("Ok"), null,
0075                                   "", null,
0076                                   null);
0077         }
0078 
0079         Keys.onEscapePressed: event.accepted = handleBackEvent();
0080 
0081         Keys.onReleased: {
0082             if (event.key === Qt.Key_Back)
0083                 event.accepted = handleBackEvent();
0084         }
0085 
0086         Component.onCompleted: {
0087             dialogActivityConfig.initialize()
0088             activity.start.connect(start)
0089             activity.stop.connect(stop)
0090             items.dpi = Math.round(Screen.pixelDensity*25.4);
0091 
0092         }
0093 
0094         onStart: {
0095             if (activity.needRestart) {
0096                 Activity.start(items);
0097                 activity.needRestart = false;
0098             } else
0099                 Activity.initLevel();
0100             if(activity.levelSet === "user" && activity.loadedFilePath === Activity.builtinFile) {
0101                 noUserLevelSelected();
0102             }
0103         }
0104 
0105         onStop: {
0106             Activity.stop();
0107             activity.needRestart = true;
0108         }
0109 
0110         QtObject {
0111             id: items
0112             property string mode: activity.mode
0113             property string levelSet: activity.levelSet
0114             property string filePath: activity.loadedFilePath
0115             property var testLevel: activity.testLevel
0116             property Item main: activity.main
0117             property alias background: background
0118             property int currentLevel: activity.currentLevel
0119             property alias bonus: bonus
0120             property alias tilt: tilt
0121             property alias timer: timer
0122             property alias ball: ball
0123             property alias file: file
0124             property int ballSize: cellSize - 2*wallSize
0125             property alias mapWrapper: mapWrapper
0126             property int cellSize: mapWrapper.length / Math.min(mapWrapper.rows, mapWrapper.columns)
0127             property int wallSize: cellSize / 5
0128             property var world: physicsWorld
0129             property alias keyboardTimer: keyboardTimer
0130             property var ballType: Fixture.Category1
0131             property var wallType: Fixture.Category2
0132             property var holeType: Fixture.Category3
0133             property var goalType: Fixture.Category4
0134             property var buttonType: Fixture.Category5
0135             property alias parser: parser
0136             property double dpi
0137             property GCSfx audioEffects: activity.audioEffects
0138             property Loading loading: activity.loading
0139         }
0140 
0141         Loader {
0142             id: editorLoader
0143             active: false
0144             sourceComponent: BalanceboxEditor {
0145                 id: editor
0146                 visible: true
0147                 testBox: activity
0148 
0149                 onClose: activity.home()
0150 
0151             }
0152         }
0153 
0154         JsonParser {
0155             id: parser
0156             onError: console.error("Balancebox: Error parsing JSON: " + msg);
0157         }
0158 
0159         // color overlay to better see the map outline
0160         Rectangle {
0161             anchors.fill: parent
0162             color: "#40FFFFFF"
0163         }
0164 
0165         Rectangle {
0166             id: mapWrapper
0167 
0168             property double margin: 20
0169             property int barHeight: ApplicationSettings.isBarHidden ? 0 : bar.height
0170             property int columns: 1
0171             property int rows: 1
0172             property double length: Math.min(background.height -
0173                     mapWrapper.barHeight - 2 * mapWrapper.margin, background.width - 2 * mapWrapper.margin);
0174 
0175             color: "#E3DEDB"
0176             width: length
0177             height: length
0178             anchors.top: background.top
0179             anchors.topMargin: mapWrapper.margin
0180             anchors.horizontalCenter: background.horizontalCenter
0181 
0182             onWidthChanged: if (activity.inForeground && pageView.currentItem === activity)
0183                 resizeTimer.restart()
0184 
0185             onHeightChanged: if (activity.inForeground && pageView.currentItem === activity)
0186                 resizeTimer.restart()
0187 
0188             transform: [
0189                 Rotation {
0190                     origin.x: mapWrapper.width / 2
0191                     origin.y: mapWrapper.height / 2
0192                     axis { x: 1; y: 0; z: 0 }
0193                     angle: ApplicationInfo.isMobile ? 0 : -items.tilt.xRotation
0194                 },
0195                 Rotation {
0196                     origin.x: mapWrapper.width / 2
0197                     origin.y: mapWrapper.height / 2
0198                     axis { x: 0; y: 1; z: 0 }
0199                     angle: ApplicationInfo.isMobile ? 0 : items.tilt.yRotation
0200                 }
0201             ]
0202 
0203             Timer {
0204                 id: resizeTimer
0205                 interval: 100
0206                 onTriggered: Activity.reconfigureScene()
0207             }
0208 
0209             // right:
0210             Wall {
0211                 id: rightWall
0212 
0213                 width: items.wallSize
0214                 height: parent.height + items.wallSize
0215 
0216                 anchors.left: mapWrapper.right
0217                 anchors.leftMargin: - items.wallSize/2
0218                 anchors.top: parent.top
0219                 anchors.topMargin: -items.wallSize/2
0220 
0221                 shadow: false
0222                 shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize)
0223                 shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize)
0224             }
0225             // bottom:
0226             Wall {
0227                 id: bottomWall
0228 
0229                 width: parent.width + items.wallSize
0230                 height: items.wallSize
0231 
0232                 anchors.left: mapWrapper.left
0233                 anchors.leftMargin: - items.wallSize/2
0234                 anchors.top: parent.bottom
0235                 anchors.topMargin: -items.wallSize/2
0236 
0237                 shadow: false
0238                 shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize)
0239                 shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize)
0240             }
0241             // top:
0242             Wall {
0243                 id: topWall
0244 
0245                 width: parent.width + items.wallSize
0246                 height: items.wallSize
0247 
0248                 anchors.left: mapWrapper.left
0249                 anchors.leftMargin: - items.wallSize/2
0250                 anchors.top: parent.top
0251                 anchors.topMargin: -items.wallSize/2
0252                 shadow: false
0253                 shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize)
0254                 shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize)
0255             }
0256             // left:
0257             Wall {
0258                 id: leftWall
0259 
0260                 width: items.wallSize
0261                 height: parent.height + items.wallSize
0262 
0263                 anchors.left: mapWrapper.left
0264                 anchors.leftMargin: - items.wallSize/2
0265                 anchors.top: parent.top
0266                 anchors.topMargin: -items.wallSize/2
0267                 shadow: false
0268                 shadowHorizontalOffset: Math.min(items.tilt.yRotation, items.wallSize)
0269                 shadowVerticalOffset: Math.min(items.tilt.xRotation, items.wallSize)
0270             }
0271 
0272             BalanceItem {
0273                 id: ball
0274                 world: physicsWorld
0275                 imageSource: Activity.baseUrl + "/ball.svg"
0276                 visible: false
0277                 scale: 1.0
0278                 width: items.ballSize
0279                 height: items.ballSize
0280                 z: 3  // above other BalanceItems
0281                 categories: items.ballType
0282                 collidesWith: items.wallType | items.holeType | items.goalType
0283                               | items.buttonType
0284                 density: 1
0285                 friction: Activity.friction
0286                 linearDamping: Activity.friction
0287                 restitution: Activity.restitution
0288                 bodyType: Body.Dynamic
0289                 shadow: true
0290                 shadowHorizontalOffset: (items.tilt.yRotation > 0) ? Math.min(items.tilt.yRotation, items.wallSize) : Math.max(items.tilt.yRotation, -items.wallSize)
0291                 shadowVerticalOffset: (items.tilt.xRotation > 0) ? Math.min(items.tilt.xRotation, items.wallSize) : Math.max(items.tilt.xRotation, -items.wallSize)
0292 
0293                 Behavior on scale {
0294                     NumberAnimation {
0295                         id: fallAnimation
0296                         duration: 1000
0297                     }
0298                 }
0299 
0300                 onBeginContact: {
0301                     if (other.categories !== items.wallType)
0302                         Activity.addBallContact(other);
0303                     else {
0304                         // sound-effect on each contact with a wall might be too annoying:
0305                         //items.audioEffects.stop();
0306                         //items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/brick.wav");
0307                     }
0308                 }
0309                 onEndContact: {
0310                     if (other.categories !== items.wallType)
0311                         Activity.removeBallContact(other);
0312                 }
0313             }
0314             World {
0315                 id: physicsWorld
0316 
0317                 gravity: Qt.point(0, 0)  // we calculate acceleration ourselves
0318 
0319                 pixelsPerMeter: Activity.box2dPpm // default: 32
0320                 timeStep: Activity.step/1000  // default: 1/60
0321 
0322             }
0323 
0324             DebugDraw {
0325                 id: debugDraw
0326                 world: physicsWorld
0327                 visible: Activity.debugDraw
0328                 z: 100
0329             }
0330 
0331         }
0332 
0333         Timer {
0334             id: timer
0335             interval: Activity.step;
0336             running: false;
0337             repeat: true
0338             onTriggered: Activity.moveBall()
0339         }
0340 
0341         Item {
0342             id: tilt
0343 
0344             property double xRotation: 0
0345             property double yRotation: 0
0346 
0347             property bool swapAxes: false
0348             property bool invertX: false
0349             property bool invertY: false
0350 
0351             onXRotationChanged: {
0352                 if (xRotation > 90)
0353                     xRotation = 90;
0354                 else if (xRotation < -90)
0355                     xRotation = -90;
0356             }
0357             onYRotationChanged: {
0358                 if (yRotation > 90)
0359                     yRotation = 90;
0360                 else if (yRotation < -90)
0361                     yRotation = -90;
0362             }
0363 
0364             TiltSensor {
0365                 id: tiltSensor
0366                 active: ApplicationInfo.isMobile ? true : false
0367 
0368                 onReadingChanged: {
0369                     if (!tilt.swapAxes) {
0370                         tilt.xRotation = tilt.invertX ? -reading.xRotation : reading.xRotation;
0371                         tilt.yRotation = tilt.invertY ? -reading.yRotation : reading.yRotation;
0372                     } else {
0373                         tilt.xRotation = tilt.invertX ? -reading.yRotation : reading.yRotation;
0374                         tilt.yRotation = tilt.invertY ? -reading.xRotation : reading.xRotation;
0375                     }
0376 
0377                     tiltText.text = "X/Y Rotation: " 
0378                         + tiltSensor.reading.xRotation 
0379                         + "/" + tiltSensor.reading.yRotation
0380                 }
0381             }
0382 
0383         }
0384 
0385         Item {
0386             id: textWrapper
0387             anchors.left: parent.left
0388             anchors.top: parent.top
0389             width: parent.width
0390             height: parent.height / 3
0391             visible: Activity.debugDraw
0392 
0393             Text {
0394                 id: tiltText
0395                 anchors.left: parent.left
0396                 anchors.top: parent.top
0397                 text: "X/Y Rotation: " + tilt.xRotation + "/" + tilt.yRotation
0398                 font.pointSize: 12
0399             }
0400 
0401             Text {
0402                 id: posText
0403                 anchors.left: parent.left
0404                 anchors.top: tiltText.bottom
0405                 text: "X/Y = " + ball.x + "/" + ball.y
0406                 font.pointSize: 12
0407             }
0408         }
0409 
0410         MultiPointTouchArea {
0411             anchors.fill: parent
0412             touchPoints: [ TouchPoint { id: point1 } ]
0413             property real startX
0414             property real startY
0415             property int offset: 30
0416 
0417             function reset() {
0418                 startX = point1.x
0419                 startY = point1.y
0420             }
0421 
0422             onPressed: {
0423                 reset()
0424             }
0425 
0426             onUpdated: {
0427                 var moveX = point1.x - startX
0428                 var moveY = point1.y - startY
0429                 // Find the direction with the most move
0430                 if(Math.abs(moveX) * ApplicationInfo.ratio > offset &&
0431                    Math.abs(moveX) > Math.abs(moveY)) {
0432                     if(moveX > offset * ApplicationInfo.ratio) {
0433                         Activity.processKeyPress(Qt.Key_Right)
0434                         reset()
0435                     } else if(moveX < -offset * ApplicationInfo.ratio) {
0436                         Activity.processKeyPress(Qt.Key_Left)
0437                         reset()
0438                     }
0439                 } else if(Math.abs(moveY) * ApplicationInfo.ratio > offset &&
0440                           Math.abs(moveX) < Math.abs(moveY)) {
0441                     if(moveY > offset * ApplicationInfo.ratio) {
0442                         Activity.processKeyPress(Qt.Key_Down)
0443                         reset()
0444                     } else if(moveY < -offset * ApplicationInfo.ratio) {
0445                         Activity.processKeyPress(Qt.Key_Up)
0446                         reset()
0447                     }
0448                 }
0449             }
0450             onReleased: {
0451                 Activity.keyboardIsTilting = false
0452             }
0453         }
0454 
0455         DialogHelp {
0456             id: dialogHelp
0457             onClose: home()
0458         }
0459 
0460         Bar {
0461             id: bar
0462             level: items.currentLevel + 1
0463             content: BarEnumContent {
0464                 value: activity.mode == "play"
0465                            ? (help | home | level | activityConfig )
0466                            : ( help | home )
0467             }
0468             onHelpClicked: {
0469                 // stop everything or the ball keeps moving while we're away:
0470                 items.timer.stop();
0471                 displayDialog(dialogHelp);
0472             }
0473             onPreviousLevelClicked: if (!Activity.finishRunning)
0474                                         Activity.previousLevel()
0475             onNextLevelClicked: if (!Activity.finishRunning)
0476                                     Activity.nextLevel()
0477             onHomeClicked: {
0478                 if (activity.mode == "test")
0479                     background.startEditor();
0480                 else
0481                     activity.home()
0482             }
0483             onActivityConfigClicked: {
0484                 items.timer.stop();
0485                 displayDialog(dialogActivityConfig)
0486             }
0487         }
0488 
0489         Bonus {
0490             id: bonus
0491 
0492             looseSound: "qrc:/gcompris/src/core/resource/sounds/crash.wav"
0493             Component.onCompleted: {
0494                 win.connect(Activity.nextLevel);
0495                 loose.connect(Activity.initLevel);
0496             }
0497         }
0498 
0499         Timer {
0500             id: keyboardTimer
0501             interval: Activity.keyboardTimeStep;
0502             running: false
0503             repeat: false
0504             onTriggered: Activity.keyboardHandler()
0505         }
0506 
0507         GCCreationHandler {
0508             id: creationHandler
0509             readonly property bool isEditorActive: editorLoader.active && editorLoader.item.visible
0510             onFileLoaded: {
0511                 if(!isEditorActive) {
0512                     activity.loadedFilePath = filePath
0513                 }
0514                 else
0515                     editorLoader.item.filename = filePath
0516                 close()
0517             }
0518             parent: isEditorActive ? editorLoader.item : dialogActivityConfig
0519         }
0520 
0521         File {
0522             id: file
0523         }
0524 
0525         DialogChooseLevel {
0526             id: dialogActivityConfig
0527             currentActivity: activity.activityInfo
0528             onClose: {
0529                 home()
0530             }
0531             onLoadData: {
0532                 if(activityData && activityData["levels"]) {
0533                     activity.levelSet = activityData["levels"];
0534                     if(activityData['filePath'])
0535                         activity.loadedFilePath = activityData["filePath"];
0536                 }
0537             }
0538             onStartActivity: {
0539                 background.stop();
0540                 background.start();
0541             }
0542         }
0543     }
0544 }