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 }