diff --git a/src/activities/tangram/CMakeLists.txt b/src/activities/tangram/CMakeLists.txt index 965440ec6..3f22eb6aa 100644 --- a/src/activities/tangram/CMakeLists.txt +++ b/src/activities/tangram/CMakeLists.txt @@ -1 +1 @@ -GCOMPRIS_ADD_RCC(activities/tangram *.qml *.svg *.js resource/*) +GCOMPRIS_ADD_RCC(activities/tangram *.qml *.svg *.js resource/*/*) diff --git a/src/activities/tangram/Tangram.qml b/src/activities/tangram/Tangram.qml index 0d891a944..8143637a6 100644 --- a/src/activities/tangram/Tangram.qml +++ b/src/activities/tangram/Tangram.qml @@ -1,308 +1,335 @@ /* GCompris - tangram.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Yves Combe / Philippe Banwarth (GTK+ version) * Bruno Coudoin (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "tangram.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Item { id: background anchors.fill: parent property bool horizontalLayout: background.width > background.height property int playX: (activity.width - playWidth) / 2 property int playY: (activity.height - playHeight) / 2 property int playWidth: horizontalLayout ? activity.height : activity.width property int playHeight: playWidth property double playRatio: playWidth / 1000 signal start signal stop /* In order to accept any screen ratio the play area is always a 1000x1000 * square and is centered in a big background image that is 2000x2000 */ Image { id: bg - source: Activity.url + "background.svg" + source: Activity.url + "traffic/background.svg" sourceSize.width: 2000 * ApplicationInfo.ratio sourceSize.height: 2000 * ApplicationInfo.ratio width: 2000 * background.playRatio height: width anchors.centerIn: parent } Rectangle { width: background.playWidth height: background.playHeight anchors.centerIn: parent border.width: 2 border.color: "black" color: "transparent" visible: true /* debug to see the play area */ } Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias modelListModel: modelList.model property alias userList: userList property alias userListModel: userList.model property Item selected + property var currentTans: Activity.dataset[bar.level - 1] } onStart: { Activity.start(items) } onStop: { Activity.stop() } + Image { + id: bgData + source: Activity.url + items.currentTans.bg + sourceSize.width: 1000 * background.playRatio + sourceSize.height: 1000 * background.playRatio + width: 1000 * background.playRatio + height: width + anchors.centerIn: parent + } + MouseArea { id: rotateArea anchors.fill: parent enabled: items.selected property double prevRotation: 0 onPositionChanged: { // Calc the angle touch / object center var rotation = Activity.getAngleOfLineBetweenTwoPoints( items.selected.x + items.selected.width / 2, items.selected.y + items.selected.height / 2, mouseX, mouseY) * (180 / Math.PI) if(prevRotation) { items.selected.rotation += rotation - prevRotation } prevRotation = rotation } onReleased: { prevRotation = 0 // Force a modulo 45 rotation items.selected.rotation = Math.floor((items.selected.rotation + 45 / 2) / 45) * 45 + background.checkWin() } } DropArea { id: dropableArea anchors.left: background.left anchors.bottom: background.bottom width: background.width height: background.height } Repeater { id: modelList + model: items.currentTans.pieces Image { id: tansModel - x: background.playX + background.playWidth * xRatio - y: background.playY + background.playHeight * yRatio - mirror: modelData[1] - rotation: modelData[4] - source: Activity.url + modelData[0] + '.svg' - sourceSize.width: 100 * background.playRatio - z: 0 - - property real xRatio: modelData[2] - property real yRatio: modelData[3] + x: background.playX + background.playWidth * modelData.x + y: background.playY + background.playHeight * modelData.y + mirror: modelData.flipping + rotation: modelData.rotation + source: Activity.url + modelData.img + sourceSize.width: modelData.width * background.playWidth + sourceSize.height: modelData.height * background.playWidth + z: index + opacity: 0.1 Colorize { anchors.fill: parent source: parent - hue: 0.5 - lightness: -0.2 - saturation: 0 + hue: 0 + lightness: -0.75 + saturation: 1 } } } GCText { id: text x: 100 - y: bar.y - 50 + y: 20 } Repeater { id: userList + model: items.currentTans.pieces Image { id: tans x: background.playX + background.playWidth * xRatio y: background.playY + background.playHeight * yRatio - mirror: modelData[1] - rotation: modelData[4] - source: Activity.url + modelData[0] + '.svg' - sourceSize.width: 100 * background.playRatio - z: 0 - - property real xRatio: modelData[2] - property real yRatio: modelData[3] + mirror: modelData.initFlipping + rotation: modelData.initRotation + source: Activity.url + modelData.img + sourceSize.width: modelData.width * background.playWidth + sourceSize.height: modelData.height * background.playWidth + z: 100 + index + + property real xRatio: modelData.initX + property real yRatio: modelData.initY property bool selected: false property int animDuration: 1000 // After a drag the [x, y] positions are adressed directly breaking our // binding. Call me to reset the binding. function restoreBindings() { x = Qt.binding(function() { return background.playX + background.playWidth * xRatio }) y = Qt.binding(function() { return background.playY + background.playHeight * yRatio }) } + function restoreZindex() { + z = 100 + index + } + function positionToTans() { return [ (x - background.playX) / background.playWidth, (y - background.playY) / background.playHeight ] } // Manage to return a base rotation as it was provided in the model function rotationToTans() { - var mod = Activity.pieceRules[modelData[0]][0] - var flipable = Activity.pieceRules[modelData[0]][1] - if(flipable || !mirror) + var mod = modelData.moduloRotation + if(modelData.flipable || !mirror) return rotation >= 0 ? rotation % mod : (360 + rotation) % mod else // It flipping but model is not flipping sensitive we have to rotate accordingly return rotation >= 0 ? (rotation - (mod - 90)) % mod : (360 + rotation - (mod - 90)) % mod } // Return all the positions as we got it from a tans definition function asTans() { - return [modelData[0], - Activity.pieceRules[modelData[0]][1] ? mirror : 0, - positionToTans()[0], positionToTans()[1], - rotationToTans()] + return { + 'img': modelData.img, + 'flipping': mirror, + 'x': positionToTans()[0], + 'y': positionToTans()[1], + 'rotation': rotationToTans() + } } Drag.active: dragArea.drag.active Drag.hotSpot.x : width / 2 Drag.hotSpot.y : height / 2 MouseArea { id: dragArea anchors.fill: parent drag.target: parent onPressed: { - parent.z = ++Activity.globalZ + parent.z = 200 + if(parent.selected) { + parent.selected = false + background.checkWin() + return + } if(items.selected) items.selected.selected = false items.selected = tans parent.selected = true - var win = Activity.check() - if(win) - text.text = "win" - else - text.text = "loose" + background.checkWin() + } + onDoubleClicked: { + if(modelData.flippable) + parent.mirror = !parent.mirror + background.checkWin() } - onDoubleClicked: parent.mirror = !parent.mirror onReleased: { tans.animDuration = 30 parent.Drag.drop() var posTans = positionToTans() var closest = Activity.getClosest(posTans) if(closest) { - console.log('closest found', closest[0], closest[1]) tans.xRatio = closest[0] tans.yRatio = closest[1] - tans.restoreBindings() } else { tans.xRatio = posTans[0] tans.yRatio = posTans[1] } - - var win = Activity.check() - if(win) - text.text = "win" - else - text.text = "loose" + tans.restoreBindings() + tans.restoreZindex() + background.checkWin() } } Colorize { id: color anchors.fill: parent source: parent hue: 0.6 lightness: -0.2 saturation: 0.5 opacity: parent.selected ? 1 : 0 } Behavior on x { PropertyAnimation { duration: tans.animDuration easing.type: Easing.InOutQuad } } Behavior on y { PropertyAnimation { duration: tans.animDuration easing.type: Easing.InOutQuad } } } // Return the tans model of all the user tans function asTans() { var tans = [] for(var i = 0; i < userList.count; i++) { tans.push(userList.itemAt(i).asTans()) } return tans } } + function checkWin() { + var win = Activity.check() + if(win) + text.text = "win" + else + text.text = "loose" + } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/tangram/resource/traffic/back_road.svg b/src/activities/tangram/resource/traffic/back_road.svg new file mode 100644 index 000000000..a922f17a9 --- /dev/null +++ b/src/activities/tangram/resource/traffic/back_road.svg @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/tangram/resource/background.svg b/src/activities/tangram/resource/traffic/background.svg similarity index 100% rename from src/activities/tangram/resource/background.svg rename to src/activities/tangram/resource/traffic/background.svg diff --git a/src/activities/tangram/resource/traffic/cabin.svg b/src/activities/tangram/resource/traffic/cabin.svg new file mode 100644 index 000000000..59c3bb5b1 --- /dev/null +++ b/src/activities/tangram/resource/traffic/cabin.svg @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/activities/tangram/resource/traffic/container.svg b/src/activities/tangram/resource/traffic/container.svg new file mode 100644 index 000000000..5b576d022 --- /dev/null +++ b/src/activities/tangram/resource/traffic/container.svg @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/activities/tangram/resource/traffic/engine.svg b/src/activities/tangram/resource/traffic/engine.svg new file mode 100644 index 000000000..e4e2b6618 --- /dev/null +++ b/src/activities/tangram/resource/traffic/engine.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/src/activities/tangram/resource/traffic/front_road.svg b/src/activities/tangram/resource/traffic/front_road.svg new file mode 100644 index 000000000..3e62ad74f --- /dev/null +++ b/src/activities/tangram/resource/traffic/front_road.svg @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/tangram/resource/traffic/traffic_bg.svg b/src/activities/tangram/resource/traffic/traffic_bg.svg new file mode 100644 index 000000000..937629249 --- /dev/null +++ b/src/activities/tangram/resource/traffic/traffic_bg.svg @@ -0,0 +1,2695 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/tangram/tangram.js b/src/activities/tangram/tangram.js index b9ee1bef7..23b5c3365 100644 --- a/src/activities/tangram/tangram.js +++ b/src/activities/tangram/tangram.js @@ -1,203 +1,306 @@ /* GCompris - tangram.js * * Copyright (C) 2015 YOUR NAME * * Authors: * (GTK+ version) * "YOUR NAME" (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ .pragma library .import QtQuick 2.0 as Quick var url = "qrc:/gcompris/src/activities/tangram/resource/" /* dataset format for each piece we have: - piece file name - flipping (0 or 1) - x - y - rotation */ + + +//back_road.svg cabin.svg container.svg front_road.svg var dataset = [ - [ - ['p2', 0, 0.1, 0.1, 0], - ['p4', 0, 0.2, 0.1, 0], - ['p1', 0, 0.3, 0.1, 0], - ['p3', 0, 0.4, 0.1, 0], - ['p4', 0, 0.1, 0.2, 0], - ['p0', 0, 0.2, 0.2, 0], - ['p0', 0, 0.3, 0.2, 0] - ], - [ - ['p3', 1, 1.555033, 3.667909, 45], - ['p2', 0, 3.055033, 4.667909, 0], - ['p4', 0, 2.221700, 4.501242, 270], - ['p4', 0, 3.888367, 4.834575, 90], - ['p0', 0, 3.083629, 2.753695, 45], - ['p0', 0, 1.221700, 5.834575, 0], - ['p1', 0, 5.221700, 5.167909, 45] - ], - [ - ['p2', 0, 3.450292, 1.017544, 45], - ['p4', 0, 3.450292, 2.196055, 45], - ['p1', 0, 3.450292, 3.098424, 45], - ['p3', 0, 3.096739, 5.199524, 90], - ['p4', 0, 4.157399, 5.081673, 45], - ['p0', 0, 3.450292, 6.495887, 45], - ['p0', 0, 3.450292, 4.374566, 45] - ] -] - -// This is the list of tans provided to the use with their initial positions -// this is the same formatting as dataset. -var defaultTan = [ - ['p2', 0, 0.1, 0.1, 0], - ['p4', 0, 0.2, 0.1, 0], - ['p1', 0, 0.3, 0.1, 0], - ['p3', 0, 0.4, 0.1, 0], - ['p4', 0, 0.1, 0.2, 0], - ['p0', 0, 0.2, 0.2, 0], - ['p0', 0, 0.3, 0.2, 0] + // Level 1 + { + 'bg': 'traffic/traffic_bg.svg', + 'pieces': [ + { + 'img': 'traffic/engine.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.744, + 'y': 0.519, + 'width': 0.233, + 'height': 0.109, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.75, + 'initY': 0.7, + 'initRotation': 0, + 'initFlipping': 0 + }, + { + 'img': 'traffic/cabin.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.744, + 'y': 0.346, + 'width': 0.207, + 'height': 0.178, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.05, + 'initY': 0.7, + 'initRotation': 0, + 'initFlipping': 0 + }, + { + 'img': 'traffic/container.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.020, + 'y': 0.354, + 'width': 0.676, + 'height': 0.271, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.0, + 'initY': 0.0, + 'initRotation': 0, + 'initFlipping': 0 + }, + { + 'img': 'traffic/back_road.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.082, + 'y': 0.587, + 'width': 0.198, + 'height': 0.092, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.7, + 'initY': 0.1, + 'initRotation': 0, + 'initFlipping': 0 + }, + { + 'img': 'traffic/front_road.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.565, + 'y': 0.557, + 'width': 0.403, + 'height': 0.121, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.3, + 'initY': 0.7, + 'initRotation': 0, + 'initFlipping': 0 + } + ] + }, + // Level 2 + { + 'bg': 'traffic/traffic_bg.svg', + 'pieces': [ + { + 'img': 'traffic/engine.svg', + 'flippable': 1, + 'flipping': 0, + 'x': 0.744, + 'y': 0.519, + 'width': 0.233, + 'height': 0.109, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.75, + 'initY': 0.7, + 'initRotation': 0, + 'initFlipping': 1 + }, + { + 'img': 'traffic/cabin.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.744, + 'y': 0.346, + 'width': 0.207, + 'height': 0.178, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.05, + 'initY': 0.7, + 'initRotation': 0, + 'initFlipping': 0 + }, + { + 'img': 'traffic/container.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.020, + 'y': 0.354, + 'width': 0.676, + 'height': 0.271, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.0, + 'initY': 0.0, + 'initRotation': 0, + 'initFlipping': 0 + }, + { + 'img': 'traffic/back_road.svg', + 'flippable': 0, + 'flipping': 0, + 'x': 0.082, + 'y': 0.587, + 'width': 0.198, + 'height': 0.092, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.7, + 'initY': 0.1, + 'initRotation': 270, + 'initFlipping': 0 + }, + { + 'img': 'traffic/front_road.svg', + 'flippable': 1, + 'flipping': 0, + 'x': 0.565, + 'y': 0.557, + 'width': 0.403, + 'height': 0.121, + 'rotation': 0, + 'moduloRotation': 360, + 'initX': 0.3, + 'initY': 0.7, + 'initRotation': 90, + 'initFlipping': 1 + } + ] + } ] -// Give specific piece rules -// For each piece we provide: -// - the modulo rotation. What angle is needed to turn to see the item in the same position -// - the flipping support (true / false) -var pieceRules = { - 'p0': [360, false], - 'p1': [360, false], - 'p2': [90, false], - 'p3': [180, true], - 'p4': [360, false] -} -var tan // The current user tangran positions var currentLevel = 0 var numberOfLevel = dataset.length var items -// We keep a globalZ across all items. It is increased on each -// item selection to put it on top -var globalZ = 0 - - function start(items_) { items = items_ currentLevel = 0 initLevel() } function stop() { } function initLevel() { - globalZ = 0 items.bar.level = currentLevel + 1 - items.modelListModel = dataset[items.bar.level - 1] - items.userListModel = [] - items.userListModel = defaultTan.slice(); } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } // Determines the angle of a straight line drawn between point one and two. // The number returned, which is a float in radian, // tells us how much we have to rotate a horizontal line clockwise // for it to match the line between the two points. function getAngleOfLineBetweenTwoPoints(x1, y1, x2, y2) { var xDiff = x2 - x1; var yDiff = y2 - y1; return Math.atan2(yDiff, xDiff); } function getDistance(ix, iy, jx, jy) { return Math.sqrt(Math.pow((ix - jx), 2) + Math.pow((iy - jy), 2)) } function dumpTans(tans) { console.log("== tans ==") for(var i = 0; i < tans.length; i++) { - console.log(tans[i][0], tans[i][1], tans[i][2], tans[i][3], tans[i][4]) + console.log(tans[i].img, tans[i].x, tans[i].y, tans[i].rotation, tans[i].flipping) } } /* Returns the [x, y] coordinate of the closest point */ function getClosest(point) { - console.log('getClosest', point[0], point[1]) - var nbpiece = dataset[items.bar.level - 1].length + var nbpiece = items.currentTans.pieces.length var closestItem var closestDist = 1 for(var i = 0; i < nbpiece; i++) { - var p1 = dataset[items.bar.level - 1][i] - var dist = getDistance(p1[2], p1[3], point[0], point[1]) + var p1 = items.currentTans.pieces[i] + var dist = getDistance(p1.x, p1.y, point[0], point[1]) if(dist < closestDist) { closestDist = dist closestItem = p1 } } - console.log(' closestDist', closestDist, closestItem[2], closestItem[3]) if(closestDist < 0.1) - return [closestItem[2], closestItem[3]] + return [closestItem.x, closestItem.y] return } function check() { - var nbpiece = dataset[items.bar.level - 1].length + var nbpiece = items.currentTans.pieces.length var userTans = items.userList.asTans() - dumpTans(userTans) + //dumpTans(userTans) console.log('== check ==') for(var i = 0; i < nbpiece; i++) { - var p1 = dataset[items.bar.level - 1][i] + var p1 = items.currentTans.pieces[i] var ok = false for(var j = 0; j < nbpiece; j++) { var p2 = userTans[j] // Check type distance and rotation are close enough - if(p1[0] === p2[0] && // Type - p1[1] == p2[1] && // Flipping - getDistance(p1[2], p1[3], p2[2], p2[3]) < 0.01 && // X, Y - p1[4] === p2[4] /* Rotation */ ) { + if(p1.img === p2.img && + p1.flipping == p2.flipping && + getDistance(p1.x, p1.y, p2.x, p2.y) < 0.01 && + p1.rotation === p2.rotation ) { ok = true } - if(p1[0] === p2[0]) + if(p1.img === p2.img) if(ok) - console.log("piece ", p1[0], "OK") + console.log("piece ", p1.img, "OK") else - console.log("piece ", p1[0], getDistance(p1[2], p1[3], p2[2], p2[3]), 'rot exp/got', p1[4], '/', p2[4], "NOK") + console.log("piece ", p1.img, getDistance(p1.x, p1.y, p2.x, p2.y), 'rot exp/got', p1.rotation, '/', p2.rotation, "NOK") } if(!ok) return false } return true }