Commit c5018c31 authored by Holger Kaelberer's avatar Holger Kaelberer
Browse files

balancebox: initial version of level editor

parent d0518041
/* GCompris - BalanceItem.qml
*
* Copyright (C) 2014 Holger Kaelberer
* Copyright (C) 2014, 2015 Holger Kaelberer
*
* Authors:
* Holger Kaelberer <holger.k@elberer.de>
......
/* GCompris - balance.qml
*
* Copyright (C) 2014 Holger Kaelberer <holger.k@elberer.de>
* Copyright (C) 2014,2015 Holger Kaelberer <holger.k@elberer.de>
*
* Authors:
* Holger Kaelberer <holger.k@elberer.de>
......@@ -24,8 +24,11 @@ import QtSensors 5.0
import QtGraphicalEffects 1.0
import GCompris 1.0
import Box2D 2.0
import QtQuick.Controls 1.0
import "../../core"
import "editor/"
import "balancebox.js" as Activity
ActivityBase {
......@@ -46,13 +49,39 @@ ActivityBase {
signal start
signal stop
Keys.onPressed: {
if (event.key == Qt.Key_A) {
startEditor()
}
}
function startEditor() {
console.log("XXX: launching editor");
editor.visible = true;
displayDialog(editor);
}
Button {
id: editButton
anchors.top: parent.top
anchors.right: parent.right
width: 80
height: 30
text: "Editor"
onClicked: {
startEditor();
}
}
Component.onCompleted: {
activity.start.connect(start)
activity.stop.connect(stop)
items.dpi = Math.round(Screen.pixelDensity*25.4);
}
onStart: { Activity.start(items) }
onStart: Activity.start(items)
onStop: { Activity.stop() }
QtObject {
......@@ -78,6 +107,17 @@ ActivityBase {
property double dpi
}
BalanceboxEditor {
id: editor
visible: false
onClose: {
console.log("XXX editor.onClose");
activity.home()
}
}
JsonParser {
id: parser
onError: console.error("Balancebox: Error parsing JSON: " + msg);
......
GCOMPRIS_ADD_RCC(activities/balancebox *.qml *.svg *.js resource/*)
GCOMPRIS_ADD_RCC(activities/balancebox *.qml *.svg *.js resource/* editor/*)
/* GCompris - balance.js
/* GCompris - balancebox.js
*
* Copyright (C) 2014 Holger Kaelberer <holger.k@elberer.de>
* Copyright (C) 2014,2015 Holger Kaelberer <holger.k@elberer.de>
*
* Authors:
* Holger Kaelberer <holger.k@elberer.de>
......@@ -30,7 +30,9 @@
.import GCompris 1.0 as GCompris
.import Box2D 2.0 as Box2D
/** Level description format:
/**
* Level description format:
*
* Example:
* [ { "level": 1,
* "map": [ [ 0x0000, 0x0308, ... ],
......@@ -45,7 +47,7 @@
*
* "level": Number of the level.
* "map": Definition of the map inside the balancebox.
* The map is a 2-dimensional array of map fields. A map field is
* The map is a 2-dimensional array of map cells. A cell is
* described by a bitmask of 16 bit with the lower 8bit defining walls,
* objects, etc. (cf. below) and the higher 8 bit defining the order of
* buttons present on the map. The values of the buttons are described
......@@ -55,14 +57,16 @@
* need to be pressed by the ball is defined in the higher 8 bits of
* the map fields.
*/
var EMPTY = 0x0000;
var NORTH = 0x0001;
var EAST = 0x0002;
var SOUTH = 0x0004;
var WEST = 0x0008;
var START = 0x0010;
var GOAL = 0x0020;
var HOLE = 0x0040;
var EMPTY = 0x0000;
var NORTH = 0x0001;
var EAST = 0x0002;
var SOUTH = 0x0004;
var WEST = 0x0008;
// all the following are mutually exclusive:
var START = 0x0010;
var GOAL = 0x0020;
var HOLE = 0x0040;
var CONTACT = 0x0080;
var dataset = null;
......@@ -499,3 +503,147 @@ function previousLevel() {
}
initLevel();
}
var TOOL_H_WALL = SOUTH
var TOOL_V_WALL = EAST
var TOOL_HOLE = HOLE
var TOOL_CONTACT = CONTACT
var TOOL_GOAL = GOAL
var TOOL_BALL = START
function initEditor(props)
{
console.log("XXX init editor " + props + " map: " + map);
props.mapModel.clear();
for (var row = 0; row < map.length; row++) {
for (var col = 0; col < map[row].length; col++) {
var contactValue = "";
var value = parseInt(map[row][col]); // always enforce number
var orderNum = (value & 0xFF00) >> 8;
if (orderNum > 0 && level.targets[orderNum - 1] === undefined) {
console.error("Invalid level: orderNum " + orderNum
+ " without target value!");
} else if (orderNum > 0) {
if (orderNum > props.lastOrderNum)
props.lastOrderNum = orderNum;
contactValue = Number(level.targets[orderNum-1]).toString();
if (contactValue > parseInt(props.contactValue) + 1)
props.contactValue = Number(contactValue + 1).toString();
}
props.mapModel.append({
"row": row,
"col": col,
"value": value,
"orn": orderNum,
"contactValue": orderNum > 0 ? contactValue : ""
});
if (value & GOAL) {
if (props.lastGoalIndex > -1) {
console.error("Invalid level: multiple goal locations: row/col="
+ row + "/" + col);
return;
}
props.lastGoalIndex = row * map.length + col;
}
if (value & START) {
if (props.lastBallIndex > -1) {
console.error("Invalid level: multiple start locations: row/col="
+ row + "/" + col);
return;
}
props.lastBallIndex = row * map.length + col;
}
}
}
}
function dec2hex(i) {
return (i+0x10000).toString(16).substr(-4).toUpperCase();
}
function modelToLevel(props)
{
map = new Array();
var targets = new Array();
for (var i = 0; i < props.mapModel.count; i++) {
var row = Math.floor(i / props.columns);
var col = i % props.columns;
if (col === 0) {
map[row] = new Array();
}
var obj = props.mapModel.get(i);
var value = obj.value;
if (obj.orn > 0) {
value |= (obj.orn << 8);
targets[obj.orn-1] = obj.contactValue;
}
map[row][col] = "0x" + dec2hex(value);
}
var level = {
level: "0",
map: map,
targets: targets
}
console.log("XXX level: " + JSON.stringify(level) + " - " + map);
}
function modifyMap(props, row, col)
{
//console.log("XXX modify map currentTool='" + props.currentTool + "'");
var modelIndex = row * map.length + col;
var obj = props.mapModel.get(modelIndex);
var newValue = obj.value;
// special treatment for mutually exclusive ones:
if (props.currentTool === TOOL_HOLE
|| props.currentTool === TOOL_GOAL
|| props.currentTool === TOOL_CONTACT
|| props.currentTool === TOOL_BALL) {
// helper:
var MUTEX_MASK = (START | GOAL | HOLE | CONTACT) ^ props.currentTool;
newValue &= ~MUTEX_MASK;
}
// special treatment for singletons:
if (props.currentTool === TOOL_GOAL) {
console.log("GGGoal " + (obj.value & TOOL_GOAL));
if ((obj.value & TOOL_GOAL) === 0) {
// setting a new one
if (props.lastGoalIndex > -1) {
// clear last one first:
props.mapModel.setProperty(props.lastGoalIndex, "value",
props.mapModel.get(props.lastGoalIndex).value &
(~TOOL_GOAL));
}
// now memorize the new one:
props.lastGoalIndex = modelIndex;
}
} else
if (props.currentTool === TOOL_BALL) {
if ((obj.value & TOOL_BALL) === 0) {
// setting a new one
if (props.lastBallIndex > -1)
// clear last one first:
props.mapModel.setProperty(props.lastBallIndex, "value",
props.mapModel.get(props.lastBallIndex).value & (~TOOL_BALL));
// now memorize the new one:
props.lastBallIndex = modelIndex;
}
}
// special treatment for contacts:
if (props.currentTool === TOOL_CONTACT) {
props.mapModel.setProperty(row * map.length + col, "orn", ++props.lastOrderNum);
props.mapModel.setProperty(row * map.length + col, "contactValue", props.contactValue);
var newContact = Number(Number(props.contactValue) + 1).toString()
props.contactValue = newContact;
}
// update value by current tool bit:
newValue ^= props.currentTool;
console.log("XXX value=" + obj.value + " typeof=" + typeof(obj.value) + " newValue=" + newValue + " typeof=" + typeof(newValue));
props.mapModel.setProperty(modelIndex, "value", newValue);
}
/* GCompris - BalanceboxEditor.qml
*
* Copyright (C) 2015 Holger Kaelberer <holger.k@elberer.de>
*
* Authors:
* Holger Kaelberer <holger.k@elberer.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.1
import QtGraphicalEffects 1.0
import GCompris 1.0
import QtQuick.Controls 1.0
import "../../../core"
import ".."
import "../balancebox.js" as Activity
Item {
id: editor
property bool isDialog: true
property ActivityBase currentActivity
/**
* Emitted when the config dialog has been closed.
*/
signal close
/**
* Emitted when the config dialog has been started.
*/
signal start
signal stop
/* Keys.enabled: ApplicationInfo.isMobile ? false : true
Keys.onPressed: Activity.processKeyPress(event.key)
Keys.onReleased: Activity.processKeyRelease(event.key)
*/
onStart: Activity.initEditor(props);
Component.onCompleted: console.log("XXX editor complete");
QtObject {
id: props
property int columns: 10
property int rows: 10
property int currentTool
property alias mapModel: mapModel
property alias mapGrimd: mapGrid
property alias toolBox: toolBox
property string contactValue: "1"
// singletons:
property int lastGoalIndex: -1
property int lastBallIndex: -1
property int lastOrderNum: 0
}
Rectangle {
id: background
anchors.fill: parent
Component.onCompleted: {
console.log("XXX editor completed");
}
JsonParser {
id: parser
onError: console.error("Balancebox: Error parsing JSON: " + msg);
}
GCText {
id: title
text: "Editor"
anchors.top: parent.top
anchors.left: parent.left
}
Column {
id: toolBox2
anchors.top: background.top
anchors.right: background.right
width: 80
height: parent.height
anchors.topMargin: 20
// /anchors.rightMargin: (background.width - mapWrapper.x - mapWrapper.width ) / 2
Button {
id: saveButton
width: 80
height: 30
style: GCButtonStyle {}
text: "Save"
onClicked: {
Activity.modelToLevel(props);
}
}
}
Column {
id: toolBox
anchors.top: title.bottom
anchors.left: parent.left
width: items.cellSize
anchors.leftMargin: (mapWrapper.x - width ) / 2
function setCurrentTool(item)
{
props.currentTool = item.type;
if (hWallTool !== item) hWallTool.selected = false;
if (vWallTool !== item) vWallTool.selected = false;
if (holeTool !== item) holeTool.selected = false;
if (ballTool !== item) ballTool.selected = false;
if (contactTool !== item) contactTool.selected = false;
if (goalTool !== item) goalTool.selected = false;
console.log("XXX new current tool: " + item);
}
EditorTool {
id: hWallTool
type: Activity.TOOL_H_WALL
anchors.left: parent.left
width: items.cellSize
height: items.cellSize
onSelectedChanged: {
if (selected) {
toolBox.setCurrentTool(hWallTool);
}
}
Wall {
id: hWall
width: parent.width
height: items.wallSize
anchors.centerIn: parent
anchors.margins: 3
}
}
EditorTool {
id: vWallTool
anchors.left: parent.left
width: items.cellSize
height: items.cellSize
type: Activity.TOOL_V_WALL
onSelectedChanged: {
if (selected) {
toolBox.setCurrentTool(vWallTool);
}
}
Wall {
id: vWall
width: items.wallSize
height: parent.height
anchors.centerIn: parent
anchors.margins: 3
}
}
EditorTool {
id: holeTool
anchors.left: parent.left
width: items.cellSize
height: items.cellSize
type: Activity.TOOL_HOLE
onSelectedChanged: {
if (selected) {
toolBox.setCurrentTool(holeTool);
}
}
BalanceItem {
id: hole
width: parent.width - items.wallSize / 2
height: parent.height - items.wallSize / 2
anchors.centerIn: parent
anchors.margins: items.wallSize / 2
visible: true
imageSource: Activity.baseUrl + "/hole.svg"
}
}
EditorTool {
id: ballTool
anchors.left: parent.left
width: items.cellSize
height: items.cellSize
type: Activity.TOOL_BALL
onSelectedChanged: {
if (selected) {
toolBox.setCurrentTool(ballTool);
}
}
BalanceItem {
id: ball
width: parent.width - items.wallSize / 2
height: parent.height - items.wallSize / 2
anchors.centerIn: parent
anchors.margins: items.wallSize / 2
visible: true
imageSource: Activity.baseUrl + "/ball.svg"
}
}
EditorTool {
id: goalTool
anchors.left: parent.left
width: items.cellSize
height: items.cellSize
type: Activity.TOOL_GOAL
onSelectedChanged: {
if (selected) {
toolBox.setCurrentTool(goalTool);
}
}
BalanceItem {
id: goal
width: items.cellSize - items.wallSize
height: items.cellSize - items.wallSize
anchors.centerIn: parent
anchors.margins: items.wallSize / 2
z: 1
imageSource: Activity.baseUrl + "/door.svg"
}
}
Rectangle {
id: contactToolWrapper
width: items.cellSize * 2
height: items.cellSize
color: "silver"
EditorTool {
id: contactTool
anchors.left: parent.left
width: items.cellSize
height: items.cellSize
type: Activity.TOOL_CONTACT
onSelectedChanged: {
if (selected) {
toolBox.setCurrentTool(contactTool);
}
}
BalanceContact {
id: contact
width: items.cellSize - items.wallSize
height: items.cellSize - items.wallSize
anchors.centerIn: parent
anchors.margins: items.wallSize / 2
pressed: false
orderNum: 99
text: props.contactValue
z: 1
}
}
TextInput {
id: contactTextInput
width: contactTool.width
height: contactTool.height
anchors.left: contactTool.right
text: props.contactValue
maximumLength: 2
//activeFocusOnPress: true
font.pixelSize: width / 2
//horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
onTextChanged: if (text !== props.contactValue) props.contactValue = text;
}
}
}
ListModel {
id: mapModel
}
Rectangle {
id: mapWrapper
property double margin: 20
property int columns: props.columns
property int rows: props.rows
property double length: Math.min(background.height -
2*mapWrapper.margin, background.width - 2*mapWrapper.margin);
color: "#E3DEDB"
width: length
height: length