diff --git a/src/activities/align4-2players/Align42players.qml b/src/activities/align4-2players/Align42players.qml index 89728e19a..99b7ca09e 100644 --- a/src/activities/align4-2players/Align42players.qml +++ b/src/activities/align4-2players/Align42players.qml @@ -1,237 +1,237 @@ /* GCompris - align4-2players.qml * * Copyright (C) 2014 Bharath M S * * Authors: * Laurent Lacheny (GTK+ version) * Bharath M S (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.6 import "../../core" import "align4.js" as Activity import GCompris 1.0 ActivityBase { id: activity property bool twoPlayer: true onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop signal start signal stop 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 fallingPiece: fallingPiece property alias pieces: pieces property alias dynamic: dynamic property alias drop: drop property alias player1score: player1score property alias player2score: player2score property alias bar: bar property alias bonus: bonus property alias repeater: repeater property alias columns: grid.columns property alias rows: grid.rows property alias trigTuxMove: trigTuxMove - property int cellSize: background.width < background.height ? (background.width / (columns + 3)) : (background.height / (rows + 4)) + property int cellSize: background.width <= background.height ? (background.width / (columns + 3)) : (background.height / (rows + 4)) property bool gameDone property int counter property int nextPlayerStart: 1 } onStart: { Activity.start(items, twoPlayer) } onStop: { Activity.stop() } Keys.onRightPressed: Activity.moveCurrentIndexRight(); Keys.onLeftPressed: Activity.moveCurrentIndexLeft(); Keys.onDownPressed: Activity.handleDrop(Activity.currentLocation); Keys.onSpacePressed: Activity.handleDrop(Activity.currentLocation); ListModel { id: pieces } // Tux move delay Timer { id: trigTuxMove repeat: false interval: 1500 onTriggered: { Activity.doMove() items.player2score.endTurn() items.player1score.beginTurn() } } Grid { id: grid z: 2 anchors.horizontalCenter: parent.horizontalCenter anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } spacing: 5 columns: 7 rows: 6 Repeater { id: repeater model: pieces delegate: blueSquare Component { id: blueSquare Rectangle { color: "#DDAAAAAA"; width: items.cellSize height: items.cellSize radius: width / 2 border.color: "#FFFFFFFF" border.width: 0 Piece { anchors.fill: parent state: stateTemp sourceSize.width: items.cellSize } } } } Piece { id: fallingPiece state: items.gameDone ? "invisible" : items.counter % 2 ? "2": "1" sourceSize.width: items.cellSize Behavior on x { PropertyAnimation { duration: 200 } } } } PropertyAnimation { id: drop target: fallingPiece properties: "y" duration: 720 onStarted: activity.audioEffects.play(Activity.url + 'slide.wav') onStopped: { dynamic.display() Activity.continueGame() } } MouseArea { id: dynamic anchors.fill: parent enabled: hoverEnabled hoverEnabled: (!drop.running && !items.gameDone && (items.counter % 2 == 0 || twoPlayer)) property bool holdMode: true function display() { var coord = grid.mapFromItem(background, mouseX, mouseY) Activity.setPieceLocation(coord.x, coord.y) } onPositionChanged: items.dynamic.enabled ? display() : '' onPressed: holdMode = false onPressAndHold: holdMode = true onClicked: { display() if(!holdMode) { var coord = grid.mapFromItem(background, mouseX, mouseY) var column = Activity.whichColumn(coord.x, coord.y) Activity.handleDrop(column) } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | reload | (twoPlayer ? 0 : level) } onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: activity.home() onReloadClicked: { Activity.reset() } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() } ScoreItem { id: player1score z: 1 player: 1 height: Math.min(background.height/7, Math.min(background.width/7, bar.height * 1.05)) width: height*11/8 anchors { top: background.top topMargin: 5 left: background.left leftMargin: 5 } playerImageSource: Activity.url + "player_1.svg" backgroundImageSource: Activity.url + "score_1.svg" } ScoreItem { id: player2score z: 1 player: 2 height: Math.min(background.height/7, Math.min(background.width/7, bar.height * 1.05)) width: height*11/8 anchors { top: background.top topMargin: 5 right: background.right rightMargin: 5 } playerImageSource: Activity.url + "player_2.svg" backgroundImageSource: Activity.url + "score_2.svg" playerScaleOriginX: player2score.width } Bonus { id: bonus } } } diff --git a/src/activities/baby_wordprocessor/BabyWordprocessor.qml b/src/activities/baby_wordprocessor/BabyWordprocessor.qml index 20cb2ac9d..92d6a19b1 100644 --- a/src/activities/baby_wordprocessor/BabyWordprocessor.qml +++ b/src/activities/baby_wordprocessor/BabyWordprocessor.qml @@ -1,317 +1,317 @@ /* GCompris - baby_wordprocessor.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (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.6 import GCompris 1.0 import QtQuick.Controls 1.5 import "../../core" ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: 'white' signal start signal stop Component.onCompleted: { activity.start.connect(start) } onStart: { keyboard.populate(); edit.forceActiveFocus(); } - property bool horizontalMode: height < width + property bool horizontalMode: height <= width GCCreationHandler { id: creationHandler onFileLoaded: { edit.clear() edit.text = data["text"] edit.cursorPosition = edit.length } onClose: edit.forceActiveFocus() } Column { id: controls width: 120 * ApplicationInfo.ratio anchors { right: parent.right top: parent.top margins: 10 } spacing: 10 Button { style: GCButtonStyle { textSize: "title"} text: qsTr("Title") width: parent.width onClicked: edit.formatLineWith('h2') } Button { style: GCButtonStyle { textSize: "subtitle"} text: qsTr("Subtitle") width: parent.width onClicked: edit.formatLineWith('h3') } Button { style: GCButtonStyle { textSize: "regular"} width: parent.width text: qsTr("Paragraph") onClicked: edit.formatLineWith('p') } } Column { id: saveAndLoadButtons width: controls.width property bool isEnoughSpace: { if(ApplicationInfo.isMobile && parent.horizontalMode) return false return (parent.height - keyboard.height - controls.height) > 2.5 * loadButton.height } anchors { right: !isEnoughSpace ? controls.left : parent.right top: !isEnoughSpace ? parent.top : controls.bottom margins: 10 } spacing: 10 Button { id: loadButton style: GCButtonStyle { textSize: "regular"} width: parent.width text: qsTr("Load") onClicked: { creationHandler.loadWindow() } } Button { id: saveButton style: GCButtonStyle { textSize: "regular"} width: parent.width text: qsTr("Save") onClicked: { var textToSave = {} textToSave["text"] = edit.getFormattedText(0, edit.length) creationHandler.saveWindow(textToSave) } } } Flickable { id: flick anchors { left: parent.left right: saveAndLoadButtons.left top: parent.top bottom: bar.top margins: 10 } contentWidth: edit.paintedWidth contentHeight: edit.paintedHeight clip: true flickableDirection: Flickable.VerticalFlick function ensureVisible(r) { if (contentX >= r.x) contentX = r.x; else if (contentX+width <= r.x+r.width) contentX = r.x+r.width-width; if (contentY >= r.y) contentY = r.y; else if (contentY+height <= r.y+r.height) contentY = r.y+r.height-height; } TextEdit { id: edit width: flick.width height: flick.height focus: true wrapMode: TextEdit.Wrap onCursorRectangleChanged: flick.ensureVisible(cursorRectangle) textFormat: TextEdit.RichText color: "#373737" font { pointSize: (18 + ApplicationSettings.baseFontSize) * ApplicationInfo.fontRatio capitalization: ApplicationSettings.fontCapitalization weight: Font.DemiBold family: GCSingletonFontLoader.fontLoader.name letterSpacing: ApplicationSettings.fontLetterSpacing wordSpacing: 10 } cursorDelegate: Rectangle { id: cursor width: 10 // height should be set automatically as mention in cursorRectangle property // documentation but it does not work height: parent.cursorRectangle.height color: '#DF543D' SequentialAnimation on opacity { running: true loops: Animation.Infinite PropertyAnimation { to: 0.2 duration: 1000 } PropertyAnimation { to: 1 duration: 1000 } } } function insertText(text) { edit.insert(cursorPosition, text) } function backspace() { if(cursorPosition > 0) { moveCursorSelection(cursorPosition - 1, TextEdit.SelectCharacters) cut() } } function newline() { insert(cursorPosition, "

") } function formatLineWith(tag) { var text = getText(0, length) var initialPosition = cursorPosition var first = cursorPosition - 1 for(; first >= 0; first--) { if(text.charCodeAt(first) === 8233) break } first++ var last = cursorPosition for(; last < text.length; last++) { if(text.charCodeAt(last) === 8233) break } var line = getText(first, last) remove(first, last) insert(first, '<' + tag + '>' + line + '') cursorPosition = initialPosition } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: help | home | reload } onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: activity.home() onReloadClicked: edit.text = '' } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter width: parent.width visible: ApplicationSettings.isVirtualKeyboard && !ApplicationInfo.isMobile onKeypress: { if(text == backspace) edit.backspace() else if(text == newline) edit.newline() else edit.insertText(text) } shiftKey: true onError: console.log("VirtualKeyboard error: " + msg); readonly property string newline: "\u21B2" function populate() { layout = [ [ { label: "0" }, { label: "1" }, { label: "2" }, { label: "3" }, { label: "4" }, { label: "5" }, { label: "6" }, { label: "7" }, { label: "8" }, { label: "9" } ], [ { label: "A" }, { label: "B" }, { label: "C" }, { label: "D" }, { label: "E" }, { label: "F" }, { label: "G" }, { label: "H" }, { label: "I" } ], [ { label: "J" }, { label: "K" }, { label: "L" }, { label: "M" }, { label: "N" }, { label: "O" }, { label: "P" }, { label: "Q" }, { label: "R" } ], [ { label: "S" }, { label: "T" }, { label: "U" }, { label: "V" }, { label: "W" }, { label: "X" }, { label: "Y" }, { label: "Z" }, { label: " " }, { label: backspace }, { label: newline } ] ] } } } } diff --git a/src/activities/babymatch/Babymatch.qml b/src/activities/babymatch/Babymatch.qml index 69af52f36..ca60ee468 100644 --- a/src/activities/babymatch/Babymatch.qml +++ b/src/activities/babymatch/Babymatch.qml @@ -1,316 +1,316 @@ /* GCompris - Babymatch.qml * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Bruno Coudoin (GTK+ version) * Pulkit Gupta (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.6 import GCompris 1.0 import "../../core" import "babymatch.js" as Activity ActivityBase { id: activity // In most cases, these 3 are the same. // But for imageName for example, we reuse the images of babymatch, so we need to differentiate them property string imagesUrl: boardsUrl property string soundsUrl: boardsUrl property string boardsUrl: "qrc:/gcompris/src/activities/babymatch/resource/" property int levelCount: 7 property bool answerGlow: true //For highlighting the answers property bool displayDropCircle: true //For displaying drop circles onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" signal start signal stop - property bool vert: background.width > background.height + property bool vert: background.width >= background.height 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 alias background: background property alias bar: bar property alias bonus: bonus property alias availablePieces: availablePieces property alias backgroundPiecesModel: backgroundPiecesModel property alias file: file property alias grid: grid property alias backgroundImage: backgroundImage property alias leftWidget: leftWidget property alias instruction: instruction property alias toolTip: toolTip property alias score: score property alias dataset: dataset } Loader { id: dataset asynchronous: false } onStart: { Activity.start(items, imagesUrl, soundsUrl, boardsUrl, levelCount, answerGlow, displayDropCircle) } onStop: { Activity.stop() } 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() } Score { id: score visible: numberOfSubLevels > 1 } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } File { id: file name: "" } Image { id: leftWidget source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW02.svg" width: background.vert ? 90 * ApplicationInfo.ratio : background.width height: background.vert ? background.height : 90 * ApplicationInfo.ratio anchors.left: parent.left ListWidget { id: availablePieces vert: background.vert } MouseArea { anchors.fill: parent onPressed: (instruction.opacity == 0 ? instruction.show() : instruction.hide()) } } Rectangle { id: toolTip anchors { bottom: bar.top bottomMargin: 10 left: leftWidget.left leftMargin: 5 } width: toolTipTxt.width + 10 height: toolTipTxt.height + 5 opacity: 1 radius: 10 z: 100 color: "#E8E8E8" property alias text: toolTipTxt.text Behavior on opacity { NumberAnimation { duration: 120 } } function show(newText) { if(newText) { text = newText opacity = 0.8 } else { opacity = 0 } } Rectangle { width: parent.width - anchors.margins height: parent.height - anchors.margins anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.margins: parent.height/8 radius: 10 color: "#87A6DD" //light blue } Rectangle { width: parent.width - anchors.margins height: parent.height - anchors.margins anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.margins: parent.height/4 radius: 10 color: "#E8E8E8" //paper white } GCText { id: toolTipTxt anchors.centerIn: parent fontSize: regularSize color: "#373737" horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } } Rectangle { id: grid color: "transparent" z: 2 x: background.vert ? 90 * ApplicationInfo.ratio : 0 y: background.vert ? 0 : 90 * ApplicationInfo.ratio width: background.vert ? background.width - 90 * ApplicationInfo.ratio : background.width height: background.vert ? background.height - (bar.height * 1.1) : background.height - (bar.height * 1.1) - 90 * ApplicationInfo.ratio Image { id: backgroundImage fillMode: Image.PreserveAspectFit property double ratio: sourceSize.width / sourceSize.height property double gridRatio: grid.width / grid.height property alias instruction: instruction source: "" z: 2 width: source == "" ? grid.width : (ratio > gridRatio ? grid.width : grid.height * ratio) height: source == "" ? grid.height : (ratio < gridRatio ? grid.height : grid.width / ratio) anchors.topMargin: 10 anchors.centerIn: parent //Inserting static background images Repeater { id: backgroundPieces model: backgroundPiecesModel delegate: piecesDelegate z: 2 Component { id: piecesDelegate Image { id: shapeBackground source: Activity.imagesUrl + imgName x: posX * backgroundImage.width - width / 2 y: posY * backgroundImage.height - height / 2 height: imgHeight ? imgHeight * backgroundImage.height : (backgroundImage.source == "" ? backgroundImage.height * shapeBackground.sourceSize.height / backgroundImage.height : backgroundImage.height * shapeBackground.sourceSize.height / backgroundImage.sourceSize.height) width: imgWidth ? imgWidth * backgroundImage.width : (backgroundImage.source == "" ? backgroundImage.width * shapeBackground.sourceSize.width / backgroundImage.width : backgroundImage.width * shapeBackground.sourceSize.width / backgroundImage.sourceSize.width) fillMode: Image.PreserveAspectFit } } } MouseArea { anchors.fill: parent onPressed: (instruction.opacity == 0 ? instruction.show() : instruction.hide()) } } } Rectangle { id: instruction anchors.horizontalCenter: instructionTxt.horizontalCenter anchors.verticalCenter: instructionTxt.verticalCenter width: instructionTxt.width + 40 height: instructionTxt.height + 40 opacity: 0.8 radius: 10 z: 3 color: "#87A6DD" //light blue property alias text: instructionTxt.text Behavior on opacity { PropertyAnimation { duration: 200 } } function show() { if(text) opacity = 1 } function hide() { opacity = 0 } Rectangle { id: insideFill width: parent.width - anchors.margins height: parent.height - anchors.margins anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.margins: parent.height/8 radius: 10 color: "#E8E8E8" //paper white } } GCText { id: instructionTxt anchors { top: background.vert ? grid.top : leftWidget.bottom topMargin: -10 horizontalCenter: background.vert ? grid.horizontalCenter : leftWidget.horizontalCenter } opacity: instruction.opacity z: instruction.z fontSize: regularSize color: "#373737" horizontalAlignment: Text.AlignHCenter width: Math.max(Math.min(parent.width * 0.9, text.length * 11), parent.width * 0.3) wrapMode: TextEdit.WordWrap } ListModel { id: backgroundPiecesModel } } } diff --git a/src/activities/babymatch/DropAnswerItem.qml b/src/activities/babymatch/DropAnswerItem.qml index 78d48481f..8cad28c46 100644 --- a/src/activities/babymatch/DropAnswerItem.qml +++ b/src/activities/babymatch/DropAnswerItem.qml @@ -1,94 +1,94 @@ /* gcompris - DropAnswerItem.qml * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Pulkit Gupta * * 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.6 import GCompris 1.0 import "babymatch.js" as Activity Rectangle { id: dropCircle property string dropCircleColor: currentTileImageItem ? 'transparent' : 'pink' property string text property double posX property double posY property string imgName property double xCenter: x + width / 2 property double yCenter: y + height / 2 property Item currentTileImageItem - width: parent.width > parent.height ? parent.height/35 : parent.width/35 + width: parent.width >= parent.height ? parent.height/35 : parent.width/35 height: width radius: width/2 z: 200 border.width: 1 color: Activity.displayDropCircle ? dropCircleColor : "transparent" border.color: dropCircle.color == "#000000" ? "transparent" : "red" x: posX * parent.width - width/2 y: posY * parent.height - height/2 // Display a shadow of image, when the image is hovered over a target area Image { id: targetImage fillMode: Image.PreserveAspectFit anchors.centerIn: parent z : -1 } function imageRemove() { if(currentTileImageItem) currentTileImageItem.imageRemove() currentTileImageItem = null } function imageAdd(tileImageItem) { currentTileImageItem = tileImageItem dropCircle.color = dropCircleColor } function show(tileImageItem) { if(Activity.displayDropCircle) dropCircle.color = "lightgreen" targetImage.source = tileImageItem.source targetImage.width = tileImageItem.fullWidth targetImage.height = tileImageItem.fullHeight if(currentTileImageItem) { currentTileImageItem.opacity = 0 } if (tileImageItem.parentIsTile) { targetImage.opacity = 1 tileImageItem.opacity = 0.5 dropCircle.z = 100 } else targetImage.opacity = 0.5 } function hide() { dropCircle.color = Activity.displayDropCircle ? dropCircleColor : "transparent" targetImage.opacity = 0 dropCircle.z = 200 if(currentTileImageItem) currentTileImageItem.opacity = 1 } } diff --git a/src/activities/bargame/Bargame.qml b/src/activities/bargame/Bargame.qml index 48598e887..16bc96eb8 100644 --- a/src/activities/bargame/Bargame.qml +++ b/src/activities/bargame/Bargame.qml @@ -1,448 +1,448 @@ /* GCompris - bargame.qml * * Copyright (C) 2016 UTKARSH TIWARI * * Authors: * Yves Combe (GTK+ version) * UTKARSH TIWARI (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.6 import GCompris 1.0 import "../../core" import "bargame.js" as Activity ActivityBase { id: activity property int gameMode: 1 onStart: focus = true onStop: {} pageComponent: Image { id: rootWindow source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop signal start signal stop Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() 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 int numberOfBalls: 1 property alias answerBallsPlacement: answerBallsPlacement property alias bar: bar property alias bonus: bonus property alias okArea: okArea property alias trigTuxMove: trigTuxMove property alias player1score: player1score property alias player2score: player2score property int mode: 1 property bool isPlayer1Beginning: true property bool isPlayer1Turn: true } onStart: { Activity.start(items, gameMode); Activity.calculateWinPlaces(); } onStop: { Activity.stop() } // Tux move delay Timer { id: trigTuxMove repeat: false interval: 1500 onTriggered: Activity.machinePlay() } // Tux image Image { id: tux visible: gameMode == 1 source: Activity.url + "tux1.svg" height: rootWindow.height / 4 width: tux.height y: rootWindow.height - rootWindow.height / 1.8 anchors { right: rootWindow.right rightMargin: 16 * ApplicationInfo.ratio } MouseArea { id: tuxArea hoverEnabled: enabled enabled: gameMode == 1 && !answerBallsPlacement.children[0].visible anchors.fill: parent onClicked: { items.isPlayer1Turn = false; Activity.machinePlay(); } } states: State { name: "tuxHover" when: tuxArea.containsMouse PropertyChanges { target: tux scale: 1.1 } } } // Box row Item { id: boxModel x: 0 anchors.top: tux.bottom transform: Rotation { origin.x: 0; origin.y: 0; - angle: (rootWindow.width > rootWindow.height) ? 0 : 90 + angle: (rootWindow.width >= rootWindow.height) ? 0 : 90 onAngleChanged: { if (angle === 90) { boxModel.anchors.top = undefined; boxModel.y = 0; boxModel.anchors.horizontalCenter = rootWindow.horizontalCenter; } else { boxModel.anchors.horizontalCenter = undefined; boxModel.x = 0; boxModel.anchors.top = tux.bottom; } } } // The empty boxes grid Grid { id: boxes rows: 1 columns: Activity.levelsProperties[items.mode - 1].boardSize anchors.centerIn: boxModel.Center Repeater { id: startCase model: boxes.columns Image { id: greenCase source: Activity.url + ((index == boxes.columns - 1) ? "case_last.svg" : "case.svg") sourceSize.width: ((rootWindow.width > rootWindow.height) ? rootWindow.width : (rootWindow.height * 0.86)) / (15 + (items.mode - 1) * Activity.levelsProperties[items.mode - 1].elementSizeFactor) width: sourceSize.width height: sourceSize.width visible: true } } } // Hidden Answer Balls Grid { // All green balls placement id: answerBallsPlacement anchors.centerIn: boxModel.Center columns: Activity.levelsProperties[items.mode - 1].boardSize rows: 1 Repeater { model: answerBallsPlacement.columns Image { source: Activity.url + "ball_1.svg" sourceSize.width: ((rootWindow.width > rootWindow.height) ? rootWindow.width : (rootWindow.height * 0.86)) / (15 + (items.mode - 1) * Activity.levelsProperties[items.mode - 1].elementSizeFactor) width: sourceSize.width height: sourceSize.width visible: false } } } // Masks Grid { id: masks anchors.centerIn: boxModel.Center rows: 1 columns: Activity.levelsProperties[items.mode - 1].boardSize Repeater { id: startMask model: masks.columns Image { id: greenMask source: Activity.url + ((index == boxes.columns - 1) ? "mask_last.svg" : "mask.svg") sourceSize.width: ((rootWindow.width > rootWindow.height) ? rootWindow.width : (rootWindow.height * 0.86)) / (15 + (items.mode - 1) * Activity.levelsProperties[items.mode - 1].elementSizeFactor) width: sourceSize.width height: sourceSize.width // Numbering label Rectangle { id: bgNbTxt visible: ((index + 1) % 5 == 0 && index > 0) ? true : false color: "#42FFFFFF" height: numberText.height * 1.2 width: height radius: height / 2 anchors { horizontalCenter: parent.horizontalCenter bottom: parent.top bottomMargin: ((rootWindow.width > rootWindow.height) ? 4 * ApplicationInfo.ratio : -16 * ApplicationInfo.ratio) } GCText { id: numberText text: index + 1 color: "#373737" fontSize: smallSize font.bold: true visible: ((index + 1) % 5 == 0 && index > 0) ? true : false anchors { horizontalCenter: bgNbTxt.horizontalCenter verticalCenter: bgNbTxt.verticalCenter } } transform: Rotation { angle: (rootWindow.width > rootWindow.height) ? 0 : -90 } } } } } } // ok button Image { id: playLabel width: ballNumberPlate.height height: width sourceSize.width: width sourceSize.height: width source: Activity.url + "bar_ok.svg" anchors { left: ballNumberPlate.right verticalCenter: ballNumberPlate.verticalCenter leftMargin: width / 4 } MouseArea { id: okArea anchors.fill: parent hoverEnabled: enabled enabled: true onClicked: { var value = items.numberOfBalls if (gameMode == 1 || items.isPlayer1Turn) { Activity.play(1, value); } else { Activity.play(2, value); } // reset next action items.numberOfBalls = Activity.levelsProperties[items.mode - 1].minNumberOfBalls } } states: State { name: "mouseHover" when: okArea.containsMouse PropertyChanges { target: playLabel scale: 1.2 } } } // Number of balls to be placed Image { id: ballNumberPlate y: rootWindow.height * 0.32 source: items.isPlayer1Turn ? Activity.url + "score_1.svg" : Activity.url + "score_2.svg" width: bar.height height: bar.height * 0.7 sourceSize.width: width sourceSize.height: height anchors { left: rootWindow.left leftMargin: 16 * ApplicationInfo.ratio } MouseArea { id: numberPlateArea anchors.fill: parent hoverEnabled: true onClicked: { items.numberOfBalls ++; var max = Activity.levelsProperties[items.mode - 1].maxNumberOfBalls if (items.numberOfBalls > max) { items.numberOfBalls = Activity.levelsProperties[items.mode - 1].minNumberOfBalls; } } } states: State { name: "numberHover" when: numberPlateArea.containsMouse PropertyChanges { target: ballNumberPlate scale: 1.1 } } // Ball Icon Image { id: ballIcon source: items.isPlayer1Turn ? Activity.url + "ball_1b.svg" : Activity.url + "ball_2b.svg" sourceSize.width: parent.height * 0.8 width: sourceSize.width height: sourceSize.width anchors { verticalCenter: ballNumberPlate.verticalCenter left: ballNumberPlate.left leftMargin: 10 } } // Number label GCText { id: numberLabel text: items.numberOfBalls color: "#C04040" font.bold: true fontSize: smallSize anchors { right: ballNumberPlate.right rightMargin: 10 verticalCenter: ballNumberPlate.verticalCenter } } } ScoreItem { id: player1score player: 1 height: Math.min(rootWindow.height/7, Math.min(rootWindow.width/7, bar.height * 1.05)) width: height*11/8 anchors { top: rootWindow.top topMargin: 5 left: rootWindow.left leftMargin: 5 } playerImageSource: Activity.url + "player_1.svg" backgroundImageSource: Activity.url + "score_1.svg" } ScoreItem { id: player2score player: 2 height: Math.min(rootWindow.height/7, Math.min(rootWindow.width/7, bar.height * 1.05)) width: height*11/8 anchors { top: rootWindow.top topMargin: 5 right: rootWindow.right rightMargin: 5 } playerImageSource: Activity.url + "player_2.svg" backgroundImageSource: Activity.url + "score_2.svg" playerScaleOriginX: player2score.width } DialogHelp { id: dialogHelp onClose: home() } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias modeBox: modeBox property var availableModes: [ { "text": qsTr("Easy"), "value": 1 }, { "text": qsTr("Medium"), "value": 2 }, { "text": qsTr("Difficult"), "value": 3 } ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: modeBox model: availableModes background: dialogActivityConfig label: qsTr("Select your difficulty") } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["mode"]) { items.mode = dataToSave["mode"]; } } onSaveData: { var newMode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.modeBox.currentIndex].value; if (newMode !== items.mode) { items.mode = newMode; dataToSave = {"mode": items.mode}; } Activity.initLevel(); } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableModes.length ; i++) { if(dialogActivityConfig.configItem.availableModes[i].value === items.mode) { dialogActivityConfig.configItem.modeBox.currentIndex = i; break; } } } } Bar { id: bar level: 1 content: BarEnumContent { value: help | home | level | reload | config } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true // Set default values dialogActivityConfig.setDefaultValues(); displayDialog(dialogActivityConfig) } onReloadClicked: Activity.restartLevel() } Bonus { id: bonus Component.onCompleted: { win.connect(Activity.initLevel) loose.connect(Activity.initLevel) } } } } diff --git a/src/activities/binary_bulb/BinaryBulb.qml b/src/activities/binary_bulb/BinaryBulb.qml index 0cbe7da89..7fa1325e9 100644 --- a/src/activities/binary_bulb/BinaryBulb.qml +++ b/src/activities/binary_bulb/BinaryBulb.qml @@ -1,209 +1,209 @@ /* GCompris - BinaryBulb.qml * * Copyright (C) 2018 Rajat Asthana * * Authors: * RAJAT ASTHANA * * 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.6 import GCompris 1.0 import "../../core" import "binary_bulb.js" as Activity import "numbers.js" as Dataset ActivityBase { id: activity onStart: focus = true onStop: {} property var dataset: Dataset pageComponent: Image { id: background anchors.fill: parent source: "../digital_electricity/resource/texture01.png" fillMode: Image.Tile signal start signal stop 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 bulbs: bulbs property int numberSoFar: 0 property int numberToConvert: 0 property int numberOfBulbs: 0 property int currentSelectedBulb: -1 property int currentLevel: 0 property alias score: score } onStart: { Activity.start(items, dataset) } onStop: { Activity.stop() } // Tutorial section starts Image { id: tutorialImage source: "../digital_electricity/resource/texture01.png" anchors.fill: parent fillMode: Image.Tile z: 5 visible: true Tutorial { id: tutorialSection tutorialDetails: Activity.tutorialInstructions useImage: false onSkipPressed: { Activity.initLevel() tutorialImage.visible = false } } } // Tutorial section ends Keys.onPressed: { if(event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { Activity.equalityCheck() } else if(event.key == Qt.Key_Space) { if(items.currentSelectedBulb != -1) { Activity.changeState(items.currentSelectedBulb) } } else if(event.key == Qt.Key_Left) { if(--items.currentSelectedBulb < 0) { items.currentSelectedBulb = items.numberOfBulbs-1 } } else if(event.key == Qt.Key_Right) { if(++items.currentSelectedBulb >= items.numberOfBulbs) { items.currentSelectedBulb = 0 } } } Rectangle { id: questionItemBackground opacity: 0 z: 10 anchors { horizontalCenter: parent.horizontalCenter bottomMargin: 10 } height: background.height / 6 width: parent.width - 20 * ApplicationInfo.ratio } GCText { id: questionItem anchors.fill: questionItemBackground anchors.bottom: questionItemBackground.bottom fontSizeMode: Text.Fit wrapMode: Text.Wrap z: 4 color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: qsTr("What is the binary representation of %1?").arg(items.numberToConvert) } Row { id: row anchors.top: questionItem.bottom anchors.topMargin: 30 * ApplicationInfo.ratio anchors.horizontalCenter: parent.horizontalCenter spacing: 10 * ApplicationInfo.ratio Repeater { id: bulbs model: items.numberOfBulbs LightBulb { height: background.height / 5 - width: (background.width > background.height) ? (background.width / 20) : ((background.width - (16 * row.spacing)) / 8) + width: (background.width >= background.height) ? (background.width / 20) : ((background.width - (16 * row.spacing)) / 8) valueVisible: Dataset.get()[items.currentLevel].bulbValueVisible } } } GCText { id: reachedSoFar anchors.horizontalCenter: row.horizontalCenter anchors.top: row.bottom anchors.topMargin: 30 * ApplicationInfo.ratio color: "white" fontSize: largeSize text: items.numberSoFar visible: Dataset.get()[items.currentLevel].enableHelp } BarButton { id: okButton anchors { bottom: bar.top right: parent.right rightMargin: 10 * ApplicationInfo.ratio bottomMargin: 10 * ApplicationInfo.ratio } source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 60 * ApplicationInfo.ratio onClicked: Activity.equalityCheck() enabled: !bonus.isPlaying && !score.isWinAnimationPlaying } 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() } Score { id: score visible: !tutorialImage.visible anchors.bottom: bar.top anchors.right: bar.right anchors.left: parent.left anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.rightMargin: 0 } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/calendar/Calendar.qml b/src/activities/calendar/Calendar.qml index f1ea2f2d7..627bac502 100644 --- a/src/activities/calendar/Calendar.qml +++ b/src/activities/calendar/Calendar.qml @@ -1,434 +1,434 @@ /* GCompris - Calendar.qml * * Copyright (C) 2017 Amit Sagtani * * Authors: * Amit Sagtani * * 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.6 import GCompris 1.0 import QtQuick.Controls 1.5 import QtQuick.Controls.Private 1.0 import QtQuick.Controls.Styles 1.4 import "../../core" import "calendar.js" as Activity import "calendar_dataset.js" as Dataset ActivityBase { id: activity property var dataset: Dataset onStart: focus = true onStop: {} pageComponent: Image { id: background signal start signal stop fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/fifteen/resource/background.svg" sourceSize.width: Math.max(parent.width, parent.height) 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 calendar: calendar property alias okButton: okButton property alias questionItem: questionItem property alias score: score property alias answerChoices: answerChoices property alias questionDelay: questionDelay property alias okButtonParticles: okButtonParticles - property bool horizontalLayout: background.width > background.height * 1.5 + property bool horizontalLayout: background.width >= background.height * 1.5 property alias daysOfTheWeekModel: daysOfTheWeekModel } onStart: { Activity.start(items, dataset) } onStop: { Activity.stop() } Keys.onPressed: (answerChoices.visible) ? answerChoices.handleKeys(event) : handleKeys(event); // Question time delay Timer { id: questionDelay repeat: false interval: 1600 onTriggered: { Activity.initQuestion() } onRunningChanged: okButtonMouseArea.enabled = !okButtonMouseArea.enabled } Rectangle { id: calendarBox width: items.horizontalLayout ? (answerChoices.visible ? parent.width * 0.75 : parent.width * 0.80) : (answerChoices.visible ? parent.width * 0.65 : parent.width * 0.85) height: items.horizontalLayout ? parent.height * 0.68 : parent.height - bar.height - questionItemBackground.height - okButton.height * 1.5 anchors.top: questionItem.bottom anchors.topMargin: 5 anchors.rightMargin: answerChoices.visible ? 100 : undefined anchors.horizontalCenterOffset: answerChoices.visible ? 80 : 0 anchors.horizontalCenter: parent.horizontalCenter color: "black" opacity: 0.3 } Calendar { id: calendar weekNumbersVisible: false width: calendarBox.width * 0.96 height: calendarBox.height * 0.96 anchors.centerIn: calendarBox frameVisible: true focus: !answerChoices.visible __locale: Qt.locale(ApplicationInfo.localeShort) style: CalendarStyle { navigationBar: Rectangle { height: calendar.height * 0.12 color: "#f2f2f2" BarButton { id: previousMonth height: parent.height * 0.8 width: previousMonth.height sourceSize.height: previousMonth.height sourceSize.width: previousMonth.width anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: parent.height * 0.1 source: "qrc:/gcompris/src/core/resource/scroll_down.svg" rotation: 90 visible: ((calendar.visibleYear + calendar.visibleMonth) > Activity.minRange) ? true : false onClicked: calendar.showPreviousMonth() } GCText { id: dateText text: styleData.title color: "#373737" horizontalAlignment: Text.AlignHCenter fontSizeMode: Text.Fit anchors.verticalCenter: parent.verticalCenter anchors.left: previousMonth.right anchors.leftMargin: 2 anchors.right: nextMonth.left anchors.rightMargin: 2 } BarButton { id: nextMonth height: previousMonth.height width: nextMonth.height sourceSize.height: nextMonth.height sourceSize.width: nextMonth.width anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: previousMonth.anchors.leftMargin source: "qrc:/gcompris/src/core/resource/scroll_down.svg" rotation: 270 visible: ((calendar.visibleYear + calendar.visibleMonth) < Activity.maxRange) ? true : false onClicked: calendar.showNextMonth() } } dayDelegate: Rectangle { anchors.fill: parent anchors.leftMargin: (!addExtraMargin || control.weekNumbersVisible) && styleData.index % CalendarUtils.daysInAWeek === 0 ? 0 : -1 anchors.rightMargin: !addExtraMargin && styleData.index % CalendarUtils.daysInAWeek === CalendarUtils.daysInAWeek - 1 ? 0 : -1 anchors.bottomMargin: !addExtraMargin && styleData.index >= CalendarUtils.daysInAWeek * (CalendarUtils.weeksOnACalendarMonth - 1) ? 0 : -1 anchors.topMargin: styleData.selected ? -1 : 0 color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "#F2F2F2" border.color: "#cec4c4" radius: 5 property bool addExtraMargin: control.frameVisible && styleData.selected readonly property color sameMonthDateTextColor: "#373737" readonly property color selectedDateColor: "#3778d0" readonly property color selectedDateTextColor: "white" readonly property color differentMonthDateTextColor: "#bbb" readonly property color invalidDateColor: "#dddddd" Label { id: dayDelegateText text: styleData.date.getDate() anchors.centerIn: parent horizontalAlignment: Text.AlignRight font.family: GCSingletonFontLoader.fontLoader.name font.pixelSize: Math.min(parent.height/3, parent.width/3) color: { var theColor = invalidDateColor; if (styleData.valid) { // Date is within the valid range. theColor = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor; if (styleData.selected) theColor = selectedDateTextColor; } theColor; } } } dayOfWeekDelegate: Rectangle { color: "lightgray" implicitHeight: Math.round(TextSingleton.implicitHeight * 2.25) Label { text: control.__locale.dayName(styleData.dayOfWeek, control.dayOfWeekFormat) font.family: GCSingletonFontLoader.fontLoader.name fontSizeMode: Text.Fit minimumPixelSize: 1 font.pixelSize: items.horizontalLayout ? parent.height * 0.7 : parent.width * 0.2 color: "#373737" anchors.centerIn: parent } } } onVisibleMonthChanged: { Activity.monthSelected = visibleMonth Activity.daySelected = selectedDate.getDate() } onVisibleYearChanged: { Activity.yearSelected = visibleYear Activity.daySelected = selectedDate.getDate() } onClicked: { Activity.daySelected = selectedDate.getDate() } onSelectedDateChanged: { Activity.daySelected = selectedDate.getDate() } } function handleKeys(event) { if(event.key === Qt.Key_Space && okButtonMouseArea.enabled) { Activity.checkAnswer() event.accepted = true } if(event.key === Qt.Key_Enter && okButtonMouseArea.enabled) { Activity.checkAnswer() event.accepted = true } if(event.key === Qt.Key_Return && okButtonMouseArea.enabled) { Activity.checkAnswer() event.accepted = true } if(event.key === Qt.Key_Home) { calendar.__selectFirstDayOfMonth(); event.accepted = true; } if(event.key === Qt.Key_End) { calendar.__selectLastDayOfMonth(); event.accepted = true; } if(event.key === Qt.Key_PageUp) { calendar.__selectPreviousMonth(); event.accepted = true; } if(event.key === Qt.Key_PageDown) { calendar.__selectNextMonth(); event.accepted = true; } if(event.key === Qt.Key_Left) { calendar.__selectPreviousDay(); event.accepted = true; } if(event.key === Qt.Key_Up) { calendar.__selectPreviousWeek(); event.accepted = true; } if(event.key === Qt.Key_Down) { calendar.__selectNextWeek(); event.accepted = true; } if(event.key === Qt.Key_Right) { calendar.__selectNextDay(); event.accepted = true; } } ListModel { id: daysOfTheWeekModel ListElement { text: qsTr("Sunday"); dayIndex: 0 } ListElement { text: qsTr("Monday"); dayIndex: 1 } ListElement { text: qsTr("Tuesday"); dayIndex: 2 } ListElement { text: qsTr("Wednesday"); dayIndex: 3 } ListElement { text: qsTr("Thursday"); dayIndex: 4 } ListElement { text: qsTr("Friday"); dayIndex: 5 } ListElement { text: qsTr("Saturday"); dayIndex: 6 } } // Creates a table consisting of days of weeks. GridView { id: answerChoices model: daysOfTheWeekModel anchors.top: calendarBox.top anchors.left: questionItem.left anchors.topMargin: 5 interactive: false property bool keyNavigation: false width: calendarBox.x - anchors.rightMargin height: (calendar.height / 6.5) * 7 cellWidth: calendar.width * 0.5 cellHeight: calendar.height / 6.5 keyNavigationWraps: true anchors.rightMargin: 10 * ApplicationInfo.ratio delegate: ChoiceTable { width: answerChoices.width height: answerChoices.height / 7 choices.text: text anchors.rightMargin: 2 } Keys.enabled: answerChoices.visible function handleKeys(event) { if(event.key === Qt.Key_Down) { keyNavigation = true answerChoices.moveCurrentIndexDown() } if(event.key === Qt.Key_Up) { keyNavigation = true answerChoices.moveCurrentIndexUp() } if(event.key === Qt.Key_Enter && !questionDelay.running) { keyNavigation = true Activity.dayOfWeekSelected = model.get(currentIndex).dayIndex answerChoices.currentItem.select() } if(event.key === Qt.Key_Space && !questionDelay.running) { keyNavigation = true Activity.dayOfWeekSelected = model.get(currentIndex).dayIndex answerChoices.currentItem.select() } if(event.key === Qt.Key_Return && !questionDelay.running) { keyNavigation = true Activity.dayOfWeekSelected = model.get(currentIndex).dayIndex answerChoices.currentItem.select() } } highlight: Rectangle { width: parent.width * 1.2 height: parent.height / 7 anchors.horizontalCenter: parent.horizontalCenter color: "#e99e33" border.width: 2 border.color: "#f2f2f2" radius: 5 visible: answerChoices.keyNavigation y: answerChoices.currentItem.y Behavior on y { SpringAnimation { spring: 3 damping: 0.2 } } } highlightFollowsCurrentItem: false focus: answerChoices.visible } Rectangle { id: questionItemBackground color: "#373737" border.width: 2 border.color: "#f2f2f2" radius: 10 opacity: 0.85 z: 10 anchors { horizontalCenter: parent.horizontalCenter bottomMargin: 10 } width: parent.width - 20 height: parent.height * 0.1 } // Displays the question. GCText { id: questionItem anchors.fill: questionItemBackground anchors.bottom: questionItemBackground.bottom fontSizeMode: Text.Fit wrapMode: Text.Wrap z: 10 color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } // Answer Submission button. BarButton { id: okButton source: "qrc:/gcompris/src/core/resource/bar_ok.svg" height: bar.height * 0.8 width: okButton.height sourceSize.width: okButton.width sourceSize.height: okButton.height z: 10 anchors.top: calendarBox.bottom anchors.right: calendarBox.right anchors.margins: items.horizontalLayout ? 30 : 6 ParticleSystemStarLoader { id: okButtonParticles clip: false } MouseArea { id: okButtonMouseArea anchors.fill: parent onClicked: { Activity.checkAnswer() } } } 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) } Score { id: score height: okButton.height width: height anchors.top: calendarBox.bottom anchors.bottom: undefined anchors.left: undefined anchors.right: answerChoices.visible ? calendarBox.right : okButton.left anchors.margins: items.horizontalLayout ? 30 : 8 } } } diff --git a/src/activities/categorization/Categorization.qml b/src/activities/categorization/Categorization.qml index 6056ed4e0..ceff9d62b 100644 --- a/src/activities/categorization/Categorization.qml +++ b/src/activities/categorization/Categorization.qml @@ -1,239 +1,239 @@ /* GCompris - categorization.qml * * Copyright (C) 2016 Divyam Madaan * * Authors: * Divyam Madaan * * 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.6 import QtQuick.Controls 1.5 import GCompris 1.0 import "../../core" import "categorization.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core import "." ActivityBase { id: activity onStart: focus = true onStop: {} property string boardsUrl: ":/gcompris/src/activities/categorization/resource/board/" - property bool vert: background.width < background.height + property bool vert: background.width <= background.height property var barAtStart pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/guesscount/resource/backgroundW01.svg" anchors.fill: parent sourceSize.width: parent.width signal start signal stop 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 categoryReview: categoryReview property alias menuScreen: menuScreen property alias menuModel: menuScreen.menuModel property alias dialogActivityConfig: dialogActivityConfig property string mode: "easy" property bool instructionsVisible: true property bool categoryImageChecked: (mode === "easy" || mode === "medium") property bool scoreChecked: (mode === "easy" || mode === "expert") property bool iAmReadyChecked: (mode === "expert") property bool displayUpdateDialogAtStart: true property var details property bool categoriesFallback property alias file: file property var categories: directory.getFiles(boardsUrl) } function hideBar() { barAtStart = ApplicationSettings.isBarHidden; - if(categoryReview.width > categoryReview.height) + if(categoryReview.width >= categoryReview.height) ApplicationSettings.isBarHidden = false; else ApplicationSettings.isBarHidden = true; } onStart: { Activity.init(items, boardsUrl) dialogActivityConfig.getInitialConfiguration() Activity.start() hideBar() } onStop: { dialogActivityConfig.saveDatainConfiguration() ApplicationSettings.isBarHidden = barAtStart; } MenuScreen { id: menuScreen File { id: file onError: console.error("File error: " + msg); } } Directory { id: directory } CategoryReview { id: categoryReview } ExclusiveGroup { id: configOptions } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 5 width: dialogActivityConfig.width height: dialogActivityConfig.height property alias easyModeBox: easyModeBox property alias mediumModeBox: mediumModeBox property alias expertModeBox: expertModeBox GCDialogCheckBox { id: easyModeBox width: column.width - 50 text: qsTr("Put together all the elements from a category (with score)") checked: (items.mode == "easy") ? true : false exclusiveGroup: configOptions onCheckedChanged: { if(easyModeBox.checked) { items.mode = "easy" menuScreen.iAmReady.visible = false } } } GCDialogCheckBox { id: mediumModeBox width: easyModeBox.width text: qsTr("Put together all the elements from a category (without score)") checked: (items.mode == "medium") ? true : false exclusiveGroup: configOptions onCheckedChanged: { if(mediumModeBox.checked) { items.mode = "medium" menuScreen.iAmReady.visible = false } } } GCDialogCheckBox { id: expertModeBox width: easyModeBox.width text: qsTr("Discover a category, grouping elements together") checked: (items.mode == "expert") ? true : false exclusiveGroup: configOptions onCheckedChanged: { if(expertModeBox.checked) { items.mode = "expert" menuScreen.iAmReady.visible = true } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) items.mode = dataToSave["mode"] if(dataToSave && dataToSave["displayUpdateDialogAtStart"]) items.displayUpdateDialogAtStart = (dataToSave["displayUpdateDialogAtStart"] == "true") ? true : false } onSaveData: { dataToSave["data"] = Activity.categoriesToSavedProperties(dataToSave) dataToSave["mode"] = items.mode dataToSave["displayUpdateDialogAtStart"] = items.displayUpdateDialogAtStart ? "true" : "false" } onClose: home() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: menuScreen.started ? withConfig : withoutConfig property BarEnumContent withConfig: BarEnumContent { value: help | home | config } property BarEnumContent withoutConfig: BarEnumContent { value: home | level } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: { if(items.menuScreen.started) activity.home() else if(items.categoryReview.started) Activity.launchMenuScreen() } onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } Loader { id: categoriesFallbackDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("You don't have all the images for this activity. " + "Press Update to get the complete dataset. " + "Press the Cross to play with demo version or 'Never show this dialog later' if you want to never see again this dialog.") button1Text: qsTr("Update the image set") button2Text: qsTr("Never show this dialog later") onClose: items.categoriesFallback = false onButton1Hit: DownloadManager.downloadResource('data2/words/words.rcc') onButton2Hit: { items.displayUpdateDialogAtStart = false; dialogActivityConfig.saveDatainConfiguration() } } anchors.fill: parent focus: true active: items.categoriesFallback && items.displayUpdateDialogAtStart onStatusChanged: if (status == Loader.Ready) item.start() } } } diff --git a/src/activities/categorization/CategoryReview.qml b/src/activities/categorization/CategoryReview.qml index b18c23235..96b256ecd 100644 --- a/src/activities/categorization/CategoryReview.qml +++ b/src/activities/categorization/CategoryReview.qml @@ -1,238 +1,238 @@ /* GCompris - CategoryReview.qml * * Copyright (C) 2016 Divyam Madaan * * Authors: * Divyam Madaan * * 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.6 import GCompris 1.0 import "../../core" import "categorization.js" as Activity Item { id: rootItem property alias score: score property alias categoryDataset: categoryDataset property alias instructionBox: instructionBox property alias categoryImage: categoryImage property bool isDropped: true property bool leftAreaContainsDrag: false property bool rightAreaContainsDrag: false property bool started: rootItem.opacity == 1 - property bool horizontalLayout: categoryBackground.width > categoryBackground.height + property bool horizontalLayout: categoryBackground.width >= categoryBackground.height property alias leftZone: leftZone.model property alias rightZone: rightZone.model property alias middleZone: middleZone.model property alias leftScreen: leftScreen property alias middleScreen: middleScreen property alias rightScreen: rightScreen anchors.fill: parent Loader { id: categoryDataset asynchronous: false } Image { id: categoryBackground source: "qrc:/gcompris/src/activities/categorization/resource/background.svg" anchors.fill: parent sourceSize.width:parent.width Zone { id:leftZone x: 0.012 * middleScreen.width z: 2 y: 0.05 * parent.height spacing: x } Rectangle { id: leftScreen width: parent.width/3 height: parent.height x: 0 color: leftAreaContainsDrag ? "#F9F8B4" : "#F9B4B4" border.width: 5 border.color: "#EC1313" opacity: 0.5 } Zone { id: rightZone spacing: leftZone.x x: leftScreen.width + middleScreen.width + spacing z: 2 anchors.top: categoryBackground.top anchors.topMargin: items.mode != "expert" ? rootItem.categoryImage.height + 0.027 * rightScreen.height : 0.05 * categoryBackground.height } Rectangle { id: rightScreen width: parent.width/3 height: parent.height x: leftScreen.width + middleScreen.width color: rightAreaContainsDrag ? "#F9F8B4" : "#B4F9C5" border.width: 5 border.color: "#13EC52" opacity: 0.5 } Rectangle { id: middleScreen width: parent.width/3 height: parent.height x: leftScreen.width color: "#00FFFFFF" } Rectangle { id: instructionBox anchors.left: categoryBackground.left anchors.right: categoryImage.left anchors.leftMargin: 0.32 * parent.width anchors.rightMargin: 0.03 * parent.width color: "black" opacity: items.instructionsVisible ? 0.85 : 0 z: 3 radius: 10 border.width: 2 width: horizontalLayout ? parent.width/5 : parent.width/3 height: horizontalLayout ? parent.height/6 : parent.height * 0.09 gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } } Zone { id: middleZone spacing: 0.012 * middleScreen.width anchors { left: leftScreen.right right: rightScreen.left top: parent.top topMargin: 0.05 * parent.height bottom: categoryBackground.bottom leftMargin: 0.015 * middleScreen.width } } GCText { id: instructions text: items.mode !== "expert" && items.details && items.details[bar.level-1] && items.details[bar.level - 1].instructions ? items.details[bar.level - 1].instructions : qsTr("Place the majority category images to the right and other images to the left") visible: items.instructionsVisible anchors.fill: instructionBox anchors.bottom: instructionBox.bottom fontSizeMode: Text.Fit wrapMode: Text.Wrap z: 3 color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } Image { id: categoryImage fillMode: Image.PreserveAspectFit source: items.details && items.details[bar.level-1] && items.details[bar.level-1].image ? items.details[bar.level-1].image : "" sourceSize.width: horizontalLayout ? rightZone.width * 0.35 : rightZone.width * 0.35 width: sourceSize.width height: sourceSize.width y: 0.015*parent.height visible: items.categoryImageChecked anchors { left: middleScreen.right leftMargin: 0.15 * rightZone.width } } BarButton { id: validate source: "qrc:/gcompris/src/core/resource/bar_ok.svg" width: horizontalLayout ? rightZone.width * 0.20 : rightZone.width * 0.35 height: width sourceSize.width: width sourceSize.height: height y: parent.height*0.8 z: 2 anchors { rightMargin: 14 * ApplicationInfo.ratio right: parent.right } MouseArea { anchors.fill: parent onClicked: { Activity.allPlaced(); } } } DropArea { id: rightArea anchors.fill: rightZone } DropArea { id: leftArea anchors.fill: leftZone } DialogHelp { id: dialogHelp onClose: home() } Score { id: score visible: items.scoreChecked width: rightZone.width * 0.4 height: width * 0.6 margins: 10 * ApplicationInfo.ratio anchors { top: parent.top right: parent.right left: undefined bottom: undefined } } } Keys.onEscapePressed: { Activity.launchMenuScreen(); } Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = true Activity.launchMenuScreen() } } function stop() { if(items.mode == "expert") items.menuScreen.iAmReady.visible = true focus = false rootItem.visible = false } function start() { focus = true rootItem.visible = true } } diff --git a/src/activities/checkers/Checkers.qml b/src/activities/checkers/Checkers.qml index a829cc3ba..434ad525a 100644 --- a/src/activities/checkers/Checkers.qml +++ b/src/activities/checkers/Checkers.qml @@ -1,466 +1,466 @@ /* GCompris - checkers.qml * * Copyright (C) 2017 Johnny Jazeix * * Authors: * Johnny Jazeix * * 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.6 import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import GCompris 1.0 import "../../core" import "." import "checkers.js" as Activity ActivityBase { id: activity property bool acceptClick: true property bool twoPlayers: false // difficultyByLevel means that at level 1 computer is bad better at last level property bool difficultyByLevel: true onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + 'background-wood.svg' signal start signal stop 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 GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 - property bool isPortrait: (background.height > background.width) + property bool isPortrait: (background.height >= background.width) property int cellSize: items.isPortrait ? Math.min(background.width / (items.numberOfCases + 2), (background.height - controls.height) / (items.numberOfCases + barHeightAddon)) : Math.min(background.width / (items.numberOfCases + 2), background.height / (items.numberOfCases + barHeightAddon)) property var fen: activity.fen property bool twoPlayer: activity.twoPlayers property bool difficultyByLevel: activity.difficultyByLevel property var positions property var pieces: pieces property var squares: squares property var history property var redo_stack property alias redoTimer: redoTimer property int from property bool blackTurn property bool gameOver property var movesToDo: [] property string message property alias trigComputerMove: trigComputerMove property int numberOfCases: 10 Behavior on cellSize { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1000 } } } onStart: { Activity.start(items) } onStop: { Activity.stop() } Grid { anchors { top: parent.top topMargin: items.isPortrait ? 0 : items.cellSize / 2 leftMargin: 10 * ApplicationInfo.ratio rightMargin: 10 * ApplicationInfo.ratio } columns: (items.isPortrait==true) ? 1 : 3 rows: (items.isPortrait==true) ? 2 : 1 width: (items.isPortrait==true) ? undefined : background.width anchors.horizontalCenter: parent.horizontalCenter spacing: 10 horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Column { id: controls anchors { leftMargin: 10 rightMargin: 10 } width: items.isPortrait ? parent.width : Math.max(undo.width * 1.2, Math.min( (background.width * 0.9 - undo.width - chessboard.width), (background.width - chessboard.width) / 2)) GCText { color: "white" anchors.horizontalCenter: parent.horizontalCenter width: parent.width fontSize: smallSize text: items.message horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } Grid { spacing: 10 columns: items.isPortrait ? 3 : 1 anchors.horizontalCenter: parent.horizontalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Button { id: undo height: 30 * ApplicationInfo.ratio text: qsTr("Undo"); style: GCButtonStyle { theme: "light" } onClicked: Activity.undo() enabled: (items.history && items.history.length > 0) ? true : false opacity: enabled ? 1 : 0 Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { id: redo height: 30 * ApplicationInfo.ratio text: qsTr("Redo"); style: GCButtonStyle { theme: "light" } onClicked: { Activity.redo() } enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0 opacity: enabled Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { height: 30 * ApplicationInfo.ratio text: qsTr("Swap"); style: GCButtonStyle { theme: "light" } enabled: items.twoPlayer opacity: enabled onClicked: chessboard.swap() } } } Rectangle { id:boardBg width: items.cellSize * (items.numberOfCases + 0.2) height: items.cellSize * (items.numberOfCases + 0.2) z: 09 color: "#2E1B0C" // The chessboard GridView { id: chessboard cellWidth: items.cellSize cellHeight: items.cellSize width: items.cellSize * items.numberOfCases height: items.cellSize * items.numberOfCases interactive: false keyNavigationWraps: true model: items.numberOfCases*items.numberOfCases layoutDirection: Qt.RightToLeft delegate: square rotation: 180 z: 10 anchors.centerIn: boardBg Component { id: square Image { source: index % 2 + (Math.floor(index / items.numberOfCases) % 2) == 1 ? Activity.url + 'checkers-white.svg' : Activity.url + 'checkers-black.svg'; width: items.cellSize height: items.cellSize } } Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } function swap() { items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') if(chessboard.rotation == 180) chessboard.rotation = 0 else chessboard.rotation = 180 } } } } Repeater { id: squares model: items.positions delegate: square parent: chessboard DropArea { id: square x: items.cellSize * (9 - pos % items.numberOfCases) + spacing / 2 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2 width: items.cellSize - spacing height: items.cellSize - spacing z: 1 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] property bool acceptMove: false property bool jumpable: false property int pos: modelData.pos property int spacing: 6 * ApplicationInfo.ratio Rectangle { id: possibleMove anchors.fill: parent color: parent.containsDrag ? '#803ACAFF' : 'transparent' border.width: parent.acceptMove || parent.jumpable ? 5 : 0 border.color: parent.acceptMove ? '#FF808080' : '#C0808080' radius: parent.acceptMove ? width*0.5 : 0 z: 1 } } function getSquareAt(pos) { for(var i=0; i < squares.count; i++) { if(squares.itemAt(i).pos === pos) return squares.itemAt(i) } return undefined } } Repeater { id: pieces model: items.positions delegate: piece parent: chessboard Piece { id: piece sourceSize.width: items.cellSize width: items.cellSize - spacing height: items.cellSize - spacing source: img ? Activity.url + img + '.svg' : '' img: modelData.img x: items.cellSize * (items.numberOfCases - 1 - pos % items.numberOfCases) + spacing / 2 y: items.cellSize * Math.floor(pos / items.numberOfCases) + spacing / 2 z: 1 pos: modelData.pos newPos: modelData.pos rotation: - chessboard.rotation property int spacing: 6 * ApplicationInfo.ratio Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent enabled: !items.gameOver drag.target: parent onPressed: { piece.Drag.keys = ['acceptMe'] parent.z = 100 if(parent.isWhite == 1 && !items.blackTurn || parent.isWhite == 0 && items.blackTurn) { items.from = parent.newPos Activity.showPossibleMoves(items.from) } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { Activity.moveTo(items.from, parent.newPos) } } onReleased: { // If no target or move not possible, we reset the position if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { var pos = parent.pos // Force recalc of the old x,y position parent.pos = -1 if(pieces.getPieceAt(pos)) pieces.getPieceAt(pos).move(pos) } } } } function moveTo(from, to, moves) { items.movesToDo.push({"from": from, "to": to, "move": moves}); if(items.movesToDo.length == 1) { moveInternal(); } } function moveInternal() { var moveToDo = items.movesToDo[0] var from = moveToDo.from; var to = moveToDo.to; var moves = moveToDo.move; var fromPiece = getPieceAt(from) var toPiece = getPieceAt(to) if(moves.jumps.length != 0) items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') else items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') toPiece.hide(from) movingPiece = fromPiece if(moves.jumps.length != 0) { listJumps = moves.jumps } else { // create the move if needed listJumps = [Activity.viewPosToEngine(from), Activity.viewPosToEngine(to)] } } function promotion(to) { var toPiece = getPieceAt(to) toPiece.promotion() } function getPieceAt(pos) { for(var i=0; i < pieces.count; i++) { if(pieces.itemAt(i).newPos == pos) return pieces.itemAt(i) } return undefined } } property var movingPiece: undefined property var listJumps property bool restartNeeded: false onListJumpsChanged: { if(listJumps.length >= 2) { if(!animationTimer.isRunning) animationTimer.restart(); else restartNeeded = true; } } SequentialAnimation { id: animationTimer onRunningChanged: { if(!running && restartNeeded) start() } ScriptAction { script: { restartNeeded = false var to = Activity.engineToViewPos(listJumps[1]) movingPiece.move(to) } } PauseAnimation { duration: 200 } ScriptAction { script: { listJumps.shift() // only shifting does not trigger the onChanged var tmp = listJumps listJumps = tmp // only change player once all the jumps have been done if(listJumps.length == 1) { items.movesToDo.shift() if(items.movesToDo.length > 0) { pieces.moveInternal() } else { Activity.refresh() movingPiece = undefined } } } } } Timer { id: trigComputerMove repeat: false interval: 400 onTriggered: Activity.randomMove() } // Use to redo the computer move after the user move Timer { id: redoTimer repeat: false interval: 400 onTriggered: { acceptClick = true; Activity.moveByEngine(move) } property var move function moveByEngine(engineMove) { move = engineMove redoTimer.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | (items.twoPlayer && !items.gameOver ? 0 : reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { trigComputerMove.stop() Activity.initLevel() } } Bonus { id: bonus } } } diff --git a/src/activities/chess/Chess.qml b/src/activities/chess/Chess.qml index 3f0b2f001..6daba0f57 100644 --- a/src/activities/chess/Chess.qml +++ b/src/activities/chess/Chess.qml @@ -1,462 +1,462 @@ /* GCompris - chess.qml * * Copyright (C) 2015 Bruno Coudoin * * Authors: * Bruno Coudoin (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.6 import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import GCompris 1.0 import "../../core" import "." import "chess.js" as Activity ActivityBase { id: activity property bool acceptClick: true property bool twoPlayers: false property int coordsOpacity: 1 // difficultyByLevel means that at level 1 computer is bad better at last level property bool difficultyByLevel: true property var fen: [ ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"], ["initial state", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 1 1"] ] onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + 'background-wood.svg' signal start signal stop 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 GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 - property bool isPortrait: (background.height > background.width) + property bool isPortrait: (background.height >= background.width) property int cellSize: items.isPortrait ? Math.min((background.width - numbers.childrenRect.width) / (8 + 2), (background.height - controls.height - letters.childrenRect.height) / (8 + barHeightAddon)) : Math.min((background.width - numbers.childrenRect.width) / (8 + 2), (background.height - letters.childrenRect.height) / (8.5 + barHeightAddon)) property var fen: activity.fen property bool twoPlayer: activity.twoPlayers property bool difficultyByLevel: activity.difficultyByLevel property var positions property var pieces: pieces property var squares: squares property var history property var redo_stack property alias redoTimer: redoTimer property int from property bool blackTurn property bool gameOver property string message property bool isWarningMessage property alias trigComputerMove: trigComputerMove Behavior on cellSize { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1000 } } } onStart: { Activity.start(items) } onStop: { Activity.stop() } Grid { anchors { top: parent.top topMargin: items.isPortrait ? 0 : items.cellSize leftMargin: 10 * ApplicationInfo.ratio rightMargin: 10 * ApplicationInfo.ratio } columns: (items.isPortrait==true)?1:3 rows: (items.isPortrait==true)?2:1 width: (items.isPortrait==true)?undefined:background.width anchors.horizontalCenter: parent.horizontalCenter spacing: 10 horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Column { id: controls anchors { leftMargin: 10 rightMargin: 10 } z: 20 width: items.isPortrait ? parent.width : Math.max(undo.width * 1.2, Math.min( (background.width * 0.9 - undo.width - chessboard.width), (background.width - chessboard.width) / 2)) GCText { color: items.isWarningMessage ? "red" : "white" anchors.horizontalCenter: parent.horizontalCenter width: parent.width fontSize: smallSize text: items.message horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } Grid { spacing: 10 columns: items.isPortrait ? 3 : 1 anchors.horizontalCenter: parent.horizontalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Button { id: undo height: 30 * ApplicationInfo.ratio text: qsTr("Undo"); style: GCButtonStyle { theme: "light" } onClicked: Activity.undo() enabled: items.history.length > 0 ? 1 : 0 opacity: enabled Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { id: redo height: 30 * ApplicationInfo.ratio text: qsTr("Redo"); style: GCButtonStyle { theme: "light" } onClicked: { if (!twoPlayers) { acceptClick = false; Activity.redo() } else { Activity.redo() } } enabled: items.redo_stack.length > 0 && acceptClick ? 1 : 0 opacity: enabled Behavior on opacity { PropertyAnimation { easing.type: Easing.InQuad duration: 200 } } } Button { height: 30 * ApplicationInfo.ratio text: qsTr("Swap"); style: GCButtonStyle { theme: "light" } enabled: items.twoPlayer opacity: enabled onClicked: chessboard.swap() } } } Rectangle { id:boardBg width: items.cellSize * 8.2 height: boardBg.width z: 08 color: "#452501" // The chessboard GridView { id: chessboard cellWidth: items.cellSize cellHeight: items.cellSize width: items.cellSize * 8 height: chessboard.width interactive: false keyNavigationWraps: true model: 64 layoutDirection: Qt.RightToLeft delegate: square rotation: 180 z: 10 anchors.centerIn: boardBg Component { id: square Image { source: index % 2 + (Math.floor(index / 8) % 2) == 1 ? Activity.url + 'chess-white.svg' : Activity.url + 'chess-black.svg'; width: items.cellSize height: items.cellSize } } Behavior on rotation { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 1400 } } function swap() { items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/flip.wav') coordsOpacity = 0 timerSwap.start() if(chessboard.rotation == 180) chessboard.rotation = 0 else chessboard.rotation = 180 } } Timer { id: timerSwap interval: 1500 running: false repeat: false onTriggered: coordsOpacity = 1 } Grid { id: letters anchors.left: chessboard.left anchors.top: chessboard.bottom opacity: coordsOpacity Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } Repeater { id: lettersA model: chessboard.rotation == 0 ? ["F", "G", "F", "E", "D", "C", "B", "A"] : ["A", "B", "C", "D", "E", "F", "G", "H"] GCText { x: items.cellSize * (index % 8) + (items.cellSize/2-width/2) y: items.cellSize * Math.floor(index / 8) text: modelData color: "#CBAE7B" } } } Grid { id: numbers anchors.left: chessboard.right anchors.top: chessboard.top opacity: coordsOpacity Behavior on opacity { PropertyAnimation { easing.type: Easing.InOutQuad; duration: 500} } Repeater { model: chessboard.rotation == 0 ? ["1", "2", "3", "4", "5", "6", "7", "8"] : ["8", "7", "6", "5", "4", "3", "2", "1"] GCText { x: items.cellSize * Math.floor(index / 8) + width y: items.cellSize * (index % 8) + (items.cellSize/2-height/2) text: modelData color: "#CBAE7B" } } } Rectangle { id: boardBorder width: items.cellSize * 10 height: boardBorder.width anchors.centerIn: boardBg z: -1 color: "#542D0F" border.color: "#3A1F0A" border.width: items.cellSize * 0.1 } } } Repeater { id: squares model: items.positions delegate: square parent: chessboard DropArea { id: square x: items.cellSize * (7 - pos % 8) + spacing / 2 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 width: items.cellSize - spacing height: square.width z: 1 keys: acceptMove ? ['acceptMe'] : ['sorryNo'] property bool acceptMove : false property int pos: modelData.pos property int spacing: 6 * ApplicationInfo.ratio Rectangle { id: possibleMove anchors.fill: parent color: parent.containsDrag ? '#803ACAFF' : 'transparent' border.width: parent.acceptMove ? 5 : 0 border.color: '#FF808080' z: 1 } } function getSquareAt(pos) { for(var i=0; i < squares.count; i++) { if(squares.itemAt(i).pos === pos) return squares.itemAt(i) } return(undefined) } } Repeater { id: pieces model: items.positions delegate: piece parent: chessboard Piece { id: piece sourceSize.width: items.cellSize width: items.cellSize - spacing height: piece.width source: img ? Activity.url + img + '.svg' : '' img: modelData.img x: items.cellSize * (7 - pos % 8) + spacing / 2 y: items.cellSize * Math.floor(pos / 8) + spacing / 2 z: 1 pos: modelData.pos newPos: modelData.pos rotation: - chessboard.rotation property int spacing: 6 * ApplicationInfo.ratio Drag.active: dragArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent enabled: !items.gameOver drag.target: parent onPressed: { piece.Drag.keys = ['acceptMe'] parent.z = 100 if(parent.isWhite == 1 && !items.blackTurn || parent.isWhite == 0 && items.blackTurn) { items.from = parent.newPos Activity.showPossibleMoves(items.from) } else if(items.from != -1 && squares.getSquareAt(parent.newPos)['acceptMove']) { Activity.moveTo(items.from, parent.newPos) } } onReleased: { // If no target or move not possible, we reset the position if(!piece.Drag.target || (items.from != -1 && !Activity.moveTo(items.from, piece.Drag.target.pos))) { var pos = parent.pos // Force recalc of the old x,y position parent.pos = -1 pieces.getPieceAt(pos).move(pos) } } } } function moveTo(from, to) { var fromPiece = getPieceAt(from) var toPiece = getPieceAt(to) if(toPiece.img != '') items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') else items.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') toPiece.hide(from) fromPiece.move(to) } function promotion(to) { var toPiece = getPieceAt(to) toPiece.promotion() } function getPieceAt(pos) { for(var i=0; i < pieces.count; i++) { if(pieces.itemAt(i).newPos == pos) return pieces.itemAt(i) } return(undefined) } } Timer { id: trigComputerMove repeat: false interval: 400 onTriggered: Activity.randomMove() } // Use to redo the computer move after the user move Timer { id: redoTimer repeat: false interval: 400 onTriggered: { acceptClick = true; Activity.moveByEngine(move) } property var move function moveByEngine(engineMove) { move = engineMove redoTimer.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | (items.twoPlayer ? 0 : level) | (items.twoPlayer && !items.gameOver ? 0 : reload) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { trigComputerMove.stop() Activity.initLevel() } } Bonus { id: bonus } } } diff --git a/src/activities/crane/Crane.qml b/src/activities/crane/Crane.qml index f9ae16f78..76e87bcc4 100644 --- a/src/activities/crane/Crane.qml +++ b/src/activities/crane/Crane.qml @@ -1,486 +1,486 @@ /* GCompris - Crane.qml * * Copyright (C) 2016 Stefan Toncu * * Authors: * (GTK+ version) * Stefan Toncu (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "crane.js" as Activity ActivityBase { id: activity // Overload this in your activity to change it // Put you default-.json files in it property string dataSetUrl: "qrc:/gcompris/src/activities/crane/resource/" onStart: focus = true onStop: {} pageComponent: Image { id: background source: activity.dataSetUrl+"background.svg" fillMode: Image.PreserveAspectCrop anchors.fill: parent sourceSize.width: Math.max(parent.width, parent.height) signal start signal stop 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 board: board property alias grid: grid property alias repeater: repeater property alias modelRepeater: modelRepeater property alias gridRepeater: gridRepeater property alias showGrid1: showGrid1 property int selected property int columns property int rows property bool ok: true property int sensivity: 80 property bool gameFinished: false property bool pieceIsMoving: false } onStart: { Activity.start(items) } onStop: { Activity.stop() } - property bool portrait: height > width ? true : false + property bool portrait: height >= width ? true : false property bool inLine: true Keys.onPressed: { if (event.key === Qt.Key_Left){ Activity.move("left") left.opacity = 0.6 } else if (event.key === Qt.Key_Right){ Activity.move("right") right.opacity = 0.6 } else if (event.key === Qt.Key_Up){ Activity.move("up") up.opacity = 0.6 } else if (event.key === Qt.Key_Down){ Activity.move("down") down.opacity = 0.6 } else if (event.key === Qt.Key_Space || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Return) Activity.move("next") } Keys.onReleased: { up.opacity = 1 down.opacity = 1 left.opacity = 1 right.opacity = 1 } //implementation of Swipe effect MouseArea { anchors.fill: parent property int startX; property int startY; onPressed: { startX = mouse.x; startY = mouse.y; } onReleased: Activity.gesture(mouse.x - startX, mouse.y - startY) } Rectangle { id: board color: "#b9e2f0" radius: width * 0.03 border.color: "#77c0d9" border.width: width * 0.02 z: 1 clip: true anchors { verticalCenter: crane_vertical.verticalCenter right: crane_vertical.left margins: 15 } width: background.portrait ? parent.width * 0.65 : ((parent.width - anchors.margins * 3 - crane_vertical.width) / 2 ) * 0.9 height: background.portrait ? (parent.height - bar.height * 1.45 - crane_top.height - crane_body.height ) / 2 : (parent.height - bar.height * 1.45 - crane_top.height - crane_body.height) * 0.9 } Grid { id: showGrid1 columns: items.columns rows: items.rows z: 1 anchors.fill: board layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { maskSource: board } Repeater { id: gridRepeater Rectangle { width: board.width/items.columns height: board.height/items.rows color: "transparent" border.width: 2 border.color: "#77c0d9" } } } Grid { id: grid columns: items.columns rows: items.rows z: 4 anchors.fill: board Repeater { id: repeater Image { id: figure sourceSize.height: board.width/items.columns sourceSize.width: board.height/items.rows width: board.width/items.columns height: board.height/items.rows property int initialIndex: -1 property alias anim: anim property int distance property int indexChange property int startPoint property string animationProperty property int _index: index // make current index accessible from outside SequentialAnimation { id: anim PropertyAction { target: items; property: "ok"; value: "false"} NumberAnimation { target: figure; property: figure.animationProperty; from: figure.startPoint; to: figure.startPoint + distance; duration: 200 } PropertyAction { target: figure; property: "opacity"; value: 0 } NumberAnimation { target: figure; property: figure.animationProperty; from: figure.startPoint + distance; to: figure.startPoint; duration: 0; } PropertyAction { target: figure; property: "opacity"; value: 1 } PropertyAction { target: items.repeater.itemAt(items.selected + indexChange); property: "source"; value: figure.source } PropertyAction { target: items.repeater.itemAt(items.selected + indexChange); property: "initialIndex"; value: figure.initialIndex } PropertyAction { target: figure; property: "initialIndex"; value: -1 } PropertyAction { target: figure; property: "source"; value: "" } PropertyAction { target: items; property: "ok"; value: "true"} PropertyAction { target: items; property: "pieceIsMoving"; value: "false"} ScriptAction { script: Activity.checkAnswer() } } MouseArea { anchors.fill: parent // Swipe effect property int startX; property int startY; onPressed: { startX = mouse.x; startY = mouse.y; } onReleased: Activity.gesture(mouse.x - startX, mouse.y - startY) // Select a figure with mouse/touch onClicked: { if (source != "" && !items.pieceIsMoving) items.selected = index } } } } } Image { id: selected source: activity.dataSetUrl+"selected.svg" sourceSize.width: board.width/items.columns sourceSize.height: board.height/items.rows width: board.width/items.columns height: board.height/items.rows opacity: 1 property var newCoord: items.selected == 0 ? grid : items.repeater.mapToItem(background,items.repeater.itemAt(items.selected).x, items.repeater.itemAt(items.selected).y) x: newCoord.x y: newCoord.y z: 100 Behavior on x { NumberAnimation { duration: 200 } } Behavior on y { NumberAnimation { duration: 200 } } } Rectangle { id: modelBoard color: "#f0b9d2" radius: width * 0.03 border.color: "#e294b7" border.width: width * 0.02 z: 1 anchors { left: background.portrait ? board.left : crane_vertical.right top: background.portrait ? crane_body.bottom : background.inLine ? board.top : parent.top topMargin: background.portrait ? board.anchors.margins : background.inLine ? 0 : crane_top.height * 1.5 leftMargin: background.portrait ? 0 : board.anchors.margins * 1.2 margins: board.anchors.margins } width: board.width height: board.height } Grid { id: modelGrid columns: items.columns rows: items.rows anchors.fill: modelBoard z: 4 Repeater { id: modelRepeater Image { id: modelFigure sourceSize.height: board.height/items.rows sourceSize.width: board.width/items.columns width: board.width/items.columns height: board.height/items.rows } } } Grid { id: showGrid2 columns: items.columns rows: items.rows z: 1 opacity: showGrid1.opacity anchors.fill: modelBoard layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { maskSource: modelBoard } Repeater { id: gridRepeater2 model: gridRepeater.model Rectangle { width: modelBoard.width/items.columns height: modelBoard.height/items.rows color: "transparent" border.width: 2 border.color: showGrid1.opacity == 1 ? "#e294b7" : "transparent" } } } Image { id: crane_top source: activity.dataSetUrl+"crane_up.svg" sourceSize.width: background.portrait ? background.width * 0.8 : background.width * 0.5 width: background.portrait ? background.width * 0.8 : background.width * 0.5 fillMode: Image.PreserveAspectFit z: 4 anchors { top: parent.top right: crane_vertical.right rightMargin: 0 margins: board.anchors.margins } } Image { id: crane_vertical source: activity.dataSetUrl+"crane_vertical.svg" sourceSize.height: background.portrait ? background.height * 0.5 : background.height * 0.73 height: background.portrait ? background.height * 0.5 : background.height * 0.73 fillMode: Image.PreserveAspectFit anchors { top: crane_top.top right: background.portrait ? parent.right : parent.horizontalCenter rightMargin: background.portrait ? width / 2 : - width / 2 topMargin: board.anchors.margins } } Image { id: crane_body source: activity.dataSetUrl+"crane_only.svg" z: 2 sourceSize.width: parent.width / 5 sourceSize.height: parent.height/ 3.6 mirror: background.portrait ? true : false anchors { top: crane_vertical.bottom topMargin: - (height / 1.8) right: crane_vertical.right rightMargin: background.portrait ? board.anchors.margins : - crane_body.width + crane_vertical.width margins: board.anchors.margins } } Image { id: crane_wire source: activity.dataSetUrl+"crane-wire.svg" z: 1 sourceSize.width: parent.width / 22 sourceSize.height: parent.width / 17 anchors { right: crane_body.left bottom: crane_command.verticalCenter rightMargin: -10 bottomMargin: -10 } } Image { id: crane_command source: activity.dataSetUrl+"command.svg" sourceSize.width: background.portrait ? parent.width / 2.7 : parent.width / 3.5 sourceSize.height: background.portrait ? parent.height / 3.5 : parent.height / 4 width: background.portrait ? parent.width / 2.7 : parent.width / 3.5 height: background.portrait ? parent.height / 3.5 : parent.height / 4 anchors { top: crane_body.top bottom: crane_body.bottom right: crane_wire.left rightMargin: 0 topMargin: background.portrait ? 0 : board.anchors.margins * 1.5 bottomMargin: background.portrait ? 0 : board.anchors.margins * 1.5 } Controls { id: up source: activity.dataSetUrl+"arrow_up.svg" anchors { left: parent.left leftMargin: parent.width / 11 } command: "up" } Controls { id: down source: activity.dataSetUrl+"arrow_down.svg" anchors { left: up.right leftMargin: parent.width / 30 } command: "down" } Controls { id: left source: activity.dataSetUrl+"arrow_left.svg" anchors { right: right.left rightMargin: parent.width / 30 } command: "left" } Controls { id: right source: activity.dataSetUrl+"arrow_right.svg" anchors { right: parent.right rightMargin: parent.width / 11 } command: "right" } } Rectangle { id: cable color: "#373737" width: 5 height: convert.y - crane_top.y x: convert.x + board.width / items.columns / 2 z: 3 anchors.top: crane_top.top anchors.topMargin: 10 property var convert: items.selected == 0 ? grid : items.repeater.mapToItem(background,items.repeater.itemAt(items.selected).x, items.repeater.itemAt(items.selected).y) Behavior on x { NumberAnimation { duration: 200 } } Behavior on height { NumberAnimation { duration: 200 } } } 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/digital_electricity/DigitalElectricity.qml b/src/activities/digital_electricity/DigitalElectricity.qml index 871603746..535c8ebfc 100644 --- a/src/activities/digital_electricity/DigitalElectricity.qml +++ b/src/activities/digital_electricity/DigitalElectricity.qml @@ -1,529 +1,529 @@ /* GCompris - DigitalElectricity.qml * * Copyright (C) 2016 Pulkit Gupta * * Authors: * Bruno Coudoin (GTK+ version) * Pulkit Gupta (Qt Quick port) * Rudra Nil Basu (Qt Quick port) * Timothée Giet (mouse drag refactoring) * * 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.6 import GCompris 1.0 import "../../core" import "digital_electricity.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property string mode: "tutorial" property bool isTutorialMode: mode == "tutorial" ? true : false pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "texture02.png" fillMode: Image.Tile signal start signal stop - property bool hori: background.width > background.height + property bool hori: background.width >= background.height Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { if ((event.key == Qt.Key_Return || event.key == Qt.Key_Enter) && okButton.enabled) { Activity.checkAnswer() } if (event.key == Qt.Key_Plus) { Activity.zoomIn() } if (event.key == Qt.Key_Minus) { Activity.zoomOut() } if (event.key == Qt.Key_Right) { playArea.x -= 200; } if (event.key == Qt.Key_Left) { playArea.x += 200 } if (event.key == Qt.Key_Up) { playArea.y += 200 } if (event.key == Qt.Key_Down) { playArea.y -= 200 } if (playArea.x >= mousePan.drag.maximumX) { playArea.x = mousePan.drag.maximumX } if (playArea.y >= mousePan.drag.maximumY) { playArea.y = mousePan.drag.maximumY } if (playArea.x <= mousePan.drag.minimumX) { playArea.x = mousePan.drag.minimumX } if (playArea.y <= mousePan.drag.minimumY) { playArea.y = mousePan.drag.minimumY } } onHoriChanged: { if (hori == true) { playArea.x += items.toolsMargin playArea.y -= items.toolsMargin } else { playArea.x -= items.toolsMargin playArea.y += items.toolsMargin } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias playArea: playArea property alias mousePan: mousePan property alias bar: bar property alias bonus: bonus property alias availablePieces: availablePieces property alias toolTip: toolTip property alias infoTxt: infoTxt property alias truthTablesModel: truthTablesModel property alias displayTruthTable: inputOutputTxt.displayTruthTable property alias dataset: dataset property alias tutorialDataset: tutorialDataset property alias infoImage: infoImage property bool isTutorialMode: activity.isTutorialMode property alias tutorialInstruction: tutorialInstruction property real toolsMargin: 90 * ApplicationInfo.ratio property real zoomLvl: 0.25 } Loader { id: dataset asynchronous: false } Dataset { id: tutorialDataset } IntroMessage { id: tutorialInstruction intro: [] textContainerWidth: background.hori ? parent.width - inputComponentsContainer.width - items.toolsMargin : 0.9 * background.width textContainerHeight: background.hori ? 0.5 * parent.height : parent.height - inputComponentsContainer.height - (bar.height * 2) - items.toolsMargin anchors { fill: undefined top: background.hori ? parent.top : inputComponentsContainer.bottom topMargin: 10 right: parent.right rightMargin: 5 left: background.hori ? inputComponentsContainer.right : parent.left leftMargin: 5 } z: 5 } onStart: { Activity.start(items) } onStop: { Activity.stop() } Rectangle { id: visibleArea color: "#00000000" width: background.hori ? background.width - items.toolsMargin - 10 : background.width - 10 height: background.hori ? background.height - bar.height - items.toolsMargin - 10 : background.height - bar.height - 10 anchors { fill: undefined top: background.hori ? parent.top : inputComponentsContainer.bottom topMargin: 5 right: parent.right rightMargin: 5 left: background.hori ? inputComponentsContainer.right : parent.left leftMargin: 5 bottom: bar.top bottomMargin: 20 } z: 6 GCText { id: infoTxt anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 2 } fontSizeMode: Text.Fit minimumPixelSize: 10 font.pixelSize: 150 color: "white" horizontalAlignment: Text.AlignHLeft width: Math.min(implicitWidth, 0.90 * parent.width) height: inputOutputTxt.visible == false ? Math.min(implicitHeight, 0.7 * parent.height) : Math.min(implicitHeight, (inputOutputTxt.inputs > 2 ? 0.3 : 0.4) * parent.height) wrapMode: TextEdit.WordWrap visible: false z: 4 } Rectangle { id: infoTxtContainer anchors.fill: parent opacity: 1 radius: 10 color: "#373737" border.width: 2 border.color: "#F2F2F2" visible: infoTxt.visible MouseArea { anchors.fill: parent onClicked: infoTxt.visible = false } z: 3 } Image { id: infoImage property bool imgVisible: false height: source == "" ? 0 : parent.height * 0.3 - 10 width: source == "" ? 0 : parent.width - 10 fillMode: Image.PreserveAspectFit visible: infoTxt.visible && imgVisible anchors { top: infoTxt.bottom horizontalCenter: infoTxtContainer.horizontalCenter } z: 5 } ListModel { id: truthTablesModel property int rows property int columns property int inputs property int outputs } Row { id: inputOutputTxt z: 5 property bool displayTruthTable visible: infoTxt.visible && displayTruthTable property int inputs: truthTablesModel.inputs property int outputs: truthTablesModel.outputs property int cellSize: (inputs > 2 ? 0.65 : 0.5) * parent.height / (truthTablesModel.rows + 1) property int maxWidth: Math.min(cellSize, parent.width * 0.95 / truthTablesModel.columns) property int minSize: 2.5 * cellSize height: cellSize anchors { top: infoTxt.bottom horizontalCenter: parent.horizontalCenter } Rectangle { color: "#A7D9F9" width: inputOutputTxt.inputs > 1 ? inputOutputTxt.maxWidth * inputOutputTxt.inputs : inputOutputTxt.minSize height: inputOutputTxt.cellSize border.color: "#373737" border.width: 1 GCText { anchors.centerIn: parent fontSizeMode: Text.Fit minimumPixelSize: 10 color: "#353535" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter height: parent.height width: parent.width text: qsTr("Input") } } Rectangle { color: "#A7F9DD" width: inputOutputTxt.outputs > 1 ? inputOutputTxt.maxWidth * inputOutputTxt.outputs : inputOutputTxt.minSize height: inputOutputTxt.cellSize border.color: "#373737" border.width: 1 GCText { anchors.centerIn: parent fontSizeMode: Text.Fit minimumPixelSize: 10 color: "#353535" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter height: parent.height width: parent.width text: qsTr("Output") } } } Grid { id: truthTable rows: truthTablesModel.rows columns: truthTablesModel.columns height: rows * inputOutputTxt.cellSize z: 5 visible: inputOutputTxt.visible anchors { top: inputOutputTxt.bottom horizontalCenter: parent.horizontalCenter } Repeater { id: repeater model: truthTablesModel delegate: blueSquare Component { id: blueSquare Rectangle { width: ((index % truthTable.columns) / (truthTablesModel.inputs - 1)) <= 1 ? (inputOutputTxt.inputs > 1 ? inputOutputTxt.maxWidth : inputOutputTxt.minSize) : (inputOutputTxt.outputs > 1 ? inputOutputTxt.maxWidth : inputOutputTxt.minSize) height: inputOutputTxt.cellSize border.color: "#373737" border.width: 1 color: { if(truthTablesModel.inputs == 1) { return index%2 == 0 ? "#A7D9F9" : "#A7F9DD" } else { return ((index % truthTable.columns) / (truthTablesModel.inputs - 1)) <= 1 ? "#A7D9F9" : "#A7F9DD" } } GCText { id: truthTableValue anchors.centerIn: parent fontSizeMode: Text.Fit minimumPixelSize: 10 color: "#353535" horizontalAlignment: Text.AlignHCenter height: parent.height width: parent.width text: value } } } } } } Rectangle { id: playArea color: "#10000000" x: background.hori ? items.toolsMargin : 0 y: background.hori ? 0 : items.toolsMargin width: background.hori ? background.width * 4 - items.toolsMargin : background.width * 4 height: background.hori ? background.height * 4 - (bar.height * 1.1) : background.height * 4 - (bar.height * 1.1) - items.toolsMargin PinchArea { id: pinchZoom anchors.fill: parent onPinchFinished: { if (pinch.scale < 1) { Activity.zoomOut() } if (pinch.scale > 1) { Activity.zoomIn() } } MouseArea { id: mousePan anchors.fill: parent scrollGestureEnabled: false //needed for pinchZoom drag.target: playArea drag.axis: Drag.XandYAxis drag.minimumX: - playArea.width * items.zoomLvl drag.maximumX: background.hori ? items.toolsMargin : 0 drag.minimumY: - playArea.height * items.zoomLvl drag.maximumY: background.hori ? 0 : items.toolsMargin onClicked: { Activity.deselect() availablePieces.hideToolbar() } } } } Rectangle { id: inputComponentsContainer width: background.hori ? items.toolsMargin : background.width height: background.hori ? background.height : items.toolsMargin color: "#4A3823" anchors.left: parent.left Image { anchors.fill: parent anchors.rightMargin: background.hori ? 3 * ApplicationInfo.ratio : 0 anchors.bottomMargin: background.hori ? 0 : 3 * ApplicationInfo.ratio source: Activity.url + "texture01.png" fillMode: Image.Tile ListWidget { id: availablePieces hori: background.hori } } z: 10 } Rectangle { id: toolTip anchors { bottom: bar.top bottomMargin: 10 left: inputComponentsContainer.left leftMargin: 5 } width: toolTipTxt.width + 10 height: toolTipTxt.height + 5 color: "#373737" opacity: 1 radius: 10 z: 100 border.width: 2 border.color: "#F2F2F2" property alias text: toolTipTxt.text Behavior on opacity { NumberAnimation { duration: 120 } } function show(newText) { if(newText) { text = newText opacity = 1 } else { opacity = 0 } } GCText { id: toolTipTxt anchors.centerIn: parent fontSize: regularSize color: "white" horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias modesComboBox: modesComboBox property var availableModes: [ { "text": qsTr("Tutorial Mode"), "value": "tutorial" }, { "text": qsTr("Free Mode"), "value": "free" }, ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: modesComboBox model: availableModes background: dialogActivityConfig label: qsTr("Select your Mode") } } } } onClose: home(); onLoadData: { if(dataToSave && dataToSave["modes"]) { activity.mode = dataToSave["modes"]; } } onSaveData: { var newMode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.modesComboBox.currentIndex].value; if (newMode !== activity.mode) { activity.mode = newMode; dataToSave = {"modes": activity.mode}; Activity.reset() } } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableModes.length; i ++) { if(dialogActivityConfig.configItem.availableModes[i].value === activity.mode) { dialogActivityConfig.configItem.modesComboBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelp onClose: home() } BarButton { id: okButton visible: activity.isTutorialMode anchors { bottom: bar.top right: parent.right rightMargin: 10 * ApplicationInfo.ratio bottomMargin: height * 0.5 } source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 60 * ApplicationInfo.ratio enabled: !tutorialInstruction.visible onClicked: Activity.checkAnswer() } Bar { id: bar content: BarEnumContent { value: help | home | ( activity.isTutorialMode ? level : 0) | reload | config} onHelpClicked: {displayDialog(dialogHelp)} onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reset() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/erase/Block.qml b/src/activities/erase/Block.qml index b65fe882d..d050c2348 100644 --- a/src/activities/erase/Block.qml +++ b/src/activities/erase/Block.qml @@ -1,110 +1,110 @@ /* GCompris - Block.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (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.6 import "erase.js" as Activity import "../../core" import GCompris 1.0 Image { id: block property Item main property Item bar property Item background property double ix property double iy property int nbx property int nby x: ix * main.width / nbx y: iy * (main.height - bar.height) / (nby + getMultipleOfRatioToAdjustHeight() * ApplicationInfo.ratio) width: main.width / nbx height: (main.height - bar.height) / (nby + getMultipleOfRatioToAdjustHeight() * ApplicationInfo.ratio) signal enter signal leave property string type property int counter: 0 function getMultipleOfRatioToAdjustHeight() { - return (background.width > background.height + 40 * ApplicationInfo.ratio) ? 0.125 : 0.625 + return (background.width >= background.height + 40 * ApplicationInfo.ratio) ? 0.125 : 0.625 } onEnter: { if(opacity == 1.0) { playSound() block.opacity = 0 } } onLeave: { if(opacity != 0) { block.opacity = 1.0 } } Behavior on opacity { PropertyAnimation { duration: 200 } } onOpacityChanged: { if (opacity == 0) { mouseArea.enabled = false mouseArea.hoverEnabled = false Activity.blockKilled() } } MouseArea { id: mouseArea anchors.fill: parent enabled: block.type !== "erase" || !ApplicationInfo.isMobile hoverEnabled: block.type === "erase" && !ApplicationInfo.isMobile onClicked: { if(block.type === "click") { enabled = false block.enter() } } onDoubleClicked: { if(block.type === "double_click") { enabled = false block.enter() } } onEntered: { if(block.type === "erase") { block.enter() } } onExited: { if(block.type === "erase") { block.leave() } } } function playSound() { activity.audioEffects.play( ix % 2 ? "qrc:/gcompris/src/activities/erase/resource/eraser1.wav" : "qrc:/gcompris/src/activities/erase/resource/eraser2.wav") } } diff --git a/src/activities/erase/Erase.qml b/src/activities/erase/Erase.qml index f5992677d..7e1b633c8 100644 --- a/src/activities/erase/Erase.qml +++ b/src/activities/erase/Erase.qml @@ -1,141 +1,141 @@ /* GCompris - Erase.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (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.6 import GCompris 1.0 import "../../core" import "erase.js" as Activity ActivityBase { id: activity focus: true property string type: "erase" onStart: {} onStop: {} pageComponent: Image { id: background signal start signal stop focus: true fillMode: Image.PreserveAspectCrop source: Activity.url + Activity.getFirstImage() sourceSize.width: Math.max(parent.width, parent.height) Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property alias background: background property alias blocks: blocks property alias bar: bar property alias bonus: bonus property int nbSubLevel: 6 property int currentSubLevel: 0 } onStart: Activity.start(main, items, type) onStop: { Activity.stop() } MultiPointTouchArea { anchors.fill: parent onTouchUpdated: { for(var i in touchPoints) { var touch = touchPoints[i] var newBlock = rootItem.childAt(touch.x, touch.y) if(newBlock) newBlock.enter() var previousBlock = rootItem.childAt(touch.previousX, touch.previousY) if(previousBlock !== newBlock && previousBlock != repeater) previousBlock.leave() } } } Item { id: rootItem } ListModel { id: blocks } Repeater { id: repeater model: blocks parent: rootItem Block { id: modelData nbx: nx nby: ny ix: a iy: b opacity: op source: img type: activity.type main: MAIN bar: BAR background: items.background } } DialogHelp { id: dialogHelpLeftRight onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } Score { id: score anchors { - bottom: (background.width > background.height + 40 * ApplicationInfo.ratio) ? background.bottom : bar.top + bottom: (background.width >= background.height + 40 * ApplicationInfo.ratio) ? background.bottom : bar.top bottomMargin: 10 * ApplicationInfo.ratio right: parent.right rightMargin: 10 * ApplicationInfo.ratio top: undefined left: undefined } numberOfSubLevels: items.nbSubLevel currentSubLevel: items.currentSubLevel + 1 } } } diff --git a/src/activities/explore_farm_animals/AnimalDescriptionLevels.qml b/src/activities/explore_farm_animals/AnimalDescriptionLevels.qml index 2bca6c780..7320e0365 100644 --- a/src/activities/explore_farm_animals/AnimalDescriptionLevels.qml +++ b/src/activities/explore_farm_animals/AnimalDescriptionLevels.qml @@ -1,248 +1,248 @@ /* GCompris - AnimalDescriptionLevels.qml * * Copyright (C) 2015 Ayush Agrawal * * Authors: * Beth Hadley (GTK+ version) * Ayush Agrawal (Qt Quick port) * Djalil MESLI (Qt Quick port) * Johnny Jazeix (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.6 import GCompris 1.0 import "../../core" import "explore-level.js" as Activity Rectangle { id: rectangleDesc radius: 30 border.width: 5 border.color: "black" width: parent.width height: parent.height MouseArea { anchors.fill: parent onPressed: parent.closeDescriptionPanel() } property alias title: heading.text property alias description: descriptionText.text property alias imageSource: animalImage.source - property bool horizontalLayout: background.width > background.height + property bool horizontalLayout: background.width >= background.height signal showDescriptionPanel signal closeDescriptionPanel onShowDescriptionPanel: { descriptionPanelCloseAnimation.stop() descriptionPanelAppearAnimation.start() } onCloseDescriptionPanel: { descriptionPanelAppearAnimation.stop() close() } // animation for appearance of the description panel NumberAnimation { id: descriptionPanelAppearAnimation target: descriptionPanel property: horizontalLayout ? "x" : "y" from: horizontalLayout ? -width : -height to: 0 duration: 1200 easing.type: Easing.OutBack } GCText { id: heading fontSize: largeSize horizontalAlignment: Text.AlignHCenter font.weight: Font.DemiBold anchors.top: parent.top color: "#2a2a2a" width: parent.width - cancelButton.width wrapMode: Text.WordWrap } Image { id: animalImage width: rectangleDesc.horizontalLayout ? parent.width / 2 : parent.width * 0.9 height: rectangleDesc.horizontalLayout ? parent.height * 0.8 : (parent.height - heading.height - descriptionText.height) * 0.9 fillMode: Image.PreserveAspectFit anchors { top: rectangleDesc.horizontalLayout ? heading.bottom : descriptionText.bottom horizontalCenter: rectangleDesc.horizontalLayout ? undefined : parent.horizontalCenter left: rectangleDesc.horizontalLayout ? parent.left : undefined leftMargin: rectangleDesc.horizontalLayout ? 30 * ApplicationInfo.ratio : 0 } MouseArea { anchors.fill: parent onClicked: parent.switchImageWithTextOrAlone() } state: "zoomedOut" // Relative width and height of the image is changed on zooming-in and zooming-out keeping original binding intact states: [ State { name: "zoomedIn" PropertyChanges { target: animalImage width: rectangleDesc.width / 1.2 height: rectangleDesc.height / 1.2 } PropertyChanges { target: descriptionText visible: false } AnchorChanges { target: animalImage anchors.top: heading.bottom } }, State { name: "zoomedOut" PropertyChanges { target: animalImage width: rectangleDesc.horizontalLayout ? parent.width / 2 : parent.width * 0.9 height: rectangleDesc.horizontalLayout ? parent.height * 0.8 : (parent.height - heading.height - descriptionText.height) * 0.9 } PropertyChanges { target: descriptionText visible: true } AnchorChanges { target: animalImage anchors.top: rectangleDesc.horizontalLayout ? heading.bottom : descriptionText.bottom } } ] // Transition to animate zoom-in and zoom-out transitions: [ Transition { from: "zoomedOut" to: "zoomedIn" NumberAnimation { target: animalImage properties: "width, height" easing.type: Easing.OutBack duration: 500 } AnchorAnimation { duration: 250 } }, Transition { from: "zoomedIn" to: "zoomedOut" NumberAnimation { target: animalImage properties: "width, height" duration: 250 } PropertyAnimation { target: descriptionText property: "visible" } AnchorAnimation { duration: 250 } } ] // Changes the state of the image function switchImageWithTextOrAlone() { if(state === "zoomedOut") { state = "zoomedIn"; } else { state = "zoomedOut"; } } } GCText { id: descriptionText font.weight: Font.DemiBold fontSizeMode: Text.Fit horizontalAlignment: Text.AlignJustify anchors { top: (heading.height > cancelButton.height) ? heading.bottom : cancelButton.bottom right: parent.right rightMargin: 30 * ApplicationInfo.ratio left: rectangleDesc.horizontalLayout ? animalImage.right : parent.left leftMargin: 30 * ApplicationInfo.ratio } color: "#2a2a2a" width: rectangleDesc.horizontalLayout ? parent.width * 0.45 : parent.width height: rectangleDesc.horizontalLayout ? parent.height * 0.5 : parent.height * 0.3 wrapMode: Text.WordWrap } // The close panel button GCButtonCancel { id: cancelButton onClose: parent.closeDescriptionPanel() } function close() { if(animalImage.state === "zoomedIn") { animalImage.state = "zoomedOut"; } descriptionPanelCloseAnimation.start(); if (Activity.isComplete()) { Activity.items.bonus.good("flower"); Activity.nextLevel(); } } SequentialAnimation { id: descriptionPanelCloseAnimation NumberAnimation { id: slideBackDescriptionPanel target: descriptionPanel property: horizontalLayout ? "x" : "y" to: horizontalLayout ? -width : -height duration: 1200 easing.type: Easing.InSine } PropertyAnimation { id: switchDescriptionPanelInvisible target: descriptionPanel property: "visible" to: false } } } diff --git a/src/activities/explore_farm_animals/ExploreLevels.qml b/src/activities/explore_farm_animals/ExploreLevels.qml index 58be5b726..4ac1991e5 100644 --- a/src/activities/explore_farm_animals/ExploreLevels.qml +++ b/src/activities/explore_farm_animals/ExploreLevels.qml @@ -1,305 +1,305 @@ /* GCompris - ExploreLevels.qml * * Copyright (C) 2015 Ayush Agrawal * * Authors: * Beth Hadley (GTK+ version) * Ayush Agrawal (Qt Quick port) * Djalil MESLI (Qt Quick port) * Johnny Jazeix (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.6 import GCompris 1.0 import QtQuick.Controls 1.5 import "../../core" import "explore-level.js" as Activity ActivityBase { id: activity property int numberOfLevels property string url property bool hasAudioQuestions onStart: focus = true onStop: {} pageComponent: Item { id: background /* 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: dataset.item.backgroundImage sourceSize.width: 2000 * ApplicationInfo.ratio sourceSize.height: 2000 * ApplicationInfo.ratio width: 2000 * background.playRatio height: width anchors.centerIn: parent } - property bool horizontalLayout: background.width > background.height + 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 focus: true signal start signal stop 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 GCAudio audioVoices: activity.audioVoices property GCSfx audioEffects: activity.audioEffects property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias progressbar: progressbar property alias ok: ok property alias dataModel: dataModel property alias dataset: dataset property alias instruction: instruction property alias instructionText: instructionText property alias descriptionPanel: descriptionPanel property alias nextQuestion: nextQuestion property bool hasAudioQuestions: activity.hasAudioQuestions property string currentAudio property var questionOrder property var currentQuestion: items.dataset ? items.dataset.item.tab[items.questionOrder[progressbar.value]] : "" } Timer { id: nextQuestion repeat: false interval: 2000 onTriggered: { Activity.repeat(); } } Loader{ id: dataset asynchronous: false onStatusChanged: { if (status == Loader.Ready) { // create table of size N filled with numbers from 0 to N items.questionOrder = Array.apply(null, {length: items.dataModel.count}).map(Number.call, Number) } } } onStart: { Activity.start(items, url, numberOfLevels) } onStop: { Activity.stop() } Keys.onEscapePressed: { descriptionPanel.visible ? descriptionPanel.closeDescriptionPanel() : home() } Repeater { id: dataModel model: dataset && dataset.item && dataset.item.tab ? dataset.item.tab.length : 0 AnimalLevels { questionId: index source: dataset.item.tab[index].image x: background.playX + background.playWidth * dataset.item.tab[index].x - width / 2 y: background.playY + background.playHeight * dataset.item.tab[index].y - height / 2 width: background.playWidth * dataset.item.tab[index].width height: background.playHeight * dataset.item.tab[index].height title: dataset.item.tab[index].title description: dataset.item.tab[index].text imageSource: dataset.item.tab[index].image2 question: dataset.item.tab[index].text2 audio: dataset.item.tab[index].audio !== undefined ? dataset.item.tab[index].audio : "" Component.onCompleted: { displayDescription.connect(displayDescriptionItem) } } } function displayDescriptionItem(animal) { descriptionPanel.title = animal.title descriptionPanel.description = animal.description descriptionPanel.imageSource = animal.imageSource descriptionPanel.visible = true descriptionPanel.showDescriptionPanel() } AnimalDescriptionLevels { id: descriptionPanel width: parent.width height: parent.height z: instruction.z + 1 } Column { id: progress visible: items.score.currentSubLevel != 1 anchors.bottom: bar.top anchors.right: parent.right anchors.margins: 10 * ApplicationInfo.ratio ProgressBar { id: progressbar height: progressbarText.height width: bar.width property string message onValueChanged: message = value + "/" + maximumValue onMaximumValueChanged: message = value + "/" + maximumValue GCText { id: progressbarText anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" text: progressbar.message } } } Image { id: ok visible: progressbar.value === progressbar.maximumValue source:"qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: questionText.height * 2 fillMode: Image.PreserveAspectFit anchors.right: progress.left anchors.bottom: bar.top anchors.margins: 10 * ApplicationInfo.ratio MouseArea { anchors.fill: parent onClicked: Activity.nextLevel() } } Row { id: row spacing: 10 * ApplicationInfo.ratio anchors.fill: parent anchors.margins: 10 * ApplicationInfo.ratio layoutDirection: leftCol.width === 0 ? Qt.RightToLeft : Qt.LeftToRight Column { id: leftCol spacing: 10 * ApplicationInfo.ratio Rectangle { id: instruction width: row.width - rightCol.width - 10 * ApplicationInfo.ratio height: instructionText.height color: "#CCCCCCCC" radius: 10 border.width: 3 border.color: "black" GCText { id: instructionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.centerIn: parent.Center color: "black" width: parent.width wrapMode: Text.Wrap text: (dataset.item && items.score.currentSubLevel - 1 != items.score.numberOfSubLevels && items.score.currentSubLevel != 0) ? dataset.item.instructions[items.score.currentSubLevel - 1].text : "" } MouseArea { anchors.fill: parent onClicked: instruction.visible = false enabled: instruction.visible } } Rectangle { id: question width: row.width - rightCol.width - 10 * ApplicationInfo.ratio height: questionText.height color: '#CCCCCCCC' radius: 10 border.width: 3 border.color: "black" visible: items.score.currentSubLevel == 3 || (items.score.currentSubLevel == 2 && !items.hasAudioQuestions) GCText { id: questionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.centerIn: parent.Center color: "black" width: parent.width wrapMode: Text.Wrap text: items.currentQuestion ? items.currentQuestion.text2 : "" } } } Column { id: rightCol spacing: 10 * ApplicationInfo.ratio Score { id: score anchors { bottom: undefined right: undefined } } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 60 * ApplicationInfo.ratio anchors.right: parent.right visible: items.score.currentSubLevel == 2 && activity.hasAudioQuestions //&& ApplicationSettings.isAudioVoicesEnabled onClicked: Activity.repeat(); } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.start(items, url, numberOfLevels) } Bonus { id: bonus } } } diff --git a/src/activities/family/Family.qml b/src/activities/family/Family.qml index 294f70a80..651a957bf 100644 --- a/src/activities/family/Family.qml +++ b/src/activities/family/Family.qml @@ -1,413 +1,413 @@ /* GCompris - family.qml * * Copyright (C) 2016 Rajdeep Kaur * * Authors: * * Rajdeep Kaur * Rudra Nil Basu * * 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.6 import GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "family.js" as Activity ActivityBase { id: activity property string mode: "family" onStart: focus = true onStop: {} pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" width: parent.width height: parent.height fillMode: Image.PreserveAspectCrop - property bool horizontalLayout: background.width > background.height + property bool horizontalLayout: background.width >= background.height signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } property real treeAreaWidth: background.horizontalLayout ? background.width * 0.65 : background.width property real treeAreaHeight: background.horizontalLayout ? background.height : background.height * 0.65 property real nodeWidth: (0.8 * treeAreaWidth) / 5 property real nodeHeight: (0.8 * treeAreaWidth) / 5 property real nodeWidthRatio: nodeWidth / treeAreaWidth property real nodeHeightRatio: nodeHeight / treeAreaHeight onWidthChanged: loadDatasetDelay.start() onHeightChanged: if (!loadDatasetDelay.running) { loadDatasetDelay.start() } /* * Adding a delay before reloading the datasets * needed for fast width / height changes */ Timer { id: loadDatasetDelay running: false repeat: false interval: 100 onTriggered: Activity.loadDatasets() } // 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 nodeRepeater: nodeRepeater property alias answersChoice: answersChoice property alias edgeRepeater: edgeRepeater property alias ringRepeator: ringRepeator property alias dataset: dataset property string mode: activity.mode property alias questionTopic: question.questionTopic property alias selectedPairs: selectedPairs property alias loadDatasetDelay: loadDatasetDelay property bool buttonsBlocked: false property point questionMarkPosition: questionMarkPosition property point meLabelPosition: meLabelPosition } onStart: { Activity.start(items) } onStop: { Activity.stop() } Dataset { id: dataset } // handling pair matching for family_find_relative Item { id: selectedPairs property var firstNodePointer: undefined property var secondNodePointer: undefined function reset() { firstNodePointer = undefined secondNodePointer = undefined } function deactivatePairs() { if (firstNodePointer && secondNodePointer) { firstNodePointer.changeState("deactivate") secondNodePointer.changeState("deactivate") reset() } } function checkResult() { if (firstNodePointer.weight == (secondNodePointer.weight * -1) && firstNodePointer.weight != 0) { return true } else { return false } } function selectNode(node_) { if (firstNodePointer && secondNodePointer) return if(firstNodePointer == undefined) { firstNodePointer = node_ firstNodePointer.changeState("activeTo") } else { secondNodePointer = node_ if (firstNodePointer == secondNodePointer) { deactivatePairs() return } secondNodePointer.changeState("activeTo") // checking results if (checkResult()) { bonus.good("lion") } else { bonus.bad("lion") deactivatePairs() } } } } Item { id: board width: background.width height: background.height Rectangle { id: treeArea color: "transparent" width: background.treeAreaWidth height: background.treeAreaHeight anchors.horizontalCenter: activity.mode == "find_relative" ? board.horizontalCenter : undefined anchors.verticalCenter: activity.mode == "find_relative" ? board.verticalCenter : undefined border.width: 0 Item { id: treeItem Repeater { id: nodeRepeater model: ListModel {} delegate: Node { id: currentPointer x: xPosition * treeArea.width y: yPosition * treeArea.height z: 30 width: treeArea.width / 5 height: treeArea.width / 5 nodeWidth: currentPointer.width nodeHeight: currentPointer.height nodeImageSource: Activity.url + nodeValue borderColor: "#373737" borderWidth: 8 color: "transparent" radius: nodeWidth / 2 state: currentState weight: nodeWeight states: [ State { name: "active" PropertyChanges { target: currentPointer borderColor: "#e1e1e1" } }, State { name: "deactivate" PropertyChanges { target: currentPointer } }, State { name: "activeTo" PropertyChanges { target: currentPointer borderColor: "#e77936" color: "#80f2f2f2" } } ] } } Rectangle { id: me visible: dataset.levelElements[bar.level-1].captions[0] !== undefined && activity.mode == "family" x: items.meLabelPosition.x * treeArea.width y: items.meLabelPosition.y * treeArea.height width: treeArea.width / 12 height: treeArea.height / 14 radius: 5 color: "#f2f2f2" border.color: "#e1e1e1" GCText { id: meLabel text: qsTr("Me") color: "#555555" anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } } } Rectangle { id: questionmark visible: dataset.levelElements[bar.level-1].captions[1] !== undefined && activity.mode == "family" x: items.questionMarkPosition.x * treeArea.width y: items.questionMarkPosition.y * treeArea.height width: treeArea.width / 14 height: width radius: width/2 color: "#f2f2f2" border.color: "#e77936" GCText { id: qLabel text: qsTr("?") color: "#555555" anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } } } Repeater { id: edgeRepeater model: ListModel {} delegate: Rectangle { id: edge z: 20 opacity: 1 antialiasing: true transformOrigin: Item.TopLeft x: _x1 * treeArea.width y: _y1 * treeArea.height property var x2: _x2 * treeArea.width property var y2: _y2 * treeArea.height width: Math.sqrt(Math.pow(x - x2, 2) + Math.pow(y- y2, 2)) height: 4 * ApplicationInfo.ratio rotation: (Math.atan((y2 - y)/(x2-x)) * 180 / Math.PI) + (((y2-y) < 0 && (x2-x) < 0) * 180) + (((y2-y) >= 0 && (x2-x) < 0) * 180) color: "#373737" } } Repeater { id: ringRepeator model: ListModel {} delegate: Image { id: ring source: Activity.url + "rings.svg" width: treeArea.width * 0.05 sourceSize.width: width fillMode: Image.PreserveAspectCrop x: ringx * treeArea.width y: ringy * treeArea.height z: 40 } } } } Rectangle { id: answers color: "transparent" width: background.horizontalLayout ? background.width*0.35 : background.width height: background.horizontalLayout ? background.height : background.height*0.35 anchors.left: background.horizontalLayout ? treeArea.right : board.left anchors.top: background.horizontalLayout ? board.top: treeArea.bottom border.width: 0 Rectangle { width: parent.width * 0.99 height: parent.height * 0.99 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter color: "transparent" Grid { id: answersGrid visible: activity.mode == "family" ? true : false columns: 1 rowSpacing: 10*ApplicationInfo.ratio states: [ State { name: "anchorCenter"; when: background.horizontalLayout AnchorChanges { target: answersGrid anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left } }, State { name: "anchorTop"; when: !background.horizontalLayout AnchorChanges { target: answersGrid anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter } } ] Repeater { id: answersChoice model: ListModel {} delegate: AnswerButton { id: options width: answers.width*0.75 height: answers.height*Activity.answerButtonRatio textLabel: optionn isCorrectAnswer: textLabel === answer onCorrectlyPressed: bonus.good("lion") onIncorrectlyPressed: bonus.bad("lion") onPressed: items.buttonsBlocked = true blockAllButtonClicks: items.buttonsBlocked } } } } } } GCText { id: question property string questionTopic visible: activity.mode == "find_relative" ? true : false width: background.width anchors.horizontalCenter: background.horizontalCenter horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap fontSize: smallSize text: qsTr("Select one of the pairs denoting: %1").arg(questionTopic) Rectangle { width: parent.width height: parent.height anchors.horizontalCenter: parent.horizontalCenter z: parent.z - 1 radius: 10 border.width: 1 color: "white" opacity: 0.8 } } 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 onStop: items.buttonsBlocked = false Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/gletters/FallingDomino.qml b/src/activities/gletters/FallingDomino.qml index cd462017e..9f59baf81 100644 --- a/src/activities/gletters/FallingDomino.qml +++ b/src/activities/gletters/FallingDomino.qml @@ -1,121 +1,121 @@ /* GCompris - FallingDomino.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "gletters.js" as Activity Item { id: word width: domino.width height: domino.height /// index into text.split("") where next typed match should occur property int unmatchedIndex: 0; property string text property var dominoValues property bool wonState: false signal won onWon: { wonState = true particle.burst(30) fadeout.restart(); } Component.onCompleted: { // make sure our word is completely visible - if (x + width > parent.width) + if (x + width >= parent.width) x = parent.width - width; } PropertyAnimation { id: fadeout target: word; property: "opacity" to: 0 duration: 1000 onStopped: Activity.deleteWord(word); } function checkMatch(c) { // We are in the ending animation if (wonState) return var chars = text.split(""); if (chars[unmatchedIndex] == c) { unmatchedIndex++; return true; } else { unmatchedIndex = 0; return false; } } function startMoving(dur) { down.duration = dur; down.restart(); } function isCompleted() { return (unmatchedIndex === text.length); } Domino { id: domino width: 120 * ApplicationInfo.ratio height: width / 2 visible: dominoValues.length != 0 value1: dominoValues[0] value2: dominoValues[1] isClickable: false ParticleSystemStarLoader { id: particle clip: false } } NumberAnimation { id: down target: word property: "y" to: parent.height duration: 10000 onStopped: { Activity.audioCrashPlay(); Activity.appendRandomWord(word.text) Activity.deleteWord(word); } } } diff --git a/src/activities/gletters/FallingImage.qml b/src/activities/gletters/FallingImage.qml index aa9fba1f9..cd14aa466 100644 --- a/src/activities/gletters/FallingImage.qml +++ b/src/activities/gletters/FallingImage.qml @@ -1,130 +1,130 @@ /* GCompris - Word.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "gletters.js" as Activity Item { id: word width: image.width height: image.height /// index into text.split("") where next typed match should occur property int unmatchedIndex: 0; property string text property alias image: image.source; property bool wonState: false signal won onWon: { wonState = true particle.burst(30) dropShadow.opacity = 0 fadeout.restart(); } Component.onCompleted: { // make sure our word is completely visible - if (x + width > parent.width) + if (x + width >= parent.width) x = parent.width - width; } PropertyAnimation { id: fadeout target: word; property: "opacity" to: 0 duration: 1000 onStopped: Activity.deleteWord(word); } function checkMatch(c) { // We are in the ending animation if (wonState) return var chars = text.split(""); if (chars[unmatchedIndex] == c) { unmatchedIndex++; return true; } else { unmatchedIndex = 0; return false; } } function startMoving(dur) { down.duration = dur; down.restart(); } function isCompleted() { return (unmatchedIndex === text.length); } Image { id: image // FIXME, the size should be passed from the caller sourceSize.height: 106 * ApplicationInfo.ratio ParticleSystemStarLoader { id: particle clip: false } } DropShadow { id: dropShadow anchors.fill: image cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3.0 samples: 16 color: "#422a2a2a" source: image } NumberAnimation { id: down target: word property: "y" to: parent.height duration: 10000 onStopped: { Activity.audioCrashPlay(); Activity.appendRandomWord(word.text) Activity.deleteWord(word); } } } diff --git a/src/activities/gletters/FallingWord.qml b/src/activities/gletters/FallingWord.qml index 7658765e5..e51013b8f 100644 --- a/src/activities/gletters/FallingWord.qml +++ b/src/activities/gletters/FallingWord.qml @@ -1,169 +1,169 @@ /* GCompris - Word.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "gletters.js" as Activity Item { id: word width: wordText.width height: wordText.height /// index into text.split("") where next typed match should occur property int unmatchedIndex: 0; property alias text: wordText.text; property bool wonState: false property string mode signal won onWon: { wonState = true particle.burst(30) dropShadow.opacity = 0 fadeout.restart(); } Component.onCompleted: { // make sure our word is completely visible - if (x + width > parent.width) + if (x + width >= parent.width) x = parent.width - width; } onUnmatchedIndexChanged: { if (unmatchedIndex <= 0) highlightedWordText.text = ""; else if (wordText.text.length > 0 && wordText.text.length >= unmatchedIndex) { highlightedWordText.text = wordText.text.substring(0, unmatchedIndex); /* Need to add the ZERO WIDTH JOINER to force joined char in Arabic and * Hangul: http://en.wikipedia.org/wiki/Zero-width_joiner * * FIXME: this works only on desktop systems, on android this * shifts the typed word a few pixels down. */ if (!ApplicationInfo.isMobile) highlightedWordText.text += "\u200C"; } } PropertyAnimation { id: fadeout target: word; property: "opacity" to: 0 duration: 1000 onStopped: Activity.deleteWord(word); } function checkMatch(c) { // We are in the ending animation if (wonState) return if(word.mode === 'letter') { // Only highlight letter if it is the good one unmatchedIndex = text === c ? c.length : 0 return (text === c) } else { var chars = text.split(""); if (chars[unmatchedIndex] == c) { unmatchedIndex++; return true; } else { unmatchedIndex = 0; return false; } } } function startMoving(dur) { down.duration = dur; down.restart(); } function isCompleted() { return (unmatchedIndex === text.length); } GCText { id: wordText text: "" fontSize: 35 font.bold: true color: "navy" style: Text.Outline styleColor: "white" ParticleSystemStarLoader { id: particle clip: false } GCText { id: highlightedWordText anchors.fill: parent text: "" fontSize: parent.fontSize font.bold: parent.font.bold color: "red" style: Text.Outline styleColor: "white" } } DropShadow { id: dropShadow anchors.fill: wordText cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3.0 samples: 16 color: "#422a2a2a" source: wordText } NumberAnimation { id: down target: word property: "y" to: parent.height duration: 10000 onStopped: { Activity.audioCrashPlay(); Activity.appendRandomWord(word.text) Activity.deleteWord(word); } } } diff --git a/src/activities/gnumch-equality/Creature.qml b/src/activities/gnumch-equality/Creature.qml index 2226715c4..ec090997d 100644 --- a/src/activities/gnumch-equality/Creature.qml +++ b/src/activities/gnumch-equality/Creature.qml @@ -1,169 +1,169 @@ /* GCompris - Creature.qml * * Copyright (C) 2014 Manuel Tondeur * * Authors: * Joe Neeman (spuzzzzzzz@gmail.com) (GTK+ version) * Manuel Tondeur (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.6 import GCompris 1.0 import "../../core" Item { id: creature property int index property string monsterType property bool movable property bool movingOn: false property bool eating: false property int frames property int frameW property real widthRatio property GCSfx audioEffects function moveTo(direction) { if (!movable) return true if (!hasReachLimit(direction)) { movementOn(direction) return true } else { return false } } function init() { index = 0 x = 0 y = 0 } function hasReachLimit(direction) { switch (direction) { case 0: if ((index + 1) % 6 > 0) return false break case 1: if ((index % 6) > 0) return false break case 2: if (index < 30) return false break case 3: if (index > 5) return false break } return true } function movementOn(direction) { // Compute if the direction is vertical (1) or not (0) var vertical = Math.floor(direction / 2) var sign = Math.pow(-1, (direction)) index += sign * (1 + 5 * vertical) var restIndex = index % 6 y = ((index - restIndex) / 6) * grid.cellHeight x = restIndex * grid.cellWidth } function updatePosition() { var restIndex = index % 6 y = ((index - restIndex) / 6) * grid.cellHeight x = restIndex * grid.cellWidth } index: 0 z: 0 movable: true width: grid.cellWidth height: grid.cellHeight onEatingChanged: { if (eating == true) { creatureImage.restart() creatureImage.resume() creature.audioEffects.play("qrc:/gcompris/src/activities/gnumch-equality/resource/eat.wav") } } AnimatedSprite { id: creatureImage property int turn: 0 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter - width: parent.width / parent.height < widthRatio ? parent.width * 0.85 : parent.height * 0.85 * widthRatio + width: parent.width / parent.height <= widthRatio ? parent.width * 0.85 : parent.height * 0.85 * widthRatio height: width * (1/widthRatio) source: "qrc:/gcompris/src/activities/gnumch-equality/resource/" + monsterType + ".png" frameCount: frames frameWidth: frameW frameDuration: 50 currentFrame: 0 running: false onCurrentFrameChanged: { if (currentFrame == frames - 1) { turn++ } } onTurnChanged: { if (turn == 2) { eating = false turn = 0 currentFrame = 0 pause() } } } Behavior on x { NumberAnimation { id: xAnim duration: 300 onRunningChanged: { movingOn = !movingOn } } } Behavior on y { NumberAnimation { id: yAnim duration: 300 onRunningChanged: { movingOn = !movingOn } } } Behavior on opacity { NumberAnimation { duration: 500 } } } diff --git a/src/activities/lang/Quiz.qml b/src/activities/lang/Quiz.qml index bc5761085..dd343ac24 100644 --- a/src/activities/lang/Quiz.qml +++ b/src/activities/lang/Quiz.qml @@ -1,293 +1,293 @@ /* GCompris - Quiz.qml * * Copyright (C) Siddhesh suthar (Qt Quick port) * * Authors: * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) * Holger Kaelberer (Qt Quick port of imageid) * Siddhesh suthar (Qt Quick port) * Bruno Coudoin (Integration Lang dataset) * * 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.6 import GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "lang.js" as Activity import "quiz.js" as QuizActivity Item { id: quiz opacity: 0 property alias background: background property alias bonus: bonus property alias score: score property alias wordImage: wordImage property alias imageFrame: imageFrame property alias wordListModel: wordListModel property alias wordListView: wordListView property alias parser: parser property var goodWord - property bool horizontalLayout: background.width > background.height + property bool horizontalLayout: background.width >= background.height property bool buttonsBlocked: false function init(loadedItems_, wordList_, mode_) { opacity = 1 loadedItems_.forceActiveFocus() return QuizActivity.init(loadedItems_, wordList_, mode_) } onGoodWordChanged: Activity.playWord(goodWord.voice) Behavior on opacity { PropertyAnimation { duration: 200 } } Image { id: background source: "qrc:/gcompris/src/activities/lang/resource/imageid-bg.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) height: parent.height anchors.fill: parent property bool keyNavigation: false Keys.enabled: !quiz.buttonsBlocked Keys.onEscapePressed: { imageReview.start() } Keys.onRightPressed: { keyNavigation = true wordListView.incrementCurrentIndex() } Keys.onLeftPressed: { keyNavigation = true wordListView.decrementCurrentIndex() } Keys.onDownPressed: { keyNavigation = true wordListView.incrementCurrentIndex() } Keys.onUpPressed: { keyNavigation = true wordListView.decrementCurrentIndex() } Keys.onSpacePressed: { keyNavigation = true wordListView.currentItem.children[1].pressed() } Keys.onEnterPressed: { keyNavigation = true wordListView.currentItem.children[1].pressed() } Keys.onReturnPressed: { keyNavigation = true wordListView.currentItem.children[1].pressed() } Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = true imageReview.start() } } JsonParser { id: parser onError: console.error("Lang: Error parsing json: " + msg); } ListModel { id: wordListModel } Grid { id: gridId columns: quiz.horizontalLayout ? 2 : 1 spacing: 10 * ApplicationInfo.ratio anchors.fill: parent anchors.margins: 10 * ApplicationInfo.ratio Item { width: quiz.horizontalLayout ? background.width * 0.40 : background.width - gridId.anchors.margins * 2 height: quiz.horizontalLayout ? background.height - bar.height : (background.height - bar.height) * 0.4 Image { id: imageFrame anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" sourceSize.width: quiz.horizontalLayout ? parent.width * 0.7 : quiz.width - repeatItem.width - score.width - 50 * ApplicationInfo.ratio z: 11 visible: QuizActivity.mode !== 3 Image { id: wordImage // Images are not svg width: Math.min(parent.width, parent.height) * 0.9 height: width anchors.centerIn: parent property string nextSource function changeSource(nextSource_) { nextSource = nextSource_ animImage.start() } SequentialAnimation { id: animImage PropertyAnimation { target: wordImage property: "opacity" to: 0 duration: 100 } PropertyAction { target: wordImage property: "source" value: wordImage.nextSource } PropertyAnimation { target: wordImage property: "opacity" to: 1 duration: 100 } } MouseArea { anchors.fill: parent onClicked: Activity.playWord(goodWord.voice) } } } } ListView { id: wordListView width: quiz.horizontalLayout ? background.width * 0.55 : background.width - gridId.anchors.margins * 2 height: quiz.horizontalLayout ? background.height - bar.height : (background.height - bar.height) * 0.60 spacing: 10 * ApplicationInfo.ratio orientation: Qt.Vertical verticalLayoutDirection: ListView.TopToBottom interactive: false model: wordListModel highlight: Rectangle { width: wordListView.width height: wordListView.buttonHeight color: "lightsteelblue" radius: 5 visible: background.keyNavigation y: wordListView.currentItem.y Behavior on y { SpringAnimation { spring: 3 damping: 0.2 } } } highlightFollowsCurrentItem: false focus: true keyNavigationWraps: true property int buttonHeight: height / wordListModel.count * 0.9 delegate: Item { id: wordListViewDelegate width: wordListView.width height: wordListView.buttonHeight anchors.right: parent.right anchors.left: parent.left Image { id: wordImageQuiz width: height height: wordListView.buttonHeight source: image z: 7 fillMode: Image.PreserveAspectFit anchors.leftMargin: 5 * ApplicationInfo.ratio visible: (QuizActivity.mode == 1) ? true : false // hide images after first mini game } AnswerButton { id: wordRectangle width: parent.width * 0.6 height: wordListView.buttonHeight textLabel: translatedTxt anchors.left: wordImageQuiz.left anchors.right: parent.right blockAllButtonClicks: quiz.buttonsBlocked onPressed: quiz.buttonsBlocked = true isCorrectAnswer: translatedTxt === quiz.goodWord.translatedTxt onIncorrectlyPressed: { // push the error to have it asked again QuizActivity.remainingWords.unshift(quiz.goodWord); QuizActivity.nextSubLevelQuiz(); } onCorrectlyPressed: { QuizActivity.nextSubLevelQuiz(); } } } } } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 80 * ApplicationInfo.ratio z: 12 anchors { top: parent.top left: parent.left margins: 10 * ApplicationInfo.ratio } onClicked: Activity.playWord(goodWord.voice) Behavior on opacity { PropertyAnimation { duration: 200 } } } Score { id: score parent: quiz anchors.bottom: undefined anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.top: parent.top } Bonus { id: bonus onWin: imageReview.nextMiniGame() } } } diff --git a/src/activities/lang/SpellIt.qml b/src/activities/lang/SpellIt.qml index 2304cfe14..16bc06b35 100644 --- a/src/activities/lang/SpellIt.qml +++ b/src/activities/lang/SpellIt.qml @@ -1,302 +1,302 @@ /* GCompris - SpellIt.qml * * Copyright (C) Siddhesh suthar (Qt Quick port) * * Authors: * Pascal Georges (pascal.georges1@free.fr) (GTK+ version) * Holger Kaelberer (Qt Quick port of imageid) * Siddhesh suthar (Qt Quick port) * Bruno Coudoin (Integration Lang dataset) * * 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.6 import GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "lang.js" as Activity import "spell_it.js" as SpellActivity Item { id: spellIt opacity: 0 property alias background: background property alias wordImage: wordImage property alias imageFrame: imageFrame property alias hintTextbg: hintTextbg property alias hintText: hintText property alias parser: parser property alias answerbg: answerbg property alias answer: answer property alias ok: ok property alias okMouseArea: okMouseArea property alias bonus: bonus property alias keyboard: keyboard property alias score: score property var goodWord property int goodWordIndex property int maximumLengthAnswer function init(loadedItems_, wordList_, mode_) { opacity = 1 return SpellActivity.init(loadedItems_, wordList_, mode_); } onGoodWordChanged: Activity.playWord(goodWord.voice) Behavior on opacity { PropertyAnimation { duration: 200 } } Keys.onEscapePressed: { imageReview.start() } Image { id: background source: "qrc:/gcompris/src/activities/lang/resource/imageid-bg.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) height: parent.height - property bool horizontalLayout: background.width > background.height + property bool horizontalLayout: background.width >= background.height JsonParser { id: parser onError: console.error("Lang: Error parsing json: " + msg); } Rectangle { id: hintTextbg x: hintText.x -4 y: hintText.y -4 width: imageFrame.width height: hintText.height +4 color: "#5090ff" border.color: "#000000" border.width: 2 radius: 16 anchors.top: parent.top anchors.bottom: imageFrame.top anchors.left: imageFrame.left anchors.bottomMargin: 5 GCText { id: hintText text: "" fontSize: largeSize font.weight: Font.DemiBold width: parent.width horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter color: "white" wrapMode: Text.WordWrap property string nextHint function changeHint(nextHint_) { nextHint = nextHint_ animHint.start() } SequentialAnimation { id: animHint PropertyAnimation { target: hintText property: "opacity" to: 0 duration: 100 } PropertyAction { target: hintText property: "text" value: ""+ hintText.nextHint } PropertyAnimation { target: hintText property: "opacity" to: 1 duration: 100 } } } } Image { id: imageFrame source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" sourceSize.width: background.horizontalLayout ? parent.width * 0.9 : parent.height * 1.2 width: background.width * 0.55 height: (background.height - hintTextbg.height - answerbg.height - keyboard.height - bar.height) * 0.8 anchors { horizontalCenter: background.horizontalCenter top: background.top topMargin: (background.height) * 0.15 } z: 11 Image { id: wordImage // Images are not svg width: Math.min(parent.width, parent.height) * 0.9 height: width anchors.centerIn: parent property string nextSource function changeSource(nextSource_) { nextSource = nextSource_ animImage.start() } SequentialAnimation { id: animImage PropertyAnimation { target: wordImage property: "opacity" to: 0 duration: 100 } PropertyAction { target: wordImage property: "source" value: wordImage.nextSource } PropertyAnimation { target: wordImage property: "opacity" to: 1 duration: 100 } } MouseArea { anchors.fill: parent onClicked: { Activity.playWord(goodWord.voice) } } } } Rectangle { id: answerbg x: answer.x -4 y: answer.y -4 width: imageFrame.width height: answer.height +4 color: "#5090ff" border.color: "#000000" border.width: 2 radius: 16 anchors { top: imageFrame.bottom left: imageFrame.left topMargin: 20* ApplicationInfo.ratio } TextInput { id: answer width: hintTextbg.width height: hintTextbg.height color: "white" cursorVisible: true focus: false activeFocusOnPress: !ApplicationInfo.isMobile visible: true horizontalAlignment: TextInput.AlignHCenter verticalAlignment: TextInput.AlignVCenter font.pointSize: hintText.pointSize font.weight: Font.DemiBold font.family: GCSingletonFontLoader.fontLoader.name font.capitalization: ApplicationSettings.fontCapitalization font.letterSpacing: ApplicationSettings.fontLetterSpacing maximumLength: maximumLengthAnswer onAccepted: { okMouseArea.clicked(okMouseArea) } } } Image { id: ok source:"qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 70 * ApplicationInfo.ratio fillMode: Image.PreserveAspectFit anchors { top: imageFrame.bottom topMargin: 10* ApplicationInfo.ratio left: imageFrame.right leftMargin: 10* ApplicationInfo.ratio right: parent.right } MouseArea { id: okMouseArea anchors.fill: parent hoverEnabled: true onEntered: ok.scale = 1.1 onClicked: { SpellActivity.checkAnswer(answer.text) } onExited: ok.scale = 1 } } Bonus { id: bonus onWin: imageReview.nextMiniGame() } } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 80 * ApplicationInfo.ratio z: 12 anchors { top: parent.top left: parent.left margins: 10 * ApplicationInfo.ratio } onClicked: Activity.playWord(goodWord.voice) Behavior on opacity { PropertyAnimation { duration: 200 } } } Score { id: score anchors.bottom: undefined anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.top: parent.top } VirtualKeyboard { id: keyboard parent: keyboardArea anchors.bottom: undefined anchors.horizontalCenter: undefined width: parent.width visible: ApplicationSettings.isVirtualKeyboard onKeypress: SpellActivity.processKeyPress(text) onError: console.log("VirtualKeyboard error: " + msg); } } diff --git a/src/activities/magic-hat-minus/MagicHat.qml b/src/activities/magic-hat-minus/MagicHat.qml index f99af65e5..2936a3926 100644 --- a/src/activities/magic-hat-minus/MagicHat.qml +++ b/src/activities/magic-hat-minus/MagicHat.qml @@ -1,239 +1,239 @@ /* GCompris - MagicHat.qml * * Copyright (C) 2014 Thibaut ROMAIN * * Authors: * (GTK+ version) * Thibaut ROMAIN (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.6 import GCompris 1.0 import "../../core" import "magic-hat.js" as Activity import "." ActivityBase { id: activity onStart: focus = true onStop: {} property string mode: "minus" pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop property int starSize: Math.min(rightLayout.width / 12, background.height / 21) signal start signal stop property var starColors : ["1", "2", "3"] Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } onStart: Activity.start(items, mode) onStop: Activity.stop() - property bool vert: background.width > (background.height - okButton.height) + property bool vert: background.width >= (background.height - okButton.height) // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias bonus: bonus property alias hat: theHat property alias introductionText: introText property var repeatersList: [repeaterFirstRow, repeaterSecondRow, repeaterAnswerRow] } Item { id: mainlayout anchors.left: background.left width: background.width * 0.4 height: background.height z: 11 Hat { id: theHat starsSize: background.starSize audioEffects: activity.audioEffects } GCText { id: introText anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 20 * ApplicationInfo.ratio } width: parent.width - 5 * ApplicationInfo.ratio fontSize: regularSize font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: TextEdit.WordWrap horizontalAlignment: TextEdit.AlignHCenter text: qsTr("Click on the hat to begin the game") } GCText { //: The math operation text: mode == "minus" ? qsTr("−") : qsTr("+") anchors.right: mainlayout.right anchors.rightMargin: 10 y: secondRow.y fontSize: 66 color: "white" style: Text.Outline styleColor: "black" } } Grid { id: rightLayout anchors { left: mainlayout.right right: background.vert ? okButton.left : background.right rightMargin: background.vert ? 0 : 10 verticalCenter: background.verticalCenter verticalCenterOffset: background.height/8 } height: background.height columns: 1 Column { id: firstRow height: background.starSize * 4 spacing: 5 z: 10 Repeater { id: repeaterFirstRow model: 3 StarsBar { barGroupIndex: 0 barIndex: index width: rightLayout.width backgroundColor: "grey" starsColor: starColors[index] theHat: items.hat starsSize: background.starSize opacity: 0 } } } Column { id: secondRow height: background.starSize * 4 spacing: 5 z: 9 Repeater { id: repeaterSecondRow model: 3 StarsBar { barGroupIndex: 1 barIndex: index width: rightLayout.width backgroundColor: "grey" starsColor: starColors[index] theHat: items.hat starsSize: background.starSize opacity: 0 } } } Rectangle { width: (background.starSize + 5) * 10 - 5 height: 5 * ApplicationInfo.ratio color: "white" } Rectangle { width: (background.starSize + 5) * 10 - 5 height: 10 * ApplicationInfo.ratio opacity: 0 } Column { id: answerRow height: background.starSize * 4 spacing: 5 Repeater { id: repeaterAnswerRow model: 3 StarsBar { barGroupIndex: 2 barIndex: index width: rightLayout.width backgroundColor: "#53b9c9" starsColor: starColors[index] authorizeClick: false theHat: items.hat starsSize: background.starSize opacity: 0 } } } } 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() } BarButton { id: okButton anchors { bottom: bar.top right: parent.right rightMargin: 10 * ApplicationInfo.ratio bottomMargin: 10 * ApplicationInfo.ratio } source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 60 * ApplicationInfo.ratio onClicked: Activity.verifyAnswer() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/menu/Menu.qml b/src/activities/menu/Menu.qml index fce49fe00..e51f08ad2 100644 --- a/src/activities/menu/Menu.qml +++ b/src/activities/menu/Menu.qml @@ -1,719 +1,719 @@ /* GCompris - Menu.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * 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.6 import "../../core" import GCompris 1.0 import QtGraphicalEffects 1.0 import "qrc:/gcompris/src/core/core.js" as Core import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 /** * GCompris' top level menu screen. * * Displays a grid of available activities divided subdivided in activity * categories/sections. * * The visibility of the section row is toggled by the setting * ApplicationSettings.sectionVisible. * * The list of available activities depends on the following settings: * * * ApplicationSettings.showLockedActivities * * ApplicationSettings.filterLevelMin * * ApplicationSettings.filterLevelMax * * @inherit QtQuick.Item */ ActivityBase { id: activity focus: true activityInfo: ActivityInfoTree.rootMenu onBack: { pageView.pop(to); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } onHome: { if(pageView.depth === 1 && !ApplicationSettings.isKioskMode) { Core.quit(main); } else { pageView.pop(); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } } onDisplayDialog: pageView.push(dialog) onDisplayDialogs: { var toPush = new Array(); for (var i = 0; i < dialogs.length; i++) { toPush.push({item: dialogs[i]}); } pageView.push(toPush); } // @cond INTERNAL_DOCS property string url: "qrc:/gcompris/src/activities/menu/resource/" property var sections: [ { icon: activity.url + "all.svg", tag: "favorite" }, { icon: activity.url + "computer.svg", tag: "computer" }, { icon: activity.url + "discovery.svg", tag: "discovery" }, { icon: activity.url + "experience.svg", tag: "experiment" }, { icon: activity.url + "fun.svg", tag: "fun" }, { icon: activity.url + "math.svg", tag: "math" }, { icon: activity.url + "puzzle.svg", tag: "puzzle" }, { icon: activity.url + "reading.svg", tag: "reading" }, { icon: activity.url + "strategy.svg", tag: "strategy" }, { icon: activity.url + "search-icon.svg", tag: "search" } ] property string currentTag: sections[0].tag /// @endcond pageComponent: Image { id: background source: activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) height: main.height fillMode: Image.PreserveAspectCrop Timer { // triggered once at startup to populate the keyboard id: keyboardFiller interval: 1000; running: true; onTriggered: { keyboard.populate(); } } function loadActivity() { // @TODO init of item would be better in setsource but it crashes on Qt5.6 // https://bugreports.qt.io/browse/QTBUG-49793 activityLoader.item.audioVoices = audioVoices activityLoader.item.audioEffects = audioEffects activityLoader.item.loading = loading //take the focus away from textField before starting an activity searchTextField.focus = false pageView.push(activityLoader.item) } Loader { id: activityLoader asynchronous: true onStatusChanged: { if (status == Loader.Loading) { loading.start(); } else if (status == Loader.Ready) { loading.stop(); loadActivity(); } else if (status == Loader.Error) loading.stop(); } } // Filters - property bool horizontal: main.width > main.height + property bool horizontal: main.width >= main.height property int sectionIconWidth: { if(horizontal) return Math.min(100 * ApplicationInfo.ratio, main.width / (sections.length + 1)) else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) return Math.min(100 * ApplicationInfo.ratio, (background.height - (bar.height+keyboard.height)) / (sections.length + 1)) else return Math.min(100 * ApplicationInfo.ratio, (background.height - bar.height) / (sections.length + 1)) } property int sectionIconHeight: sectionIconWidth property int sectionCellWidth: sectionIconWidth * 1.1 property int sectionCellHeight: sectionIconHeight * 1.1 property var currentActiveGrid: activitiesGrid property bool keyboardMode: false Keys.onPressed: { // Ctrl-modifiers should never be handled by the search-field if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_S) { // Ctrl+S toggle show / hide section ApplicationSettings.sectionVisible = !ApplicationSettings.sectionVisible } } else if(currentTag === "search") { // forward to the virtual keyboard the pressed keys if(event.key == Qt.Key_Backspace) keyboard.keypress(keyboard.backspace); else keyboard.keypress(event.text); } else if(event.key === Qt.Key_Space && currentActiveGrid.currentItem) { currentActiveGrid.currentItem.selectCurrentItem() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onTabPressed: currentActiveGrid = ((currentActiveGrid == activitiesGrid) ? section : activitiesGrid); Keys.onEnterPressed: if(currentActiveGrid.currentItem) currentActiveGrid.currentItem.selectCurrentItem(); Keys.onReturnPressed: if(currentActiveGrid.currentItem) currentActiveGrid.currentItem.selectCurrentItem(); Keys.onRightPressed: if(currentActiveGrid.currentItem) currentActiveGrid.moveCurrentIndexRight(); Keys.onLeftPressed: if(currentActiveGrid.currentItem) currentActiveGrid.moveCurrentIndexLeft(); Keys.onDownPressed: if(currentActiveGrid.currentItem && !currentActiveGrid.atYEnd) currentActiveGrid.moveCurrentIndexDown(); Keys.onUpPressed: if(currentActiveGrid.currentItem && !currentActiveGrid.atYBeginning) currentActiveGrid.moveCurrentIndexUp(); GridView { id: section model: sections width: horizontal ? main.width : sectionCellWidth height: { if(horizontal) return sectionCellHeight else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) return sectionCellHeight * (sections.length+1) else return main.height - bar.height } x: ApplicationSettings.sectionVisible ? section.initialX : -sectionCellWidth y: ApplicationSettings.sectionVisible ? section.initialY : -sectionCellHeight visible: ApplicationSettings.sectionVisible cellWidth: sectionCellWidth cellHeight: sectionCellHeight interactive: false keyNavigationWraps: true property int initialX: 4 property int initialY: 4 Component { id: sectionDelegate Item { id: backgroundSection width: sectionCellWidth height: sectionCellHeight Image { source: modelData.icon sourceSize.height: sectionIconHeight anchors.margins: 5 anchors.horizontalCenter: parent.horizontalCenter } ParticleSystemStarLoader { id: particles anchors.fill: backgroundSection clip: false } MouseArea { anchors.fill: backgroundSection onClicked: { selectCurrentItem() } } function selectCurrentItem() { section.currentIndex = index activity.currentTag = modelData.tag particles.burst(10) if(modelData.tag === "search") { ActivityInfoTree.filterBySearch(searchTextField.text); } else { ActivityInfoTree.filterByTag(modelData.tag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } } } delegate: sectionDelegate highlight: Item { width: sectionCellWidth height: sectionCellHeight Rectangle { anchors.fill: parent color: "#5AFFFFFF" } Image { source: "qrc:/gcompris/src/core/resource/button.svg" anchors.fill: parent } Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } } // Activities property int iconWidth: 120 * ApplicationInfo.ratio property int iconHeight: 120 * ApplicationInfo.ratio property int activityCellWidth: horizontal ? background.width / Math.floor(background.width / iconWidth) : (background.width - section.width) / Math.floor((background.width - section.width) / iconWidth) property int activityCellHeight: iconHeight * 1.7 Loader { id: warningOverlay anchors { top: horizontal ? section.bottom : parent.top bottom: parent.bottom left: horizontal ? parent.left : section.right right: parent.right margins: 4 } active: (ActivityInfoTree.menuTree.length === 0) && (currentTag === "favorite") sourceComponent: Item { anchors.fill: parent GCText { id: instructionTxt fontSize: smallSize y: height * 0.2 x: (parent.width - width) / 2 z: 2 width: parent.width * 0.6 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.weight: Font.DemiBold color: 'white' text: qsTr("Put your favorite activities here by selecting the " + "sun at the top right of that activity.") } Rectangle { anchors.fill: instructionTxt anchors.margins: -6 z: 1 opacity: 0.5 radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } } } } GridView { id: activitiesGrid anchors { top: { if(activity.currentTag === "search") return searchBar.bottom else return horizontal ? section.bottom : parent.top } bottom: bar.top left: horizontal ? parent.left : section.right margins: 4 } width: background.width cellWidth: activityCellWidth cellHeight: activityCellHeight clip: true model: ActivityInfoTree.menuTree keyNavigationWraps: true property int spacing: 10 delegate: Item { id: delegateItem width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing Rectangle { id: activityBackground width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { source: "qrc:/gcompris/src/activities/" + icon; anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter sourceSize.height: iconHeight anchors.margins: 5 Image { source: "qrc:/gcompris/src/core/resource/difficulty" + ActivityInfoTree.menuTree[index].difficulty + ".svg"; anchors.top: parent.top sourceSize.width: iconWidth * 0.15 x: 5 } Image { anchors { horizontalCenter: parent.horizontalCenter top: parent.top rightMargin: 4 } source: demo || !ApplicationSettings.isDemoMode ? "" : activity.url + "lock.svg" sourceSize.width: 30 * ApplicationInfo.ratio } Image { anchors { left: parent.left bottom: parent.bottom } source: ActivityInfoTree.menuTree[index].createdInVersion == ApplicationInfo.GCVersionCode ? activity.url + "new.svg" : "" sourceSize.width: 30 * ApplicationInfo.ratio } GCText { id: title anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap text: ActivityInfoTree.menuTree[index].title } // If we have enough room at the bottom display the description GCText { id: description visible: delegateItem.height - (title.y + title.height) > description.height ? 1 : 0 anchors.top: title.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 3 wrapMode: Text.WordWrap text: ActivityInfoTree.menuTree[index].description } } ParticleSystemStarLoader { id: particles anchors.fill: activityBackground } MouseArea { anchors.fill: activityBackground onClicked: selectCurrentItem() } Image { source: activity.url + (favorite ? "all.svg" : "all_disabled.svg"); anchors { top: parent.top right: parent.right rightMargin: 4 * ApplicationInfo.ratio } sourceSize.width: iconWidth * 0.25 visible: ApplicationSettings.sectionVisible MouseArea { anchors.fill: parent onClicked: favorite = !favorite } } function selectCurrentItem() { if(pageView.busy) return particles.burst(50) ActivityInfoTree.currentActivity = ActivityInfoTree.menuTree[index] activityLoader.setSource("qrc:/gcompris/src/activities/" + ActivityInfoTree.menuTree[index].name, { 'menu': activity, 'activityInfo': ActivityInfoTree.currentActivity }) if (activityLoader.status == Loader.Ready) loadActivity() } } highlight: Rectangle { width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing color: "#AAFFFFFF" border.width: 3 border.color: "black" visible: background.keyboardMode Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } Rectangle { id: activitiesMask visible: false anchors.fill: activitiesGrid gradient: Gradient { GradientStop { position: 0.0; color: "#FFFFFFFF" } GradientStop { position: 0.92; color: "#FFFFFFFF" } GradientStop { position: 0.96; color: "#00FFFFFF"} } } layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { id: activitiesOpacity source: activitiesGrid maskSource: activitiesMask anchors.fill: activitiesGrid } } // The scroll buttons GCButtonScroll { visible: !ApplicationInfo.useOpenGL anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: activitiesGrid.bottom anchors.bottomMargin: 30 * ApplicationInfo.ratio onUp: activitiesGrid.flick(0, 1127) onDown: activitiesGrid.flick(0, -1127) upVisible: activitiesGrid.visibleArea.yPosition <= 0 ? false : true downVisible: activitiesGrid.visibleArea.yPosition >= 1 ? false : true } Rectangle { id: searchBar width: horizontal ? parent.width/2 : parent.width - (section.width+10) height: searchTextField.height visible: activity.currentTag === "search" anchors { top: horizontal ? section.bottom : parent.top left: horizontal ? undefined : section.right } anchors.topMargin: horizontal ? 0 : 4 anchors.bottomMargin: horizontal ? 0 : 4 anchors.horizontalCenter: horizontal ? parent.horizontalCenter : undefined opacity: 0.5 radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.3; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } Connections { // On mobile with GCompris' virtual keyboard activated: // Force invisibility of Androids virtual keyboard: target: (ApplicationInfo.isMobile && activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) ? Qt.inputMethod : null onVisibleChanged: { if (ApplicationSettings.isVirtualKeyboard && visible) Qt.inputMethod.hide(); } onAnimatingChanged: { // note: seems to be never fired! if (ApplicationSettings.isVirtualKeyboard && Qt.inputMethod.visible) Qt.inputMethod.hide(); } } Connections { target: activity onCurrentTagChanged: { if (activity.currentTag === 'search') { searchTextField.focus = true; } else activity.focus = true; } } TextField { id: searchTextField width: parent.width textColor: "black" font.pointSize: 16 font.bold: true horizontalAlignment: TextInput.AlignHCenter verticalAlignment: TextInput.AlignVCenter font.family: GCSingletonFontLoader.fontLoader.name inputMethodHints: Qt.ImhNoPredictiveText // Note: we give focus to the textfield also in case // isMobile && !ApplicationSettings.isVirtualKeyboard // in conjunction with auto-hiding the inputMethod to always get // an input-cursor: activeFocusOnPress: true //ApplicationInfo.isMobile ? !ApplicationSettings.isVirtualKeyboard : true Keys.onReturnPressed: { if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard) Qt.inputMethod.hide(); activity.focus = true; } onEditingFinished: { if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard) Qt.inputMethod.hide(); activity.focus = true; } style: TextFieldStyle { placeholderTextColor: "black" } placeholderText: qsTr("Search specific activities") onTextChanged: ActivityInfoTree.filterBySearch(searchTextField.text); } } VirtualKeyboard { id: keyboard readonly property var letter: ActivityInfoTree.characters width: parent.width visible: activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter onKeypress: { if(text == keyboard.backspace) { searchTextField.text = searchTextField.text.slice(0, -1); } else if(text == keyboard.space) { searchTextField.text = searchTextField.text.concat(" "); } else { searchTextField.text = searchTextField.text.concat(text); } } function populate() { var tmplayout = []; var row = 0; var offset = 0; var cols; while(offset < letter.length-1) { if(letter.length <= 100) { cols = Math.ceil((letter.length-offset) / (3 - row)); } else { cols = background.horizontal ? (Math.ceil((letter.length-offset) / (15 - row))) :(Math.ceil((letter.length-offset) / (22 - row))) if(row == 0) { tmplayout[row] = new Array(); tmplayout[row].push({ label: keyboard.backspace }); tmplayout[row].push({ label: keyboard.space }); row ++; } } tmplayout[row] = new Array(); for (var j = 0; j < cols; j++) tmplayout[row][j] = { label: letter[j+offset] }; offset += j; row ++; } if(letter.length <= 100) { tmplayout[0].push({ label: keyboard.space }); tmplayout[row-1].push({ label: keyboard.backspace }); } keyboard.layout = tmplayout } } Bar { id: bar // No exit button on mobile, UI Guidelines prohibits it content: BarEnumContent { value: help | config | about | (ApplicationInfo.isMobile ? 0 : exit) } anchors.bottom: keyboard.visible ? keyboard.top : parent.bottom onAboutClicked: { searchTextField.focus = false displayDialog(dialogAbout) } onHelpClicked: { searchTextField.focus = false displayDialog(dialogHelp) } onConfigClicked: { searchTextField.focus = false dialogActivityConfig.active = true dialogActivityConfig.loader.item.loadFromConfig() displayDialog(dialogActivityConfig) } } DialogAbout { id: dialogAbout onClose: home() } DialogHelp { id: dialogHelp onClose: home() activityInfo: ActivityInfoTree.rootMenu } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { ConfigurationItem { id: configItem width: dialogActivityConfig.width - 50 * ApplicationInfo.ratio } } onSaveData: { dialogActivityConfig.configItem.save(); } onClose: { if(activity.currentTag != "search") { ActivityInfoTree.filterByTag(activity.currentTag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } else ActivityInfoTree.filterBySearch(searchTextField.text); home() } } } } diff --git a/src/activities/mining/Mining.qml b/src/activities/mining/Mining.qml index 8a485f21a..1f3ba9d73 100644 --- a/src/activities/mining/Mining.qml +++ b/src/activities/mining/Mining.qml @@ -1,543 +1,543 @@ /* GCompris - mining.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Peter Albrecht (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.6 import GCompris 1.0 import "../../core" import "mining.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Item { id: background anchors.fill: parent signal start signal stop property bool gotIt: false - property bool horizontalLayout: background.width > background.height + property bool horizontalLayout: background.width >= background.height 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 miningBg: miningBg property alias bar: bar property alias bonus: bonus property alias mineModel: mineObjects.model property Item nugget } onStart: { Activity.start(items) } onStop: { Activity.stop() } Image { id: miningBg source: Activity.url + "rockwall.svg" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter sourceSize.width: parent.width width: parent.width height: parent.height scale: miningBg._MIN_SCALE property int subLevel property int maxSubLevel property real _MAX_SCALE: 3 property real _MIN_SCALE: 1 onScaleChanged: items.nugget.checkOnScreen() Image { source: Activity.url + "vertical_border.svg" sourceSize.height: parent.height width: parent.width * 0.05 anchors { top: parent.top left: parent.left bottom: parent.bottom } } Image { source: Activity.url + "vertical_border.svg" sourceSize.height: parent.height width: parent.width * 0.05 anchors { top: parent.top right: parent.right bottom: parent.bottom } } Image { source: Activity.url + "horizontal_border.svg" sourceSize.width: parent.width height: parent.height * 0.05 anchors { top: parent.top right: parent.right left: parent.left } } GridView { id: mineObjects anchors.fill: parent cellWidth: parent.width / 4 cellHeight: parent.height / 4 delegate: Item { width: mineObjects.cellWidth height: mineObjects.cellHeight // Calculated value true when the nugget is on the visible // part of the screen property bool onScreen: true property alias nuggetImg: nuggetImg signal hit(real x, real y) signal checkOnScreen onHit: { if(!mouseArea.enabled) return var point = parent.mapToItem(nuggetImg, x, y) if(point.x > 0 && point.x < nuggetImg.width && point.y > 0 && point.y < nuggetImg.height) nuggetImg.hit() } onCheckOnScreen: { // Calc if the nugget is visible or not var nuggetCoord1 = background.mapFromItem(miningBg, items.nugget.x + items.nugget.nuggetImg.x, items.nugget.y + items.nugget.nuggetImg.y) var nuggetCoord2 = background.mapFromItem(miningBg, items.nugget.x + items.nugget.nuggetImg.x + items.nugget.nuggetImg.width, items.nugget.y + items.nugget.nuggetImg.y + items.nugget.nuggetImg.height) if(nuggetCoord1.x > miningBg.width || nuggetCoord2.x < 0 || nuggetCoord1.y > miningBg.height || nuggetCoord2.y < 0) onScreen = false else onScreen = true } Image { id: nuggetImg source: Activity.url + "gold_nugget.svg" sourceSize.width: mineObjects.cellWidth * 3 width: mineObjects.cellWidth * modelData.widthFactor / 2 height: mineObjects.cellHeight * modelData.widthFactor / 2 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter opacity: modelData.isTarget && miningBg.scale === miningBg._MAX_SCALE && !background.gotIt ? 1 : 0 signal hit onHit: { activity.audioEffects.play(Activity.url + "pickaxe."+ApplicationInfo.CompressedAudio) background.gotIt = true tuto.setState("Unzoom") } Component.onCompleted: { if(modelData.isTarget) items.nugget = parent } MouseArea { id: mouseArea anchors.fill: parent enabled: modelData.isTarget && miningBg.scale === miningBg._MAX_SCALE onClicked: parent.hit() } Behavior on opacity { PropertyAnimation { duration: 1000 } } } Image { id: cell source: modelData.source sourceSize.width: mineObjects.cellWidth * 3 width: mineObjects.cellWidth * modelData.widthFactor height: mineObjects.cellHeight * modelData.widthFactor anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter rotation: modelData.rotation opacity: !modelData.isTarget ? 1 : (background.gotIt ? 0 : 1) Component.onCompleted: { activity.audioEffects.play(Activity.url + "realrainbow."+ApplicationInfo.CompressedAudio) } ParallelAnimation { running: modelData.isTarget && !background.gotIt loops: Animation.Infinite SequentialAnimation { loops: Animation.Infinite NumberAnimation { target: cell property: "rotation" from: 0; to: 360 duration: 5000; easing.type: Easing.InOutQuad } NumberAnimation { target: cell; property: "rotation" from: 360; to: 0 duration: 5000; easing.type: Easing.InOutQuad } } SequentialAnimation { loops: Animation.Infinite NumberAnimation { target: cell; property: "scale" from: 0; to: 1 duration: 3000; easing.type: Easing.InOutQuad } PauseAnimation { duration: 300 + Math.random() * 300 } NumberAnimation { target: cell property: "scale" from: 1; to: 0 duration: 1000; easing.type: Easing.InOutQuad } } } } } } function updateScale(zoomDelta, x, y) { var xx1 = background.mapFromItem(miningBg, x, y) var previousScale = miningBg.scale if (zoomDelta > 0 && miningBg.scale < miningBg._MAX_SCALE) { if(miningBg.scale < miningBg._MAX_SCALE - 0.1) miningBg.scale += 0.1; else miningBg.scale = miningBg._MAX_SCALE if(gotIt) tuto.setState("Unzoom") else if(miningBg.scale < miningBg._MAX_SCALE) tuto.setState(items.nugget.onScreen ? "ZoomOk" : "ZoomBad") else tuto.setState(items.nugget.onScreen ? "NuggetSeen" : "NuggetNotSeen") } else if (zoomDelta < 0) { if(miningBg.scale > miningBg._MIN_SCALE) { miningBg.scale -= 0.1; if(gotIt) tuto.setState("Unzoom") else if(miningBg.scale > miningBg._MIN_SCALE) tuto.setState(items.nugget.onScreen ? "UnzoomOk" : "UnzoomBad") else tuto.setState("Started") } else if (gotIt) { gotIt = false if(miningBg.subLevel == miningBg.maxSubLevel) { bonus.good("lion") } else { miningBg.subLevel++ miningBg.scale = miningBg._MIN_SCALE Activity.createLevel() } tuto.setState("Stopped") } else { miningBg.anchors.horizontalCenterOffset = 0 miningBg.anchors.verticalCenterOffset = 0 tuto.setState("Started") } } if(previousScale != miningBg.scale) { var xx2 = background.mapFromItem(miningBg, x, y) miningBg.anchors.horizontalCenterOffset += xx1.x - xx2.x miningBg.anchors.verticalCenterOffset += xx1.y - xx2.y } } MouseArea { anchors.fill: parent propagateComposedEvents: true onWheel: miningBg.updateScale(wheel.angleDelta.y, wheel.x, wheel.y) } MultiPointTouchArea { anchors.fill: parent mouseEnabled: false minimumTouchPoints: 1 maximumTouchPoints: 2 // To determine if we zoom or unzoom property int prevDist: 0 // To avoid having too many updates or the zoom flickers property date dateEvent: new Date() touchPoints: [ TouchPoint { id: point1 }, TouchPoint { id: point2 } ] onReleased: prevDist = 0 onTouchUpdated: { if(!point2.pressed) { mineObjects.itemAt(point1.x, point1.y).hit(point1.x, point1.y) return } // Calc Distance var dist = Math.floor(Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2))) var newDateEvent = new Date() if(prevDist != dist && newDateEvent.getTime() - dateEvent.getTime() > 50) { miningBg.updateScale(dist - prevDist, (point1.x + point2.x) / 2, (point1.y + point2.y) / 2) dateEvent = newDateEvent } prevDist = dist } } } Image { id: carriage source: Activity.url + "gold_carriage.svg" sourceSize.height: background.horizontalLayout ? 120 * ApplicationInfo.ratio : 80 * ApplicationInfo.ratio anchors { right: parent.right bottom: background.horizontalLayout ? parent.bottom : bar.top } GCText { id: score anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter horizontalCenterOffset: parent.width / 10 } text: miningBg.subLevel + "/" + miningBg.maxSubLevel color: "white" font.bold: true style: Text.Outline styleColor: "black" fontSize: 22 } } Rectangle { id: tutoBackground color: "#C0b7b353" border.color: "black" border.width: 2 radius: 10 anchors { left: parent.left right: parent.right top: parent.top margins: 10 } height: tuto.height + anchors.margins * 2 visible: tuto.state != "Stopped" Behavior on height { PropertyAnimation { duration: 100 } } GCText { id: tuto fontSize: 13 anchors { left: parent.left right: parent.right top: parent.top margins: 10 } color: "white" wrapMode: TextEdit.WordWrap horizontalAlignment: TextEdit.AlignHCenter property string newState function setState(nextState) { if(bar.level == 1) { if(newState != nextState) { newState = nextState anim.restart() } } else { newState = "Stopped" anim.restart() } } states: [ State { name: "Started" PropertyChanges { target: tuto; text: qsTr("Find the sparkle and zoom in around it. If you have a mouse, point the cursor on the sparkle then use the scroll wheel. If you have a trackpad, point the cursor on the sparkle then drag one finger on the right area or two fingers on the center. On a touch area, drag two fingers away from the sparkle, one in each direction.") } }, State { name: "Stopped" PropertyChanges { target: tuto; text: ""} }, State { name: "ZoomOk" PropertyChanges { target: tuto text: qsTr("Perfect you are zooming. Continue until you see the nugget.")} }, State { name: "ZoomBad" PropertyChanges { target: tuto text: qsTr("Hum, take care, you are zooming too far from the sparkle.")} }, State { name: "NuggetSeen" PropertyChanges { target: tuto text: qsTr("Now you see the nugget, click on it to catch it.")} }, State { name: "NuggetNotSeen" PropertyChanges { target: tuto text: qsTr("Hum, you are too far from the nugget to see it. Unzoom then zoom again as close as you can from the sparkle.")} }, State { name: "Unzoom" PropertyChanges { target: tuto text: qsTr("Now unzoom and try to find another sparkle.")} }, State { name: "UnzoomBad" PropertyChanges { target: tuto text: qsTr("Continue to unzoom until you see the sparkle.")} }, State { name: "UnzoomOk" PropertyChanges { target: tuto text: qsTr("Now you see the sparkle, go ahead, you can zoom on it.")} } ] SequentialAnimation { id: anim PropertyAnimation { target: tuto property: "opacity" easing.type: Easing.Linear from: 1.0; to: 0 duration: 200 } PropertyAction { target: tuto property: "state" value: tuto.newState } PropertyAnimation { target: tuto property: "opacity" easing.type: Easing.Linear from: 0; to: 1.0 duration: 200 } } Behavior on opacity { PropertyAnimation { duration: 100 } } transitions: Transition { PropertyAnimation { target: tuto property: "opacity" to: 1.0 } } } } 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() onLevelChanged: { miningBg.subLevel = 1 miningBg.anchors.horizontalCenterOffset = 0 miningBg.anchors.verticalCenterOffset = 0 miningBg.scale = miningBg._MIN_SCALE tuto.setState("Started") switch(bar.level) { case 1: miningBg.maxSubLevel = 2 break case 2: miningBg.maxSubLevel = 4 break case 3: miningBg.maxSubLevel = 10 break } } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/mosaic/Mosaic.qml b/src/activities/mosaic/Mosaic.qml index 251fc5c6e..f8b881d50 100644 --- a/src/activities/mosaic/Mosaic.qml +++ b/src/activities/mosaic/Mosaic.qml @@ -1,382 +1,382 @@ /* GCompris - mosaic.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Clement coudoin (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.6 import GCompris 1.0 import "../../core" import "mosaic.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Image { id: background source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop anchors.fill: parent signal start signal stop property bool keyboardMode: false property var areaWithKeyboardFocus: selector 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 GCSfx audioEffects: activity.audioEffects property alias question: question property alias answer: answer property alias selector: selector property alias nbItems: column.nbItems property alias background: background property alias bar: bar property alias bonus: bonus property string selectedItem } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.onLeftPressed: { keyboardMode = true areaWithKeyboardFocus.moveCurrentIndexLeft() } Keys.onRightPressed: { keyboardMode = true areaWithKeyboardFocus.moveCurrentIndexRight() } Keys.onUpPressed: { keyboardMode = true areaWithKeyboardFocus.moveCurrentIndexUp() } Keys.onDownPressed: { keyboardMode = true areaWithKeyboardFocus.moveCurrentIndexDown() } Keys.onEnterPressed: { selectCell() } Keys.onSpacePressed: { selectCell() } Keys.onReturnPressed: { selectCell() } Keys.onTabPressed: { keyboardMode = true areaWithKeyboardFocus.changeAreaWithKeyboardFocus() } function selectCell() { keyboardMode = true areaWithKeyboardFocus.selectCurrentCell(areaWithKeyboardFocus.currentItem) } Column { id: column spacing: 10 x: parent.width * 0.05 y: parent.height * 0.05 width: parent.width * 0.9 property int nbItems: 24 - property bool horizontal: background.width > background.height + property bool horizontal: background.width >= background.height property int nbColumns: Activity.questionLayout[nbItems][0] property int nbLines: Activity.questionLayout[nbItems][1] property int itemWidth: horizontal ? Math.min(width / 2 / nbColumns - 10 - 10 / nbColumns / 2, parent.height / 2 / nbLines - 10 - 10 / nbLines / 2) : Math.min(width / nbColumns - 10 - 10 / nbColumns / 2, parent.height * 0.25 / nbLines - 10 - 10 / nbLines / 2) property int itemHeight: itemWidth property int nbSelectorColumns: horizontal ? Activity.selectorLayout[nbItems][0] : Activity.selectorLayout[nbItems][0] / 2 property int nbSelectorLines: horizontal ? Activity.selectorLayout[nbItems][1] : Activity.selectorLayout[nbItems][1] * 2 Grid { id: row spacing: 10 columns: column.horizontal ? 2 : 1 // === The Question Area === Rectangle { height: (column.itemHeight + 10) * column.nbLines width: column.horizontal ? column.width / 2 : column.width + 10 color: "#55333333" border.color: "black" border.width: 2 radius: 5 GridView { id: question width: (column.nbColumns * column.itemWidth) + (12.5 * (column.nbColumns - 1)) height: parent.height x: 2 * ApplicationInfo.ratio y: 2 * ApplicationInfo.ratio cellHeight: cellWidth cellWidth: width / column.nbColumns interactive: false keyNavigationWraps: true delegate: Image { source: Activity.url + modelData fillMode: Image.PreserveAspectFit width: question.cellWidth - 5 * ApplicationInfo.ratio height: width sourceSize.width: width sourceSize.height: height } } } // === The Answer Area === Rectangle { height: (column.itemHeight + 10) * column.nbLines width: column.horizontal ? column.width / 2 : column.width + 10 color: "#55333333" border.color: "black" border.width: 2 radius: 5 GridView { id: answer width: (column.nbColumns * column.itemWidth) + (12.5 * (column.nbColumns - 1)) height: parent.height x: 2 * ApplicationInfo.ratio cellHeight: cellWidth cellWidth: width / column.nbColumns interactive: false keyNavigationWraps: true highlightFollowsCurrentItem: true highlight: Rectangle { color: "red" border.width: 3 border.color: "black" opacity: 0.6 visible: background.keyboardMode && (background.areaWithKeyboardFocus === answer) Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } // If the image was directly used as a delegate (without containing it in the item), the highlight element would have been be hard to notice as it would get completely hidden by the image due to the same sizes. delegate: Item { id: cellItem width: answer.cellWidth height: answer.cellHeight readonly property int cellIndex: index Image { id: imageAnswerId source: Activity.url + modelData fillMode: Image.PreserveAspectFit width: answer.cellWidth - 5 * ApplicationInfo.ratio height: width sourceSize.width: width sourceSize.height: height anchors.centerIn: parent MouseArea { anchors.fill: parent onClicked: answer.selectCurrentCell(cellItem) } } } function selectCurrentCell(selectedCell) { Activity.answerSelected(selectedCell.cellIndex) } function changeAreaWithKeyboardFocus() { areaWithKeyboardFocus = selector } } } } // === The Selector === Rectangle { height: (column.itemWidth + 10) * column.nbSelectorLines width: column.width + 10 color: "#661111AA" border.color: "black" border.width: 2 radius: 5 GridView { id: selector width: (column.nbSelectorColumns * column.itemWidth) + (12.5 * (column.nbSelectorColumns - 1)) height: parent.height x: 2 * ApplicationInfo.ratio y: 2 * ApplicationInfo.ratio cellHeight: cellWidth cellWidth: width / column.nbSelectorColumns interactive: false keyNavigationWraps: true highlightFollowsCurrentItem: true highlight: Rectangle { color: "red" border.width: 3 border.color: "black" opacity: 0.6 visible: background.keyboardMode && (background.areaWithKeyboardFocus === selector) Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } delegate: Image { id: imageId source: Activity.url + modelData fillMode: Image.PreserveAspectFit width: selector.cellWidth - 5 * ApplicationInfo.ratio height: width sourceSize.width: width sourceSize.height: height z: iAmSelected ? 10 : 1 readonly property bool iAmSelected: items.selectedItem === modelData readonly property string imageName: modelData states: [ State { name: "notclicked" when: !imageId.iAmSelected && !mouseArea.containsMouse PropertyChanges { target: imageId scale: 0.8 } }, State { name: "clicked" when: mouseArea.pressed PropertyChanges { target: imageId scale: 0.7 } }, State { name: "hover" when: mouseArea.containsMouse PropertyChanges { target: imageId scale: 1.1 } }, State { name: "selected" when: imageId.iAmSelected PropertyChanges { target: imageId scale: 1 } } ] SequentialAnimation { id: anim running: imageId.iAmSelected loops: Animation.Infinite alwaysRunToEnd: true NumberAnimation { target: imageId property: "rotation" from: 0; to: 10 duration: 200 easing.type: Easing.OutQuad } NumberAnimation { target: imageId property: "rotation" from: 10; to: -10 duration: 400 easing.type: Easing.InOutQuad } NumberAnimation { target: imageId property: "rotation" from: -10; to: 0 duration: 200 easing.type: Easing.InQuad } } Behavior on scale { NumberAnimation { duration: 70 } } MouseArea { id: mouseArea anchors.fill: imageId hoverEnabled: true onClicked: selector.selectCurrentCell(parent) } } function selectCurrentCell(selectedCell) { items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/scroll.wav") items.selectedItem = selectedCell.imageName } function changeAreaWithKeyboardFocus() { areaWithKeyboardFocus = answer } } } } 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/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index a186411b1..0c664c44e 100644 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,407 +1,407 @@ /* GCompris - NoteNames.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * 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.6 import QtQuick.Controls 1.5 import GCompris 1.0 import "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} - property bool horizontalLayout: width > height + property bool horizontalLayout: width >= height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyNoteBindings = {} keyNoteBindings[Qt.Key_1] = 'C' keyNoteBindings[Qt.Key_2] = 'D' keyNoteBindings[Qt.Key_3] = 'E' keyNoteBindings[Qt.Key_4] = 'F' keyNoteBindings[Qt.Key_5] = 'G' keyNoteBindings[Qt.Key_6] = 'A' keyNoteBindings[Qt.Key_7] = 'B' if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { if(keyNoteBindings[event.key]) { // If the key pressed matches the note, pass the correct answer as parameter. isCorrectKey(keyNoteBindings[event.key]) } else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { doubleOctave.currentOctaveNb-- } else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { doubleOctave.currentOctaveNb++ } } } function isCorrectKey(key) { if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) Activity.correctAnswer() else items.displayNoteNameTimer.start() } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias multipleStaff: multipleStaff property alias doubleOctave: doubleOctave property alias bonus: bonus property alias iAmReady: iAmReady property alias messageBox: messageBox property alias addNoteTimer: addNoteTimer property alias dataset: dataset property alias progressBar: progressBar property alias introMessage: introMessage property bool isTutorialMode: true property alias displayNoteNameTimer: displayNoteNameTimer } Loader { id: dataset asynchronous: false source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string clefType: "Treble" Timer { id: displayNoteNameTimer interval: 2000 onRunningChanged: { if(running) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() messageBox.visible = true } else { messageBox.visible = false if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { Activity.wrongAnswer() addNoteTimer.resume() } } } } Rectangle { id: messageBox width: label.width + 20 height: label.height + 20 border.width: 5 border.color: "black" anchors.centerIn: multipleStaff radius: 10 z: 11 visible: false onVisibleChanged: text = Activity.targetNotes[0] == undefined ? "" : items.isTutorialMode ? qsTr("New note: %1").arg(Activity.targetNotes[0]) : Activity.newNotesSequence[Activity.currentNoteIndex] property string text GCText { id: label anchors.centerIn: parent fontSize: mediumSize text: parent.text } MouseArea { anchors.fill: parent enabled: items.isTutorialMode onClicked: { items.multipleStaff.pauseNoteAnimation() items.multipleStaff.musicElementModel.remove(1) Activity.showTutorial() } } } Rectangle { id: colorLayer anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 visible: !introMessage.visible onVisibleChanged: { messageBox.visible = false } onClicked: { Activity.initLevel() } } IntroMessage { id: introMessage anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 12 } AdvancedTimer { id: addNoteTimer onTriggered: { Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) } } ProgressBar { id: progressBar height: 20 * ApplicationInfo.ratio width: parent.width / 4 property int percentage: 0 value: percentage maximumValue: 100 visible: !items.isTutorialMode anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 10 } GCText { anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" //: The following translation represents percentage. text: qsTr("%1%").arg(parent.value) z: 2 } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 nbStaves: 1 clef: clefType notesColor: "red" softColorOpacity: 0 isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: progressBar.height + 20 flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 noteAnimationEnabled: true onNoteAnimationFinished: { if(!items.isTutorialMode) displayNoteNameTimer.start() } } // We present a pair of two joint piano keyboard octaves. Item { id: doubleOctave width: parent.width * 0.95 height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 30 readonly property int nbJointKeyboards: 2 readonly property int maxNbOctaves: 3 property int currentOctaveNb: 0 property var coloredKeyLabels: [] Repeater { id: octaveRepeater anchors.fill: parent model: doubleOctave.nbJointKeyboards PianoOctaveKeyboard { id: pianoKeyboard width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 blackLabelsVisible: false blackKeysEnabled: blackLabelsVisible whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 onNoteClicked: Activity.checkAnswer(note) currentOctaveNb: doubleOctave.currentOctaveNb anchors.top: (index === 1) ? octaveRepeater.top : undefined anchors.topMargin: horizontalLayout ? 0 : -15 anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined anchors.right: (index === 1) ? octaveRepeater.right : undefined coloredKeyLabels: doubleOctave.coloredKeyLabels labelsColor: "red" // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. whiteKeyNoteLabelsBass: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 ] } else { return [ whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 ] } } whiteKeyNoteLabelsTreble: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 ] } else { return [ whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 ] } } } } } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top left: doubleOctave.left leftMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb-- } } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top right: doubleOctave.right rightMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb++ } } } OptionsRow { id: optionsRow iconsWidth: 0 visible: false } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { iAmReady.visible = true Activity.initLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/photo_hunter/PhotoHunter.qml b/src/activities/photo_hunter/PhotoHunter.qml index 908330e3e..3524c622b 100644 --- a/src/activities/photo_hunter/PhotoHunter.qml +++ b/src/activities/photo_hunter/PhotoHunter.qml @@ -1,244 +1,244 @@ /* GCompris - PhotoHunter.qml * * Copyright (C) 2016 Stefan Toncu * * Authors: * (GTK+ version) * Stefan Toncu (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.6 import GCompris 1.0 import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import "../../core" import "photo_hunter.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "white" signal start signal stop 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 var model property bool notShowed: true property alias img1: img1 property alias img2: img2 property int total property int totalFound: img1.good + img2.good property alias problem: problem property alias frame: frame } onStart: { Activity.start(items) } onStop: { Activity.stop() } - property bool vert: background.width < background.height + property bool vert: background.width <= background.height property double barHeight: ApplicationSettings.isBarHidden ? bar.height / 2 : bar.height property bool startedHelp: false function checkAnswer() { if (items.totalFound === items.model.length) { bonus.good("flower") // after completing a level, mark the problem as shown if (items.notShowed) { items.notShowed = false } //remove the problem from the board after first level if (problem.z > 0) Activity.hideProblem() } } Rectangle { id: problem width: parent.width height: problemText.height anchors.top: parent.top anchors.topMargin: 10 border.width: 2 border.color: "black" color: "red" z: 5 property alias problemText: problemText GCText { id: problemText anchors.centerIn: parent width: parent.width * 3 / 4 fontSize: mediumSize wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter text: background.startedHelp ? qsTr("Drag the slider to show the differences!") : qsTr("Click on the differences between the two images!") color: "white" onHeightChanged: { if (items.problem.z > 0) frame.problemTextHeight = problemText.height } } MouseArea { anchors.fill: parent onClicked: Activity.hideProblem() } } Rectangle { id: frame color: "transparent" width: background.vert ? img1.width : parent.width - 20 height: parent.height - background.barHeight - 30 anchors.top: problem.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.margins: 10 property real problemTextHeight: problemText.height //left/top image Observe { id: img1 show: true anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: background.startedHelp ? 0 : background.vert ? 0 : - img1.width / 2 - 5 verticalCenter: parent.verticalCenter verticalCenterOffset: background.startedHelp ? background.vert ? - frame.problemTextHeight * 0.8 : - frame.problemTextHeight * 1.01 : background.vert ? - frame.problemTextHeight * 0.5 - img1.height * 0.5 - 5 : - frame.problemTextHeight * 0.5 } } //right/bottom image Observe { id: img2 opacity: background.startedHelp ? 1 - slider.value : 1 show: false anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: background.startedHelp ? 0 : background.vert ? 0 : img1.width / 2 + 5 verticalCenter: parent.verticalCenter verticalCenterOffset: background.startedHelp ? background.vert ? - frame.problemTextHeight * 0.8 : - frame.problemTextHeight * 1.01 : background.vert ? - frame.problemTextHeight * 0.5 + img1.height * 0.5 + 5 : - frame.problemTextHeight * 0.5 } } Slider { id: slider value: 0 height: 50 width: img1.width * 0.9 z: background.startedHelp ? 5 : -5 opacity: background.startedHelp ? 1 : 0 enabled: background.startedHelp style: SliderStyle { handle: Rectangle { height: background.vert ? 80 : 70 width: height radius: width / 2 color: "lightblue" } groove: Rectangle { implicitHeight: slider.height implicitWidth: background.vert ? slider.width * 0.85 : slider.width radius: height / 2 border.color: "#6699ff" color: "#99bbff" Rectangle { height: parent.height width: styleData.handlePosition implicitHeight: 6 implicitWidth: 100 radius: height/2 color: "#4d88ff" } } } anchors { top: img1.bottom topMargin: 20 horizontalCenter: img1.horizontalCenter } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | hint } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onHintClicked: { background.startedHelp = !background.startedHelp slider.value = 0 } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } Score { anchors { bottom: parent.bottom bottomMargin: 10 * ApplicationInfo.ratio right: parent.right rightMargin: 10 * ApplicationInfo.ratio top: undefined left: undefined } numberOfSubLevels: items.total currentSubLevel: items.totalFound } } } diff --git a/src/activities/piano_composition/MelodyList.qml b/src/activities/piano_composition/MelodyList.qml index 4c6d603b7..cc1ec6f92 100644 --- a/src/activities/piano_composition/MelodyList.qml +++ b/src/activities/piano_composition/MelodyList.qml @@ -1,174 +1,174 @@ /* GCompris - MelodyList.qml * * Copyright (C) 2017 Divyam Madaan * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Divyam Madaan (Qt Quick port) * Aman Kumar Gupta (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.6 import GCompris 1.0 import QtQuick.Controls 1.5 import "../../core" import "piano_composition.js" as Activity Rectangle { id: dialogBackground color: "#696da3" border.color: "black" border.width: 1 z: 10000 anchors.fill: parent visible: false focus: true Keys.onEscapePressed: close() signal close property alias melodiesModel: melodiesModel - property bool horizontalLayout: dialogBackground.width > dialogBackground.height + property bool horizontalLayout: dialogBackground.width >= dialogBackground.height property int selectedMelodyIndex: -1 ListModel { id: melodiesModel } Row { spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { id: titleRectangle color: "#e6e6e6" radius: 6.0 width: dialogBackground.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 GCText { id: title text: qsTr("Melodies") width: dialogBackground.width - 30 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogBackground.width - 30 height: dialogBackground.height - 100 border.color: "black" border.width: 2 anchors.margins: 100 Flickable { id: flickableList anchors.fill: parent anchors.topMargin: 10 anchors.leftMargin: 20 contentWidth: parent.width contentHeight: melodiesGrid.height flickableDirection: Flickable.VerticalFlick clip: true Flow { id: melodiesGrid width: parent.width spacing: 40 anchors.horizontalCenter: parent.horizontalCenter Repeater { id: melodiesRepeater model: melodiesModel Item { id: melodiesItem width: dialogBackground.horizontalLayout ? dialogBackground.width / 5 : dialogBackground.width / 4 height: dialogBackground.height / 5 Button { text: title onClicked: { dialogBackground.selectedMelodyIndex = index items.multipleStaff.stopAudios() items.multipleStaff.nbStaves = 2 items.multipleStaff.bpmValue = defaultBPM ? defaultBPM : 60 items.multipleStaff.loadFromData(melody) lyricsArea.setLyrics(title, _origin, lyrics) } width: parent.width height: parent.height * 0.8 style: GCButtonStyle { theme: "dark" } Image { source: "qrc:/gcompris/src/core/resource/apply.svg" sourceSize.width: height sourceSize.height: height width: height height: parent.height / 4 anchors.bottom: parent.bottom anchors.right: parent.right anchors.margins: 2 visible: dialogBackground.selectedMelodyIndex === index } } } } } } // The scroll buttons GCButtonScroll { anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: flickableList.bottom anchors.bottomMargin: 30 * ApplicationInfo.ratio width: parent.width / 20 height: width * heightRatio onUp: flickableList.flick(0, 1400) onDown: flickableList.flick(0, -1400) upVisible: (flickableList.visibleArea.yPosition <= 0) ? false : true downVisible: ((flickableList.visibleArea.yPosition + flickableList.visibleArea.heightRatio) >= 1) ? false : true } } Item { width: 1; height: 10 } } } GCButtonCancel { onClose: { dialogBackground.selectedMelodyIndex = -1 parent.close() } } } diff --git a/src/activities/planegame/Planegame.qml b/src/activities/planegame/Planegame.qml index d227464cd..c30d2b425 100644 --- a/src/activities/planegame/Planegame.qml +++ b/src/activities/planegame/Planegame.qml @@ -1,150 +1,150 @@ /* gcompris - Planegame.qml Copyright (C) 2014 Johnny Jazeix 2003, 2014: Bruno Coudoin: initial version 2014: Johnny Jazeix: Qt 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.6 import GCompris 1.0 import "../../core" import "planegame.js" as Activity ActivityBase { id: activity focus: true onStart: { focus = true; } onStop: { } Keys.onPressed: Activity.processPressedKey(event) Keys.onReleased: Activity.processReleasedKey(event) property var dataset property int oldWidth: width onWidthChanged: { // Reposition helico and clouds, same for height Activity.repositionObjectsOnWidthChanged(width / oldWidth) oldWidth = width } property int oldHeight: height onHeightChanged: { // Reposition helico and clouds, same for height Activity.repositionObjectsOnHeightChanged(height / oldHeight) oldHeight = height } pageComponent: Image { id: background anchors.fill: parent signal start signal stop source: Activity.url + "../algorithm/resource/desert_scene.svg" sourceSize.width: parent.width Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias plane: plane property GCAudio audioVoices: activity.audioVoices property GCSfx audioEffects: activity.audioEffects property alias movePlaneTimer: movePlaneTimer property alias cloudCreation: cloudCreation } onStart: Activity.start(items, dataset) onStop: Activity.stop(); MultiPointTouchArea { anchors.fill: parent touchPoints: [ TouchPoint { id: point1 } ] onReleased: { plane.x = point1.x - plane.width / 2 plane.y = point1.y - plane.height / 2 } } 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) } Score { id: score visible: false - fontSize: background.width > background.height ? internalTextComponent.largeSize : internalTextComponent.mediumSize + fontSize: background.width >= background.height ? internalTextComponent.largeSize : internalTextComponent.mediumSize height: internalTextComponent.height + 10 anchors.bottom: bar.top anchors.margins: 10 } property int movePlaneTimerCounter: 0 Timer { id: movePlaneTimer running: false repeat: true onTriggered: { plane.state = "play" interval = 50 if(movePlaneTimerCounter++ % 3 == 0) { /* Do not call this too often or plane commands are too hard */ Activity.handleCollisionsWithCloud(); } Activity.computeVelocity(); Activity.planeMove(); } } Timer { id: cloudCreation running: false repeat: true interval: 10200 - (bar.level * 200) onTriggered: Activity.createCloud() } Plane { id: plane background: background } } } diff --git a/src/activities/play_piano/PlayPiano.qml b/src/activities/play_piano/PlayPiano.qml index eca6a04b8..86693378d 100644 --- a/src/activities/play_piano/PlayPiano.qml +++ b/src/activities/play_piano/PlayPiano.qml @@ -1,332 +1,332 @@ /* GCompris - PlayPiano.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Aman Kumar Gupta (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.6 import QtQuick.Controls 1.5 import GCompris 1.0 import QtMultimedia 5.0 import "../../core" import "../piano_composition" import "play_piano.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true - property bool horizontalLayout: width > height * 1.2 + property bool horizontalLayout: width >= height * 1.2 pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyboardBindings = {} keyboardBindings[Qt.Key_1] = 0 keyboardBindings[Qt.Key_2] = 1 keyboardBindings[Qt.Key_3] = 2 keyboardBindings[Qt.Key_4] = 3 keyboardBindings[Qt.Key_5] = 4 keyboardBindings[Qt.Key_6] = 5 keyboardBindings[Qt.Key_7] = 6 keyboardBindings[Qt.Key_8] = 7 keyboardBindings[Qt.Key_F1] = 1 keyboardBindings[Qt.Key_F2] = 2 keyboardBindings[Qt.Key_F3] = 3 keyboardBindings[Qt.Key_F4] = 4 keyboardBindings[Qt.Key_F5] = 5 if(piano.whiteKeysEnabled && !iAmReady.visible) { if(event.key >= Qt.Key_1 && event.key <= Qt.Key_8) { piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { if(piano.blackKeysEnabled) findBlackKey(keyboardBindings[event.key]) } else if(event.key === Qt.Key_Space) { multipleStaff.play() } else if(event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { Activity.undoPreviousAnswer() } } } function findBlackKey(keyNumber) { for(var i = 0; keyNumber; i++) { if(piano.keyRepeater.itemAt(i) == undefined) break if(piano.keyRepeater.itemAt(i).blackKey.visible) keyNumber-- if(keyNumber === 0) piano.keyRepeater.itemAt(i).blackKey.keyPressed() } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias multipleStaff: multipleStaff property alias piano: piano property alias bar: bar property alias bonus: bonus property alias score: score property alias iAmReady: iAmReady property alias introductoryAudioTimer: introductoryAudioTimer property alias parser: parser property string mode: "coloredNotes" } onStart: { dialogActivityConfig.getInitialConfiguration() Activity.start(items) } onStop: { Activity.stop() } property string clefType: (items.bar.level <= 5) ? "Treble" : "Bass" Timer { id: introductoryAudioTimer interval: 4000 onRunningChanged: { if(running) Activity.isIntroductoryAudioPlaying = true else { Activity.isIntroductoryAudioPlaying = false Activity.initSubLevel() } } } JsonParser { id: parser } Rectangle { anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 onClicked: { Activity.initLevel() } } Score { id: score anchors.top: background.top anchors.bottom: undefined numberOfSubLevels: 5 width: horizontalLayout ? parent.width / 10 : (parent.width - instruction.x - instruction.width - 1.5 * anchors.rightMargin) } Rectangle { id: instruction radius: 10 width: background.width * 0.6 height: background.height / 9 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: qsTr("Click on the piano keys that match the given notes.") } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.8 height: horizontalLayout ? parent.height * 0.85 : parent.height * 0.58 nbStaves: 1 clef: clefType coloredNotes: (items.mode === "coloredNotes") ? ['C', 'D', 'E', 'F', 'G', 'A', 'B'] : [] isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: instruction.bottom anchors.topMargin: horizontalLayout ? parent.height * 0.02 : parent.height * 0.15 onNoteClicked: { playNoteAudio(musicElementModel.get(noteIndex).noteName_, musicElementModel.get(noteIndex).noteType_, musicElementModel.get(noteIndex).soundPitch_) } centerNotesPosition: true } PianoOctaveKeyboard { id: piano width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.7 height: parent.height * 0.3 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 20 blackLabelsVisible: ([4, 5, 9, 10].indexOf(items.bar.level) != -1) blackKeysEnabled: blackLabelsVisible && !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeysEnabled: !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeyNoteLabelsTreble: [ whiteKeyNoteLabelsArray.slice(18, 26) ] whiteKeyNoteLabelsBass: [ whiteKeyNoteLabelsArray.slice(11, 19)] onNoteClicked: { multipleStaff.playNoteAudio(note, "Quarter", clefType, 500) Activity.checkAnswer(note) } useSharpNotation: true } Rectangle { id: optionDeck width: optionsRow.changeAccidentalStyleButtonVisible ? optionsRow.iconsWidth * 3.3 : optionsRow.iconsWidth * 2.2 height: optionsRow.iconsWidth * 1.1 color: "white" opacity: 0.5 radius: 10 y: horizontalLayout ? piano.y : multipleStaff.y / 2 + instruction.height - height / 2 x: horizontalLayout ? multipleStaff.x + multipleStaff.width + 25 : background.width / 2 - width / 2 } OptionsRow { id: optionsRow anchors.centerIn: optionDeck playButtonVisible: true undoButtonVisible: true onUndoButtonClicked: Activity.undoPreviousAnswer() } ExclusiveGroup { id: configOptions } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 5 width: dialogActivityConfig.width height: dialogActivityConfig.height property alias coloredNotesModeBox: coloredNotesModeBox property alias colorlessNotesModeBox: colorlessNotesModeBox GCDialogCheckBox { id: coloredNotesModeBox width: column.width - 50 text: qsTr("Display colored notes.") checked: items.mode === "coloredNotes" exclusiveGroup: configOptions onCheckedChanged: { if(coloredNotesModeBox.checked) { items.mode = "coloredNotes" } } } GCDialogCheckBox { id: colorlessNotesModeBox width: coloredNotesModeBox.width text: qsTr("Display colorless notes.") checked: items.mode === "colorlessNotes" exclusiveGroup: configOptions onCheckedChanged: { if(colorlessNotesModeBox.checked) { items.mode = "colorlessNotes" } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) items.mode = dataToSave["mode"] } onSaveData: dataToSave["mode"] = items.mode onClose: { home() } onVisibleChanged: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config | reload } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/play_rhythm/PlayRhythm.qml b/src/activities/play_rhythm/PlayRhythm.qml index bf3deffd4..e32a1c53a 100644 --- a/src/activities/play_rhythm/PlayRhythm.qml +++ b/src/activities/play_rhythm/PlayRhythm.qml @@ -1,324 +1,324 @@ /* GCompris - PlayRhythm.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Aman Kumar Gupta (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.6 import GCompris 1.0 import "../../core" import "../piano_composition" import "play_rhythm.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true - property bool horizontalLayout: width > height + property bool horizontalLayout: width >= height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop 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 GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property alias parser: parser property alias score: score property alias multipleStaff: multipleStaff property alias iAmReady: iAmReady property alias introductoryAudioTimer: introductoryAudioTimer property alias metronomeOscillation: metronomeOscillation property bool isMetronomeVisible: false property bool isWrongRhythm: false } onStart: { Activity.start(items) } onStop: { Activity.stop() } property string clefType: "Treble" property bool isRhythmPlaying: false property int metronomeSpeed: 60000 / multipleStaff.bpmValue - 53 property real weightOffset: metronome.height * multipleStaff.bpmValue * 0.004 Keys.onSpacePressed: if (!background.isRhythmPlaying && !bonus.isPlaying) tempo.tempoPressed() Rectangle { id: instructionBox radius: 10 width: background.width * 0.7 height: background.height / 9 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { id: instructionText color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: items.isMetronomeVisible ? qsTr("Use the metronome to estimate the time intervals and play the rhythm correctly.") : qsTr("Follow the vertical line and click on the tempo or press space key and play the rhythm correctly.") } } Timer { id: introductoryAudioTimer interval: 3500 onRunningChanged: { if(running) Activity.isIntroductoryAudioPlaying = true else { Activity.isIntroductoryAudioPlaying = false Activity.initSubLevel() } } } JsonParser { id: parser } Rectangle { anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 onClicked: { Activity.initLevel() } } Score { id: score anchors.top: background.top anchors.bottom: undefined numberOfSubLevels: 3 width: horizontalLayout ? parent.width / 10 : (parent.width - instructionBox.x - instructionBox.width - 1.5 * anchors.rightMargin) } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.6 : parent.width * 0.9 height: horizontalLayout ? parent.height * 1.1 : parent.height * 0.76 bpmValue: 90 nbStaves: 1 clef: clefType isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: horizontalLayout ? 0 : parent.height * 0.1 centerNotesPosition: true firstCenteredNotePosition: width / (2 * (musicElementModel.count - 1)) spaceBetweenNotes: width / (2.5 * (musicElementModel.count - 1)) enableNotesSound: false onPulseMarkerAnimationFinished: background.isRhythmPlaying = false onPlayDrumSound: { if(background.isRhythmPlaying && !metronomeOscillation.running) GSynth.generate(60, 100) } } Image { id: tempo source: "qrc:/gcompris/src/activities/play_rhythm/resource/drumhead.png" width: horizontalLayout ? parent.width / 7 : parent.width / 4 height: width / 2 anchors.top: metronome.top anchors.horizontalCenter: parent.horizontalCenter MouseArea { anchors.fill: parent enabled: !background.isRhythmPlaying && !bonus.isPlaying onPressed: tempo.tempoPressed() onReleased: tempo.scale = 1 } function tempoPressed() { tempo.scale = 0.85 if(!multipleStaff.isMusicPlaying && Activity.currentNote == 0) { multipleStaff.play() } else if (!multipleStaff.isMusicPlaying && Activity.currentNote > 0){ items.bonus.bad("flower") } GSynth.generate(60, 100) Activity.checkAnswer(multipleStaff.pulseMarkerX) } } Image { id: metronome source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_stand.svg" fillMode: Image.PreserveAspectFit sourceSize.width: parent.width / 3 sourceSize.height: parent.height / 4 width: sourceSize.width height: sourceSize.height anchors.bottom: bar.top anchors.bottomMargin: 20 visible: items.isMetronomeVisible MouseArea { anchors.fill: parent onClicked: { if(metronomeOscillation.running) metronomeOscillation.stop() else metronomeOscillation.start() } } Image { id: metronomeNeedle source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_needle.svg" fillMode: Image.PreserveAspectFit width: parent.height height: parent.height anchors.centerIn: parent transformOrigin: Item.Bottom SequentialAnimation { id: metronomeOscillation loops: Animation.Infinite onStarted: metronomeNeedle.rotation = 12 onStopped: metronomeNeedle.rotation = 0 ScriptAction { script: items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav") } RotationAnimator { target: metronomeNeedle from: 12 to: 348 direction: RotationAnimator.Shortest duration: metronomeSpeed } ScriptAction { script: items.audioEffects.play("qrc:/gcompris/src/activities/play_rhythm/resource/click.wav") } RotationAnimator { target: metronomeNeedle from: 348 to: 12 direction: RotationAnimator.Shortest duration: metronomeSpeed } } Image { id: metronomeWeight source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_weight.svg" fillMode: Image.PreserveAspectFit width: parent.height height: parent.height anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: weightOffset } } Image { id: metronomeFront source: "qrc:/gcompris/src/activities/play_rhythm/resource/metronome_front.svg" fillMode: Image.PreserveAspectFit width: parent.height height: parent.height anchors.centerIn: parent } } OptionsRow { id: optionsRow anchors.verticalCenter: tempo.verticalCenter anchors.left: tempo.right bpmVisible: true onBpmDecreased: { if(multipleStaff.bpmValue >= 51) multipleStaff.bpmValue-- } onBpmIncreased: { if(multipleStaff.bpmValue <= 179) multipleStaff.bpmValue++ } onBpmChanged: { Activity.initSubLevel() background.isRhythmPlaying = true } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: { Activity.initSubLevel() } } Bonus { id: bonus Component.onCompleted: { win.connect(Activity.nextSubLevel) loose.connect(Activity.initSubLevel) } } } } diff --git a/src/activities/railroad/Loco.qml b/src/activities/railroad/Loco.qml index bac4b5a53..9b16a9aa0 100644 --- a/src/activities/railroad/Loco.qml +++ b/src/activities/railroad/Loco.qml @@ -1,45 +1,45 @@ /* GCompris - Loco.qml * * Copyright (C) 2017 Utkarsh Tiwari * Copyright (C) 2018 Amit Sagtani * * Authors: * (GTK+ version) * Utkarsh Tiwari (Qt Quick port) * Amit Sagtani (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.6 import GCompris 1.0 import "railroad.js" as Activity Item { id: draggedItem property string imageURL Image { id: img source: imageURL height: background.height / 8.0 - width: ((background.width > background.height) ? background.width : background.height) / 5.66 + width: ((background.width >= background.height) ? background.width : background.height) / 5.66 Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 } function destroy() { // Destroy this copy object on drop draggedItem.destroy(); } } diff --git a/src/activities/railroad/Railroad.qml b/src/activities/railroad/Railroad.qml index 6084e4053..7c233f060 100644 --- a/src/activities/railroad/Railroad.qml +++ b/src/activities/railroad/Railroad.qml @@ -1,595 +1,595 @@ /* GCompris - railroad.qml * * Copyright (C) 2016 Utkarsh Tiwari * Copyright (C) 2018 Amit Sagtani * * Authors: * Pascal Georges (GTK+ version) * Utkarsh Tiwari (Qt Quick port) * Amit Sagtani (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.6 import GCompris 1.0 import "../../core" import "railroad.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} - property bool isHorizontal: background.width > background.height + property bool isHorizontal: background.width >= background.height pageComponent: Image { id: background source: Activity.resourceURL + "railroad-bg.svg" sourceSize.height: background.height fillMode: Image.PreserveAspectCrop anchors.horizontalCenter: parent.horizontalCenter signal start signal stop 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 score: score property alias trainAnimationTimer: trainAnimationTimer property alias sampleList: sampleList property alias listModel: listModel property alias answerZone: answerZone property alias animateFlow: animateFlow property alias introMessage: introMessage property bool memoryMode: false property bool mouseEnabled: true property var currentKeyZone: sampleList property bool keyNavigationMode: false // stores height of sampleGrid images to set rail bar support position property int sampleImageHeight: 0 } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.enabled: !trainAnimationTimer.running && !animateFlow.running && !introMessage.visible Keys.onPressed: { items.keyNavigationMode = true; items.currentKeyZone.handleKeys(event); activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); } // Countdown timer Timer { id: trainAnimationTimer repeat: false interval: 4000 onTriggered: { items.animateFlow.start() activity.audioEffects.play(Activity.resourceURL + 'sounds/train.wav') } } // Intro message IntroMessage { id: introMessage y: background.height / 4.7 anchors { right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: score.z + 1 onIntroDone: { trainAnimationTimer.start() } intro: [ qsTr("Observe and remember the train before the timer ends and then drag the items to set up a similar train."), qsTr("If you forget the positions, you can click on the Hint button to view them again.") ] } // Top Display Area Rectangle { id: topDisplayArea width: background.width height: background.height * 0.2 anchors.bottom: sampleList.top color: 'transparent' z: 1 GridView { id: answerZone readonly property int levelCellWidth: isHorizontal ? background.width / (listModel.count > 5 ? 7.2 : 5.66) : background.width / ((listModel.count > 5) ? 7.1 : 5) readonly property int levelCellHeight: levelCellWidth * 0.42 width: parent.width height: levelCellHeight cellWidth: levelCellWidth cellHeight: levelCellHeight anchors.bottom: parent.bottom interactive: false model: listModel delegate: Image { id: wagon source: Activity.resourceURL + modelData + ".svg" fillMode: Image.PreserveAspectFit width: answerZone.cellWidth sourceSize.width: wagon.width function checkDrop(dragItem) { // Checks the drop location of this wagon var globalCoordinates = dragItem.mapToItem(answerZone, 0, 0) if(globalCoordinates.y <= ((background.height / 12.5) + (background.height / 8))) { var dropIndex = Activity.getDropIndex(globalCoordinates.x) if(dropIndex > (listModel.count - 1)) { // Handles index overflow dropIndex = listModel.count - 1 } listModel.move(listModel.count - 1, dropIndex, 1) opacity = 1 } if(globalCoordinates.y > (background.height / 8)) { // Remove it if dropped in the lower section activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') listModel.remove(listModel.count - 1) } } function createNewItem() { var component = Qt.createComponent("Loco.qml"); if(component.status === Component.Ready) { var newItem = component.createObject(parent, {"x": x, "y": y, "z": 10, "imageURL": source}); } return newItem } MouseArea { id: displayWagonMouseArea hoverEnabled: true enabled: !introMessage.visible && items.mouseEnabled anchors.fill: parent onPressed: { if(items.memoryMode) { drag.target = parent.createNewItem(); parent.opacity = 0 listModel.move(index, listModel.count - 1, 1) } answerZone.selectedSwapIndex = -1; } onReleased: { if(items.memoryMode) { var dragItem = drag.target parent.checkDrop(dragItem) dragItem.destroy(); parent.Drag.cancel() } } onClicked: { // skips memorization time if(!items.memoryMode) { bar.hintClicked() } else { items.currentKeyZone = answerZone if(items.keyNavigationMode) { answerZone.currentIndex = index } } answerZone.selectedSwapIndex = -1; } } states: State { name: "wagonHover" when: displayWagonMouseArea.containsMouse && (items.memoryMode === true) PropertyChanges { target: wagon scale: 1.1 } } } onXChanged: { if(answerZone.x >= background.width) { trainAnimationTimer.stop() animateFlow.stop(); listModel.clear(); items.memoryMode = true; } } PropertyAnimation { id: animateFlow target: answerZone properties: "x" from: answerZone.x to: background.width duration: 4000 easing.type: Easing.InExpo loops: 1 onStopped: answerZone.x = 2; } function handleKeys(event) { // Switch zones via tab key. if(event.key === Qt.Key_Tab) { items.currentKeyZone = sampleList sampleList.currentIndex = 0 answerZone.currentIndex = -1 } if(event.key === Qt.Key_Down) { items.currentKeyZone = sampleList answerZone.currentIndex = -1 sampleList.currentIndex = 0 } if(event.key === Qt.Key_Up) { items.currentKeyZone = sampleList answerZone.currentIndex = -1 sampleList.currentIndex = 0 } if(event.key === Qt.Key_Left) { items.currentKeyZone = answerZone answerZone.moveCurrentIndexLeft() } if(event.key === Qt.Key_Right) { items.currentKeyZone = answerZone answerZone.moveCurrentIndexRight() } // Remove a wagon via Delete/Return key. if(event.key === Qt.Key_Delete && listModel.count > 0) { activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') listModel.remove(answerZone.currentIndex) if(listModel.count < 2) { answerZone.selectedSwapIndex = -1; } } // Checks answer. if((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && items.mouseEnabled) { items.currentKeyZone = answerZone Activity.checkAnswer(); } // Swaps two wagons with help of Space/Enter keys. if(event.key === Qt.Key_Space) { if(selectedSwapIndex === -1 && listModel.count > 1) { answerZone.selectedSwapIndex = answerZone.currentIndex; swapHighlight.x = answerZone.currentItem.x; swapHighlight.anchors.top = answerZone.top; } else if(answerZone.currentIndex != selectedSwapIndex && listModel.count > 1){ var min = Math.min(selectedSwapIndex, answerZone.currentIndex); var max = Math.max(selectedSwapIndex, answerZone.currentIndex); listModel.move(min, max, 1); listModel.move(max-1, min, 1); answerZone.selectedSwapIndex = -1; } } } // variable for storing the index of wagons to be swapped via key navigations. property int selectedSwapIndex: -1 Keys.enabled: true focus: true keyNavigationWraps: true highlightRangeMode: GridView.ApplyRange highlight: Rectangle { width: answerZone.cellWidth height: answerZone.cellHeight color: "blue" opacity: 0.3 radius: 5 visible: (items.currentKeyZone === answerZone) && (!trainAnimationTimer.running && !animateFlow.running) && items.keyNavigationMode x: (visible && answerZone.currentItem) ? answerZone.currentItem.x : 0 y: (visible && answerZone.currentItem) ? answerZone.currentItem.y : 0 Behavior on x { SpringAnimation { spring: 3 damping: 0.2 } } Behavior on y { SpringAnimation { spring: 3 damping: 0.2 } } } highlightFollowsCurrentItem: false } // Used to highlight a wagon selected for swaping via key navigations Rectangle { id: swapHighlight width: answerZone.cellWidth height: answerZone.cellHeight visible: answerZone.selectedSwapIndex != -1 ? true : false color: "#AA41AAC4" opacity: 0.8 radius: 5 } ListModel { id: listModel } } // Lower Sample Wagon Display Area GridView { id: sampleList visible: items.memoryMode y: background.height * 0.2 z: 5 width: background.width height: background.height * 0.8 anchors.margins: 20 cellWidth: width / columnCount cellHeight: isHorizontal ? background.height / 7 : background.height / 7.5 model: Activity.dataset["noOfLocos"][bar.level - 1] + Activity.dataset["noOfWagons"][bar.level - 1] interactive: false // No. of wagons in a row readonly property int columnCount: isHorizontal ? Activity.dataset["columnsInHorizontalMode"][bar.level - 1] : Activity.dataset["columsInVerticalMode"][bar.level - 1] readonly property int rowCount: columnCount > 0 ? model / columnCount : 0 delegate: Image { id: loco readonly property string uniqueID: Activity.uniqueId[index] property real originX property real originY source: Activity.resourceURL + uniqueID + ".svg" width: isHorizontal ? background.width / 5.66 : background.width / 4.2 sourceSize.width: width fillMode: Image.PreserveAspectFit visible: true onHeightChanged: items.sampleImageHeight = height onVisibleChanged: items.sampleImageHeight = height function initDrag() { originX = x originY = y } function replace() { x = originX y = originY } function checkDrop() { // Checks the drop location of this wagon var globalCoordinates = loco.mapToItem(answerZone, 0, 0) // checks if the wagon is dropped in correct zone and no. of wagons in answer row are less than // total no. of wagons in correct answer + 2, before dropping the wagon if(globalCoordinates.y <= (background.height / 12.5) && listModel.count < Activity.dataset["WagonsInCorrectAnswers"][bar.level - 1] + 2) { activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') var dropIndex = Activity.getDropIndex(globalCoordinates.x) Activity.addWagon(uniqueID, dropIndex); } } MouseArea { id: mouseArea hoverEnabled: true anchors.fill: parent drag.target: parent enabled: items.mouseEnabled onClicked: { items.currentKeyZone = sampleList if(items.keyNavigationMode) { sampleList.currentIndex = index } } onPressed: { parent.initDrag() } onReleased: { parent.Drag.cancel() parent.checkDrop() parent.replace() } } Component.onCompleted: initDrag(); states: State { name: "carHover" when: mouseArea.containsMouse PropertyChanges { target: loco scale: 1.1 } } } function handleKeys(event) { if(event.key === Qt.Key_Tab) { if(listModel.count > 0) { items.currentKeyZone = answerZone sampleList.currentIndex = -1 answerZone.currentIndex = 0 } } if(event.key === Qt.Key_Up) { items.currentKeyZone = sampleList // Checks if current highlighted element is in first row of the grid. if(sampleList.currentIndex < columnCount && listModel.count > 0) { items.currentKeyZone = answerZone answerZone.currentIndex = 0 sampleList.currentIndex = -1 } else { sampleList.moveCurrentIndexUp() } } if(event.key === Qt.Key_Down) { items.currentKeyZone = sampleList sampleList.moveCurrentIndexDown() } if(event.key === Qt.Key_Left) { items.currentKeyZone = sampleList sampleList.moveCurrentIndexLeft() } if(event.key === Qt.Key_Right) { items.currentKeyZone = sampleList sampleList.moveCurrentIndexRight() } if(event.key === Qt.Key_Space) { var imageId = Activity.uniqueId[sampleList.currentIndex] // At most (current level + 2) wagons are allowed in answer row at a time. if(listModel.count < Activity.dataset["WagonsInCorrectAnswers"][bar.level - 1] + 2) { activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') Activity.addWagon(imageId, listModel.count); } } if((event.key === Qt.Key_Enter || event.key === Qt.Key_Return) && listModel.count > 0 && items.mouseEnabled) { items.currentKeyZone = sampleList Activity.checkAnswer() } } Keys.enabled: true focus: true keyNavigationWraps: true highlightRangeMode: GridView.ApplyRange highlight: Rectangle { width: isHorizontal ? background.width / 5.66 : background.width / 4.2 height: isHorizontal ? sampleList.cellHeight : sampleList.cellHeight / 1.65 color: "#AA41AAC4" opacity: 0.8 radius: 5 visible: items.currentKeyZone === sampleList && items.keyNavigationMode x: (sampleList.currentIndex >= 0 && sampleList.currentItem) ? sampleList.currentItem.x : 0 y: (sampleList.currentIndex >= 0 && sampleList.currentItem) ? sampleList.currentItem.y : 0 Behavior on x { SpringAnimation { spring: 3 damping: 0.2 } } Behavior on y { SpringAnimation { spring: 3 damping: 0.2 } } } highlightFollowsCurrentItem: false } // Lower level wagons shelves Repeater { id: railSupporter model: sampleList.rowCount Rectangle { x: 0 y: sampleList.y + (sampleList.cellHeight * (index + 1)) - (sampleList.cellHeight - items.sampleImageHeight) z: 1 width: background.width height: isHorizontal ? 6 : 3 border.color: "#808180" color: "transparent" border.width: 4 } } // Answer Submission button BarButton { id: okButton source: "qrc:/gcompris/src/core/resource/bar_ok.svg" height: score.height width: height sourceSize.width: width sourceSize.height: height anchors.top: score.top z: score.z anchors { right: score.left rightMargin: 10 } ParticleSystemStarLoader { id: okButtonParticles clip: false } MouseArea { id: okButtonMouseArea anchors.fill: parent enabled: !trainAnimationTimer.running && !animateFlow.running && listModel.count > 0 && items.mouseEnabled onClicked: Activity.checkAnswer() } } DialogHelp { id: dialogHelp onClose: home() } Score { id: score height: bar.height * 0.8 width: height anchors.top: parent.top anchors.topMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottom: undefined anchors.left: undefined } Bar { id: bar content: BarEnumContent { value: help | home | level | hint } onHelpClicked: { displayDialog(dialogHelp) } z: introMessage.z onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onHintClicked: { if(!introMessage.visible && items.mouseEnabled) { if(items.memoryMode == false) { trainAnimationTimer.stop() animateFlow.stop(); listModel.clear(); for(var index = 0; index < Activity.backupListModel.length; index++) { Activity.addWagon(Activity.backupListModel[index], index); } items.memoryMode = true; okButton.visible = true; } else { Activity.restoreLevel(); okButton.visible = false; } } } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/redraw/Redraw.qml b/src/activities/redraw/Redraw.qml index e4fd4eb85..6011df873 100644 --- a/src/activities/redraw/Redraw.qml +++ b/src/activities/redraw/Redraw.qml @@ -1,440 +1,440 @@ /* GCompris - redraw.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (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.6 import GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "redraw.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property bool symmetry: false pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) - property bool landscape: width > height + property bool landscape: width >= height signal start signal stop 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 int colorSelector: 0 property alias userModel: userModel property int numberOfColumn property int numberOfColor property int numberOfLine: targetModelData.length / numberOfColumn property alias targetModel: targetModel property var targetModelData } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.onPressed: { if(event.key >= Qt.Key_0 && event.key < Qt.Key_0 + items.numberOfColor) items.colorSelector = event.key - Qt.Key_0 if(event.key == Qt.Key_Backspace) userModel.clearCurrentItem() } Keys.onEnterPressed: userModel.paintCurrentItem() Keys.onReturnPressed: userModel.paintCurrentItem() Keys.onSpacePressed: userModel.paintCurrentItem() Keys.onDeletePressed: userModel.clearCurrentItem() Keys.onRightPressed: userModel.moveCurrentIndexRight() Keys.onLeftPressed: userModel.moveCurrentIndexLeft() Keys.onDownPressed: userModel.moveCurrentIndexDown() Keys.onUpPressed: userModel.moveCurrentIndexUp() // For creating new content, dump the drawing on the console Keys.onTabPressed: Activity.dump() Row { anchors { top: parent.top right: parent.right left: parent.left bottom: bar.top } anchors.margins: 10 spacing: 20 // The color selector Flickable { id: flickable interactive: true width: 70 * ApplicationInfo.ratio height: background.height boundsBehavior: Flickable.StopAtBounds contentHeight: items.numberOfColor * width bottomMargin: bar.height Column { id: colorSelector Repeater { model: items.numberOfColor Item { width: flickable.width height: width Image { id: img source: Activity.url + Activity.colorShortcut[modelData] + ".svg" sourceSize.width: parent.width z: iAmSelected ? 10 : 1 property bool iAmSelected: modelData == items.colorSelector states: [ State { name: "notclicked" when: !img.iAmSelected && !mouseArea.containsMouse PropertyChanges { target: img scale: 0.8 } }, State { name: "clicked" when: mouseArea.pressed PropertyChanges { target: img scale: 0.7 } }, State { name: "hover" when: mouseArea.containsMouse PropertyChanges { target: img scale: 1.1 } }, State { name: "selected" when: img.iAmSelected PropertyChanges { target: img scale: 1 } } ] SequentialAnimation { id: anim running: img.iAmSelected loops: Animation.Infinite alwaysRunToEnd: true NumberAnimation { target: img property: "rotation" from: 0; to: 10 duration: 200 easing.type: Easing.OutQuad } NumberAnimation { target: img property: "rotation" from: 10; to: -10 duration: 400 easing.type: Easing.InOutQuad } NumberAnimation { target: img property: "rotation" from: -10; to: 0 duration: 200 easing.type: Easing.InQuad } } Behavior on scale { NumberAnimation { duration: 70 } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true onClicked: { activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') items.colorSelector = modelData } } } GCText { id: text1 anchors.fill: parent text: modelData fontSize: regularSize z: modelData == items.colorSelector ? 12 : 2 font.bold: true style: Text.Outline styleColor: "black" color: "white" } DropShadow { anchors.fill: text1 cached: false horizontalOffset: 1 verticalOffset: 1 radius: 8.0 samples: 16 color: "#80000000" source: text1 } } } } } Grid { id: drawAndExampleArea columns: background.landscape ? 2 : 1 width: gridWidth height: parent.height spacing: 10 property int gridWidth: parent.width - colorSelector.width // The drawing area Grid { id: drawingArea width: background.landscape ? parent.gridWidth / 2 - parent.spacing * 2 : parent.gridWidth height: background.landscape ? parent.height : parent.height / 2 columns: items.numberOfColumn Repeater { id: userModel model: items.targetModelData.length property int currentItem: 0 property bool keyNavigation: false function reset() { for(var i=0; i < items.userModel.count; ++i) userModel.itemAt(i).paint(items.colorSelector) currentItem = 0 keyNavigation = false } function clearCurrentItem() { userModel.itemAt(currentItem).paint(0) } function paintCurrentItem() { userModel.itemAt(currentItem).playEffect(items.colorSelector) userModel.itemAt(currentItem).paint(items.colorSelector) } function moveCurrentIndexRight() { keyNavigation = true if(currentItem++ >= items.targetModelData.length - 1) currentItem = 0 } function moveCurrentIndexLeft() { keyNavigation = true if(currentItem-- <= 0) currentItem = items.targetModelData.length - 1 } function moveCurrentIndexUp() { keyNavigation = true currentItem -= items.numberOfColumn if(currentItem < 0) currentItem += items.targetModelData.length } function moveCurrentIndexDown() { keyNavigation = true currentItem += items.numberOfColumn if(currentItem > items.targetModelData.length - 1) currentItem -= items.targetModelData.length } Item { id: userItem width: Math.min(drawingArea.width / items.numberOfColumn, drawingArea.height / items.numberOfLine) height: width property color color: Activity.colors[colorIndex] property int colorIndex function paint(color) { colorIndex = color } function playEffect(color) { if(color === 0) activity.audioEffects.play(Activity.url + 'eraser.wav') else activity.audioEffects.play(Activity.url + 'brush.wav') } Rectangle { id: userRect anchors.fill: parent border.width: userModel.keyNavigation && userModel.currentItem == modelData ? 3 : 1 border.color: 'black' color: parent.color Behavior on color { ColorAnimation { duration: 200 onRunningChanged: { if(!running && Activity.checkModel()) bonus.good("flower") } } } } GCText { id: text2 anchors.fill: parent anchors.margins: 4 text: parent.colorIndex == 0 ? "" : parent.colorIndex fontSize: regularSize font.bold: true style: Text.Outline styleColor: "black" color: "white" } DropShadow { anchors.fill: text2 cached: false horizontalOffset: 1 verticalOffset: 1 radius: 8.0 samples: 16 color: "#80000000" source: text2 } } } } // The painting to reproduce Grid { id: imageArea width: drawingArea.width height: drawingArea.height columns: items.numberOfColumn LayoutMirroring.enabled: activity.symmetry LayoutMirroring.childrenInherit: true Repeater { id: targetModel model: items.targetModelData Item { width: Math.min(imageArea.width / items.numberOfColumn, imageArea.height / items.numberOfLine) height: width property alias color: targetRect.color Rectangle { id: targetRect anchors.fill: parent color: Activity.colors[modelData] border.width: 1 border.color: 'black' } GCText { id: text3 anchors.fill: parent anchors.margins: 4 text: modelData == 0 ? "" : modelData fontSize: regularSize font.bold: true style: Text.Outline styleColor: "black" color: "white" } DropShadow { anchors.fill: text3 cached: false horizontalOffset: 1 verticalOffset: 1 radius: 8.0 samples: 16 color: "#80000000" source: text3 } } } } } } MultiPointTouchArea { x: drawAndExampleArea.x y: drawAndExampleArea.y width: drawAndExampleArea.width height: drawAndExampleArea.height onPressed: checkTouchPoint(touchPoints) onTouchUpdated: checkTouchPoint(touchPoints) function checkTouchPoint(touchPoints) { for(var i in touchPoints) { var touch = touchPoints[i] var block = drawingArea.childAt(touch.x, touch.y) if(block) { block.playEffect(items.colorSelector) block.paint(items.colorSelector) } } } } 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/share/Share.qml b/src/activities/share/Share.qml index 8a4f74575..8ca12cd81 100644 --- a/src/activities/share/Share.qml +++ b/src/activities/share/Share.qml @@ -1,433 +1,433 @@ /* GCompris - Share.qml * * Copyright (C) 2016 Stefan Toncu * * 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.6 import GCompris 1.0 import "../../core" import "share.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ffffb3" signal start signal stop Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() 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 instruction: instruction property int currentSubLevel: 0 property int nbSubLevel property alias listModel: listModel property bool acceptCandy: false property alias dataset: dataset property alias girlWidget: girlWidget property alias boyWidget: boyWidget property alias candyWidget: candyWidget property alias basketWidget: basketWidget property alias leftWidget: leftWidget property int totalBoys property int totalGirls property int totalCandies property int totalChildren: totalBoys + totalGirls property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 property int cellSize: Math.min(background.width / 11, background.height / (9 + barHeightAddon)) property alias repeaterDropAreas: repeaterDropAreas property int maxNumberOfCandiesPerWidget: 8 } Loader { id: dataset asynchronous: false } onStart: { Activity.start(items) } onStop: { Activity.stop() } - property bool vert: background.width > background.height + property bool vert: background.width >= background.height property int currentBoys: 0 property int currentGirls: 0 property int currentCandies: 0 property int rest property int placedInGirls property int placedInBoys property bool showCount: true property bool easyMode: true property alias wrongMove: wrongMove property bool finished: false //returns true if the x and y is in the "dest" area function contains(x, y, dest) { return (x > dest.x && x < dest.x + dest.width && y > dest.y && y < dest.y + dest.height) } //stop the candy rotation function resetCandy() { items.acceptCandy = false; candyWidget.element.rotation = 0 } //check if the answer is correct function check() { background.resetCandy() background.finished = true var ok = 0 var okRest = 0 if (listModel.count >= items.totalChildren) { for (var i = 0 ; i < listModel.count ; i++) { if (listModel.get(i).nameS === "basket") okRest = listModel.get(i).countS else if (listModel.get(i).countS === Math.floor(items.totalCandies/items.totalChildren)) ok ++ } //condition without rest if (rest == 0 && ok == items.totalChildren) { bonus.good("flower") return } //condition with rest else if (rest == okRest && ok == items.totalChildren) { bonus.good("tux") return } } //else => bad bonus.bad("flower") } //center zone Rectangle { id: grid z: 4 //map the coordinates from widgets to grid property var boy: leftWidget.mapFromItem(boyWidget, boyWidget.element.x, boyWidget.element.y) property var girl: leftWidget.mapFromItem(girlWidget, girlWidget.element.x, girlWidget.element.y) property var basket: leftWidget.mapFromItem(basketWidget, basketWidget.element.x, basketWidget.element.y) //show that the widget can be dropped here color: background.contains(boy.x, boy.y, grid) || background.contains(girl.x, girl.y, grid) || background.contains(basket.x, basket.y, grid) ? "pink" : "transparent" anchors { top: background.vert ? parent.top : leftWidget.bottom left: background.vert ? leftWidget.right : parent.left topMargin: 20 leftMargin: 20 } width: background.vert ? background.width - leftWidget.width - 40 : background.width - 40 height: ApplicationSettings.isBarHidden ? background.height : background.vert ? background.height - (bar.height * 1.1) : background.height - (bar.height * 1.1) - leftWidget.height //shows/hides the Instruction MouseArea { anchors.fill: parent // first hide the wrong move if visible, then show/hide instruction onClicked: wrongMove.visible ? wrongMove.visible = false : (instruction.opacity === 0) ? instruction.show() : instruction.hide() } ListModel { id: listModel } Flow { id: dropAreas spacing: 10 width: parent.width height: parent.height Repeater { id: repeaterDropAreas model: listModel DropChild { id: rect2 //"nameS" from listModel name: nameS } } } } //instruction rectangle Rectangle { id: instruction anchors.fill: instructionTxt opacity: 0.8 radius: 10 border.width: 2 z: 10 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } property alias text: instructionTxt.text Behavior on opacity { PropertyAnimation { duration: 200 } } //shows/hides the Instruction MouseArea { anchors.fill: parent onClicked: instruction.hide() enabled: instruction.opacity !== 0 } function show() { if(text) opacity = 1 } function hide() { opacity = 0 } } //instruction for playing the game GCText { id: instructionTxt anchors { top: background.vert ? parent.top : leftWidget.bottom topMargin: -10 horizontalCenter: grid.horizontalCenter } opacity: instruction.opacity z: instruction.z fontSize: background.vert ? regularSize : smallSize color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter width: Math.max(Math.min(parent.width * 0.8, text.length * 8), parent.width * 0.3) wrapMode: TextEdit.WordWrap } //left widget, with girl/boy/candy/basket widgets in a grid Rectangle { id: leftWidget width: background.vert ? items.cellSize * 1.74 : background.width height: background.vert ? background.height : items.cellSize * 1.74 color: "#FFFF42" border.color: "#FFD85F" border.width: 4 z: 4 //grid with ok button and images of a boy, a girl, a candy and a basket Grid { id: view x: 10 y: 10 width: background.vert ? leftWidget.width : 3 * bar.height height: background.vert ? background.height - 2 * bar.height : bar.height spacing: 10 columns: background.vert ? 1 : 5 //ok button Image { id: okButton source:"qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: items.cellSize * 1.5 fillMode: Image.PreserveAspectFit MouseArea { id: mouseArea anchors.fill: parent enabled: background.finished ? false : true onPressed: okButton.opacity = 0.6 onReleased: okButton.opacity = 1 onClicked: background.check() } } ChildWidget { id: girlWidget name: "girl" total: items.totalGirls current: background.currentGirls placedInChild: background.placedInGirls } ChildWidget { id: boyWidget name: "boy" total: items.totalBoys current: background.currentBoys placedInChild: background.placedInBoys } CandyWidget { id: candyWidget total: background.easyMode ? items.totalCandies : 8 * items.totalChildren + 1 current: background.currentCandies element.opacity: background.easyMode ? 1 : 0 } BasketWidget { id: basketWidget } } } // show message warning for placing too many candies in one area Rectangle { id: wrongMove z: 5 color: "orange" radius: width / height * 10 visible: false width: grid.width height: grid.height / 3 anchors.centerIn: grid MouseArea { anchors.fill: parent onClicked: parent.visible = false } GCText { id: wrongMoveText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width: parent.width - 2 // -2 for margin height: parent.height fontSizeMode: Text.Fit wrapMode: Text.WordWrap color: "#404040" text: qsTr("You can't put more than %1 pieces of candy in the same rectangle").arg(items.maxNumberOfCandiesPerWidget) } } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { height: column.height Column { id: column spacing: 10 width: parent.width GCDialogCheckBox { id: easyModeBox width: dialogActivityConfig.width text: qsTr("Display candy counter") checked: background.easyMode onCheckedChanged: { background.easyMode = checked Activity.reloadRandom() } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) { background.easyMode = (dataToSave["mode"] === "true"); } } onSaveData: { dataToSave = { "mode": "" + background.easyMode } } onClose: home() } //bar buttons DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload | config} onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reloadRandom() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: { win.connect(Activity.nextSubLevel) } onStop: background.finished = false } Score { anchors { left: undefined right: leftWidget.right bottom: background.vert ? bar.top : leftWidget.bottom margins: 3 * ApplicationInfo.ratio } width: basketWidget.width height: background.vert ? (basketWidget.height * 0.8) : basketWidget.height numberOfSubLevels: items.nbSubLevel currentSubLevel: items.currentSubLevel + 1 } } } diff --git a/src/activities/solar_system/SolarSystem.qml b/src/activities/solar_system/SolarSystem.qml index 1374b544b..e65608fa3 100644 --- a/src/activities/solar_system/SolarSystem.qml +++ b/src/activities/solar_system/SolarSystem.qml @@ -1,364 +1,364 @@ /* GCompris - SolarSystem.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * 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.6 import GCompris 1.0 import "../../core" import "solar_system.js" as Activity ActivityBase { id: activity onStart: { focus = true; } onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent - property bool horizontalLayout: background.width > background.height + property bool horizontalLayout: background.width >= background.height Image { id: stars fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/solar_system/resource/background.svg" width: horizontalLayout ? parent.width * 2.5 : parent.height * 2.5 height: stars.width sourceSize.width: stars.width sourceSize.height: stars.width x: horizontalLayout ? -stars.width * 0.48 : -stars.width * 0.5 + parent.width * 0.5 y: horizontalLayout ? -stars.height * 0.5 + parent.height * 0.5 : -stars.height * 0.5 + parent.height * 0.9 transform: Rotation { origin.x: stars.width / 2; origin.y: stars.height / 2; angle: 0; NumberAnimation on angle{ loops: Animation.Infinite from: 0 to: 360 duration: 108000 } } } signal start signal stop 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 planetsModel: planetsModel property alias mainQuizScreen: mainQuizScreen property alias dialogActivityConfig: dialogActivityConfig property bool assessmentMode: false property bool solarSystemVisible: true property bool quizScreenVisible: false property string temperatureHint property string lengthOfYearHint } onStart: { dialogActivityConfig.getInitialConfiguration() Activity.start(items) } onStop: Activity.stop() IntroMessage { id: message anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 10 readonly property string commonInstruction: qsTr("Mode: %1

There are two modes in the activity which you can switch from the configuration window:
1. Normal mode - In this mode you can play and learn about the Solar System.
2. Assessment mode - In this mode you can test your knowledge about the Solar System.").arg(items.assessmentMode ? qsTr("Assessment") : qsTr("Normal")) readonly property var normalModeInstructions: [ commonInstruction, qsTr("Click on the Sun or any planet to reveal questions. Each question will have 4 options, out of which one is correct."), qsTr("After a planet is clicked, the Closeness meter at the bottom-right corner of the screen represents the degree of correctness of your selected answer. The least correct answer is represented by 1%. Try again until you reach a 100% closeness by following the closeness meter, or hint which indicates the correct answer.") ] readonly property var assessmentModeInstructions: [ commonInstruction, qsTr("There are 20 questions initially with 4 options each. The progress bar at the bottom right of the screen shows your percentage score."), qsTr("If your answer is correct, your score will increase.
If your answer is wrong, your score decreases and one more question will be asked in the end along with the incorrectly answered question.
Maximum 25 questions will be asked after which no more question will be added."), qsTr("You should score above 90% to pass the assessment and become a Solar System expert!") ] intro: items.assessmentMode ? assessmentModeInstructions : normalModeInstructions onIntroChanged: index = 0 } ListModel { id: planetsModel } readonly property int itemWidth: horizontalLayout ? background.width / 9 : (background.height - bar.height) / 9 // Arrangement of all the planets in the solar system GridView { id: planetView y: horizontalLayout ? (parent.height - bar.height) / 2 - cellHeight/2 : 0 x: horizontalLayout ? 0 : parent.width / 2 - cellHeight / 2 layoutDirection: Qt.LeftToRight verticalLayoutDirection: GridView.BottomToTop width: horizontalLayout ? parent.width : cellWidth height: horizontalLayout ? cellHeight : parent.height - bar.height clip: false interactive: false visible: items.solarSystemVisible cellWidth: background.itemWidth cellHeight: cellWidth model: planetsModel delegate: PlanetInSolarModel { planetImageSource: realImg planetName: bodyName planetSize: bodySize } } QuizScreen { id: mainQuizScreen visible: items.quizScreenVisible } Rectangle { id: solarSystemImageHint radius: 30 border.width: 5 border.color: "black" width: parent.width height: parent.height visible: false z: 2000 parent: items.assessmentMode ? background : hintDialog onVisibleChanged: { if(visible) { hintCloseAnimation.stop() hintAppearAnimation.start() } else { solarSystemImageHint.x = 0 solarSystemImageHint.y = 0 } } Image { id: solarSystemImage fillMode: Image.PreserveAspectCrop source: "qrc:/gcompris/src/activities/solar_system/resource/background.svg" width: horizontalLayout ? parent.width * 2.5 : parent.height * 2.5 height: stars.width sourceSize.width: stars.width sourceSize.height: stars.width x: horizontalLayout ? -stars.width * 0.48 : -stars.width * 0.5 + parent.width * 0.5 y: horizontalLayout ? -stars.height * 0.5 + parent.height * 0.5 : -stars.height * 0.5 + parent.height * 0.9 } GridView { id: planetViewHint y: horizontalLayout ? (parent.height - bar.height) / 2 - cellHeight/2 : 0 x: horizontalLayout ? 0 : parent.width / 2 - cellHeight / 2 layoutDirection: Qt.LeftToRight verticalLayoutDirection: GridView.BottomToTop width: horizontalLayout ? parent.width : cellWidth height: horizontalLayout ? cellHeight : parent.height - bar.height clip: false interactive: false cellWidth: background.itemWidth cellHeight: cellWidth model: items.planetsModel delegate: PlanetInSolarModel { hintMode: true planetImageSource: realImg planetName: bodyName planetSize: bodySize } } NumberAnimation { id: hintAppearAnimation target: solarSystemImageHint property: horizontalLayout ? "x" : "y" from: horizontalLayout ? -width : -height to: 0 duration: 1200 easing.type: Easing.OutBack } GCButtonCancel { id: cancelButton onClose: hintCloseAnimation.start() } SequentialAnimation { id: hintCloseAnimation NumberAnimation { target: solarSystemImageHint property: horizontalLayout ? "x" : "y" to: horizontalLayout ? -width : -height duration: 1200 easing.type: Easing.InSine } onStopped: solarSystemImageHint.visible = false } } // Hint dialog while playing the quiz DialogBackground { id: hintDialog visible: false readonly property string hint1: qsTr("1. The farther a planet from the Sun, the lower is its temperature.
%1").arg(items.temperatureHint) readonly property string hint2: qsTr("2. The duration of a year on a planet increases as we go away from the Sun.
%1").arg(items.lengthOfYearHint) title: qsTr("Hint") content: "%1
%2".arg(hint1).arg(hint2) onClose: { solarSystemImageHint.visible = false home() } button0Text: qsTr("View solar system") onButton0Hit: solarSystemImageHint.visible = true } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { width: dialogActivityConfig.width height: dialogActivityConfig.height property alias assessmentModeBox: assessmentModeBox property bool initialCheckStatus GCDialogCheckBox { id: assessmentModeBox width: dialogActivityConfig.width text: qsTr("Assessment mode") checked: items.assessmentMode } } } onLoadData: { if(dataToSave && dataToSave["assessmentMode"]) items.assessmentMode = dataToSave["assessmentMode"] === "true" ? true : false Activity.numberOfLevel = items.assessmentMode ? 1 : 2 } onSaveData: { if(!dialogActivityConfig.configItem) { return } dialogActivityConfig.configItem.initialCheckStatus = items.assessmentMode if(dialogActivityConfig.configItem.assessmentModeBox.checked != items.assessmentMode) { items.assessmentMode = !items.assessmentMode dataToSave["assessmentMode"] = items.assessmentMode ? "true" : "false" Activity.numberOfLevel = items.assessmentMode ? 1 : 2 } } onClose: { if(items.assessmentMode != dialogActivityConfig.configItem.initialCheckStatus) { if(items.assessmentMode) Activity.startAssessmentMode() else { Activity.showSolarModel() } } home() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: items.mainQuizScreen.restartAssessmentMessage.visible ? withConfigWithRestart : items.solarSystemVisible ? withConfig : items.assessmentMode ? withConfigWithHint : Activity.indexOfSelectedPlanet == 0 ? withoutConfigWithoutHint : withoutConfigWithHint property BarEnumContent withConfig: BarEnumContent { value: help | home | config } property BarEnumContent withoutConfigWithHint: BarEnumContent { value: help | home | level | hint } property BarEnumContent withoutConfigWithoutHint: BarEnumContent { value: help | home | level } property BarEnumContent withConfigWithRestart: BarEnumContent { value: help | home | config | reload } property BarEnumContent withConfigWithHint: BarEnumContent { value: help | home | config | hint } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: { mainQuizScreen.closenessMeter.stopAnimations() if(items.solarSystemVisible || items.assessmentMode) activity.home() else Activity.showSolarModel() } onHintClicked: { if(items.assessmentMode) solarSystemImageHint.visible = true else displayDialog(hintDialog) } onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: Activity.startAssessmentMode() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/submarine/Submarine.qml b/src/activities/submarine/Submarine.qml index 7e8b175a1..21f98016a 100644 --- a/src/activities/submarine/Submarine.qml +++ b/src/activities/submarine/Submarine.qml @@ -1,980 +1,980 @@ /* GCompris - submarine.qml * * Copyright (C) 2017 RUDRA NIL BASU * * Authors: * Pascal Georges (GTK+ version) * Rudra Nil Basu (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.6 import QtQuick.Particles 2.0 import Box2D 2.0 import QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "submarine.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property string url: "qrc:/gcompris/src/activities/submarine/resource/" pageComponent: Image { id: background source: url + "background.svg" anchors.fill: parent sourceSize.height: parent.height sourceSize.width: parent.width onWidthChanged: updateOnWidthReset.start() onHeightChanged: Activity.resetUpperGate() - property bool hori: background.width > background.height + property bool hori: background.width >= background.height signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } /* Testing purposes, A / Left Key => Reduces velocity, D / Right Key => Increases velocity */ Keys.onPressed: { if ((event.key == Qt.Key_D || event.key == Qt.Key_Right) && !tutorial.visible) { submarine.increaseHorizontalVelocity(1) } if ((event.key == Qt.Key_A || event.key == Qt.Key_Left) && !tutorial.visible) { submarine.decreaseHorizontalVelocity(1) } if ((event.key == Qt.Key_W || event.key == Qt.Key_Up) && !tutorial.visible) { centralBallastTank.fillBallastTanks() controls.updateVannes(centralBallastTank.waterFilling, controls.rotateCentralFill) } if ((event.key == Qt.Key_S || event.key == Qt.Key_Down) && !tutorial.visible) { centralBallastTank.flushBallastTanks() controls.updateVannes(centralBallastTank.waterFlushing, controls.rotateCentralFlush) } if ((event.key == Qt.Key_Plus) && !tutorial.visible) { submarine.increaseWingsAngle(1) } if ((event.key == Qt.Key_Minus) && !tutorial.visible) { submarine.decreaseWingsAngle(1) } if ((event.key == Qt.Key_R) && !tutorial.visible) { leftBallastTank.fillBallastTanks() controls.updateVannes(leftBallastTank.waterFilling, controls.rotateLeftFill) } if ((event.key == Qt.Key_F) && !tutorial.visible) { leftBallastTank.flushBallastTanks() controls.updateVannes(leftBallastTank.waterFlushing, controls.rotateLeftFlush) } if ((event.key == Qt.Key_T) && !tutorial.visible) { rightBallastTank.fillBallastTanks() controls.updateVannes(rightBallastTank.waterFilling, controls.rotateRightFill) } if ((event.key == Qt.Key_G) && !tutorial.visible) { rightBallastTank.flushBallastTanks() controls.updateVannes(rightBallastTank.waterFlushing, controls.rotateRightFlush) } } // 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 crown: crown property alias whale: whale property var submarineCategory: Fixture.Category1 property var crownCategory: Fixture.Category2 property var whaleCategory: Fixture.Category3 property var upperGatefixerCategory: Fixture.Category4 property var lowerGatefixerCategory: Fixture.Category5 property var shipCategory: Fixture.Category6 property var rockCategory: Fixture.Category7 property var maxDepthCategory: Fixture.Category8 property alias submarine: submarine property alias tutorial: tutorial property alias upperGate: upperGate property alias ship: ship property alias controls: controls property alias physicalWorld: physicalWorld property bool processingAnswer: false } IntroMessage { id: tutorial textContainerHeight: 0.5 * parent.height z: 100 onIntroDone: { tutorial.visible = false } } onStart: { Activity.start(items) } onStop: { Activity.stop() } World { id: physicalWorld running: !tutorial.visible && !items.processingAnswer gravity: Qt.point(0,0) autoClearForces: false } Item { id: waterLevel x: 0 y: background.height / 15 } Rectangle { id: maximumWaterDepth width: background.width height: 10 color: "transparent" y: background.height * 0.65 Body { id: maxDepthBody target: maximumWaterDepth bodyType: Body.Static sleepingAllowed: true linearDamping: 0 fixtures: Box { categories: items.maxDepthCategory collidesWith: items.submarineCategory width: maximumWaterDepth.width height: maximumWaterDepth.height density: 1 friction: 0 restitution: 0 } } } Item { id: submarine z: 1 property point initialPosition: Qt.point(0,waterLevel.y - submarineImage.height/2) property bool isHit: false property int terminalVelocityIndex: 75 property int maxAbsoluteRotationAngle: 15 /* Maximum depth the submarine can dive when ballast tank is full */ property real maximumDepthOnFullTanks: maximumWaterDepth.y * 0.45 property real ballastTankDiveSpeed: 10 /* Engine properties */ property point velocity property int maximumXVelocity: 5 property int currentFinalVelocity: 0 /* Wings property */ property int wingsAngle property int initialWingsAngle: 0 property int maxWingsAngle: 2 property int minWingsAngle: -2 function destroySubmarine() { isHit = true } function resetSubmarine() { isHit = false submarineImage.reset() leftBallastTank.resetBallastTanks() rightBallastTank.resetBallastTanks() centralBallastTank.resetBallastTanks() currentFinalVelocity = 0 velocity = Qt.point(0,0) smoothHorizontalVelocity.stop() wingsAngle = initialWingsAngle } /* While increasing or decreasing, we can't use submarine.velocity.x since it is interpolating */ function increaseHorizontalVelocity(amount) { if (submarine.currentFinalVelocity + amount <= submarine.maximumXVelocity) { submarine.currentFinalVelocity += amount smoothHorizontalVelocity.stop() smoothHorizontalVelocity.setFinalVelocity(submarine.currentFinalVelocity) smoothHorizontalVelocity.setIncreaseVelocity(true) smoothHorizontalVelocity.start() } } function decreaseHorizontalVelocity(amount) { if (submarine.currentFinalVelocity - amount >= 0) { submarine.currentFinalVelocity -= amount smoothHorizontalVelocity.stop() smoothHorizontalVelocity.setFinalVelocity(submarine.currentFinalVelocity) smoothHorizontalVelocity.setIncreaseVelocity(false) smoothHorizontalVelocity.start() } } function increaseWingsAngle(amount) { if (wingsAngle + amount <= maxWingsAngle) { wingsAngle += amount } else { wingsAngle = maxWingsAngle } } function decreaseWingsAngle(amount) { if (wingsAngle - amount >= minWingsAngle) { wingsAngle -= amount } else { wingsAngle = minWingsAngle } } function changeVerticalVelocity() { /* Check if we are currently using diving planes or ballast tanks */ var isDivingPlanesActive if (submarineImage.y > 0 && submarine.velocity.x > 0 && wingsAngle != 0) { /* * Movement due to planes * Movement is affected only when the submarine is moving forward * When the submarine is on the surface, the planes cannot be used */ isDivingPlanesActive = true } else { isDivingPlanesActive = false } var yPosition if (isDivingPlanesActive) { /* Currently using diving planes */ var multiplier if (wingsAngle == 1) { multiplier = 0.6 } else if (wingsAngle == 2) { multiplier = 0.8 } else if (wingsAngle == -1) { multiplier = 0.2 } else if (wingsAngle == -2) { multiplier = 0.1 } yPosition = multiplier * maximumWaterDepth.y } else { /* Currently under the influence of Ballast Tanks */ yPosition = submarineImage.currentWaterLevel / submarineImage.totalWaterLevel * submarine.maximumDepthOnFullTanks if (bar.level >= 7) { var finalAngle = ((rightBallastTank.waterLevel - leftBallastTank.waterLevel) / leftBallastTank.maxWaterLevel) * submarine.maxAbsoluteRotationAngle submarineRotation.angle = finalAngle } } var depthToMove if (submarineImage.y <= submarine.initialPosition.y && yPosition == 0){ depthToMove = 0 }else { depthToMove = yPosition - submarineImage.y } submarine.velocity.y = ballastTankDiveSpeed * (depthToMove / background.width) } Timer { id: smoothHorizontalVelocity running: false repeat: true interval: 100 property real finalVelocity property real smoothRate: 0.1 property bool increaseVelocity function increaseVelocitySmoothly() { if (submarine.velocity.x + smoothRate > finalVelocity) { submarine.velocity.x = finalVelocity smoothHorizontalVelocity.stop() } else { submarine.velocity.x += smoothRate } } function decreaseVelocitySmoothly() { if (submarine.velocity.x - smoothRate <= finalVelocity) { submarine.velocity.x = finalVelocity smoothHorizontalVelocity.stop() } else { submarine.velocity.x -= smoothRate } } function setFinalVelocity(_finalVelocity) { finalVelocity = _finalVelocity } function setIncreaseVelocity(value) { increaseVelocity = value } onTriggered: { if (increaseVelocity) { increaseVelocitySmoothly() } else { decreaseVelocitySmoothly() } } } BallastTank { id: leftBallastTank } BallastTank { id: rightBallastTank } BallastTank { id: centralBallastTank } Image { id: submarineImage source: submarine.isHit ? url + "submarine-broken.svg" : url + "submarine.svg" property int currentWaterLevel: bar.level < 7 ? centralBallastTank.waterLevel : leftBallastTank.waterLevel + rightBallastTank.waterLevel property int totalWaterLevel: bar.level < 7 ? centralBallastTank.maxWaterLevel : leftBallastTank.maxWaterLevel + rightBallastTank.maxWaterLevel width: background.width / 9 sourceSize.width: submarineImage.width fillMode: Image.PreserveAspectFit function reset() { x = submarine.initialPosition.x y = submarine.initialPosition.y } onXChanged: { if (submarineImage.x >= background.width) { Activity.finishLevel(true) } } transform: Rotation { id: submarineRotation origin.x: submarineImage.width / 2; origin.y: 0; angle: 0; Behavior on angle { NumberAnimation { duration: 1000 } } } Loader { anchors.fill: parent active: ApplicationInfo.hasShader && submarine.velocity.x > 0 && submarineImage.y > 0 && !submarine.isHit sourceComponent: ParticleSystem { anchors.fill: parent Emitter { x: parent.x y: parent.y + parent.height / 1.75 width: 1 height: 1 emitRate: 0.8 lifeSpan: 800 lifeSpanVariation: 2500 acceleration: PointDirection { x: -20 xVariation: 5 y: 0 yVariation: 0 } velocity: PointDirection { x: -20 xVariation: 10 y: 0 yVariation: 0 } size: 12 sizeVariation: 8 } ImageParticle { source: "qrc:/gcompris/src/activities/clickgame/resource/bubble.png" } } } } Body { id: submarineBody target: submarineImage bodyType: Body.Dynamic fixedRotation: true linearDamping: 0 linearVelocity: submarine.isHit ? Qt.point(0,0) : submarine.velocity fixtures: [ Box { id: submarineFixer y: submarineImage.height * 0.50 width: submarineImage.width height: submarineImage.height * 0.50 categories: items.submarineCategory collidesWith: Fixture.All density: 1 friction: 0 restitution: 0 onBeginContact: { var collidedObject = other.getBody().target if (collidedObject == whale) { whale.hit() } if (collidedObject == crown) { crown.captureCrown() } else { Activity.finishLevel(false) } } }, Box { id: submarinePeriscopeFixer x: submarineImage.width * 0.5 width: submarineImage.width * 0.25 height: submarineImage.height categories: items.submarineCategory collidesWith: Fixture.All density: 1 friction: 0 restitution: 0 onBeginContact: { var collidedObject = other.getBody().target if (collidedObject == whale) { whale.hit() } if (collidedObject == crown) { crown.captureCrown() } else { Activity.finishLevel(false) } } } ] } Timer { id: updateVerticalVelocity interval: 50 running: true repeat: true onTriggered: submarine.changeVerticalVelocity() } } Image { id: sparkle source: "qrc:/gcompris/src/activities/mining/resource/sparkle.svg" x: crown.x y: crown.y z: 1 width: crown.width height: width * 0.7 property bool isCaptured: false scale: isCaptured ? 1 : 0 function createSparkle() { isCaptured = true removeSparkleTimer.start() } function removeSparkle() { isCaptured = false } Behavior on scale { NumberAnimation { duration: 100 } } Timer { id: removeSparkleTimer interval: 3000 repeat: false running: false onTriggered: sparkle.removeSparkle() } } Rectangle { id: upperGate visible: (bar.level > 1) ? true : false width: background.width / 18 height: isGateOpen ? background.height * (5 / 36) : background.height * (5 / 12) + 4 y: -2 z: 2 color: "#9E948A" border.color: "#766C62" border.width: 2 anchors.right: background.right anchors.rightMargin: -2 property bool isGateOpen: false Body { id: upperGateBody target: upperGate bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: upperGatefixer width: upperGate.width height: upperGate.height categories: items.upperGatefixerCategory collidesWith: upperGate.visible ? items.submarineCategory : Fixture.None density: 1 friction: 0 restitution: 0 } } Behavior on height { NumberAnimation { duration: 1000 } } } Rectangle { id: lowerGate z: 1 visible: upperGate.visible width: background.width / 18 height: background.height * (5 / 12) - subSchemaImage.height / 1.4 y: background.height * (5 / 12) color: "#9E948A" border.color: "#766C62" border.width: 2 anchors.right:background.right anchors.rightMargin: -2 Body { id: lowerGateBody target: lowerGate bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: lowerGatefixer width: lowerGate.width height: lowerGate.height categories: items.lowerGatefixerCategory collidesWith: lowerGate.visible ? items.submarineCategory : Fixture.None density: 1 friction: 0 restitution: 0 } } } Rectangle { id: subSchemaImage width: background.width/1.3 height: background.height/4 x: background.width/9 y: background.height/1.5 visible: false } Image { id: crown width: submarineImage.width * 0.85 height: crown.width * 0.5 sourceSize.width: crown.width sourceSize.height: crown.height visible: ((bar.level > 2) && !isCaptured) ? true : false source: url + "crown.svg" property bool isCaptured: false function captureCrown() { upperGate.isGateOpen = true isCaptured = true sparkle.createSparkle() } function reset() { isCaptured = false upperGate.isGateOpen = false } x: background.width / 2 y: background.height - (subSchemaImage.height * 2) z: 1 Body { id: crownbody target: crown bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: crownfixer width: crown.width height: crown.height sensor: true categories: items.crownCategory collidesWith: crown.visible ? items.submarineCategory : Fixture.None density: 0.1 friction: 0 restitution: 0 } } } Whale { id: whale visible: (bar.level > 5) ? true : false y: rock2.y - (rock2.height * 1.15) z: 1 leftLimit: 0 rightLimit: background.width - whale.width - (upperGate.visible ? upperGate.width : 0) } Image { id: ship width: background.width / 9 sourceSize.width: ship.width fillMode: Image.PreserveAspectFit visible: (bar.level > 3) ? true : false source: collided ? url + "boat-hit.svg" : url + "boat.svg" x: initialXPosition z: 1 anchors.bottom: waterLevel.top property bool movingLeft: true property bool collided: false property real initialXPosition: background.width - ship.width - (upperGate.visible ? upperGate.width : 0) property real horizontalSpeed: 1 function reset() { ship.collided = false ship.x = initialXPosition } function collide() { /* Add few visual effects */ collided = true } transform: Rotation { id: rotateShip origin.x: ship.width / 2; origin.y: 0; axis { x: 0; y: 1; z: 0 } angle: 0 } SequentialAnimation { id: rotateShipLeft loops: 1 PropertyAnimation { target: rotateShip properties: "angle" from: 0 to: 180 duration: 500 } } SequentialAnimation { id: rotateShipRight loops: 1 PropertyAnimation { target: rotateShip properties: "angle" from: 180 to: 0 duration: 500 } } onXChanged: { if (x <= 0) { rotateShipLeft.start() movingLeft = false } else if (x >= background.width - ship.width - (upperGate.visible ? upperGate.width : 0)) { rotateShipRight.start() movingLeft = true } } Body { id: shipbody target: ship bodyType: Body.Dynamic sleepingAllowed: true fixedRotation: true linearDamping: 0 linearVelocity: Qt.point( (ship.collided ? 0 : ((ship.movingLeft ? -1 : 1) * ship.horizontalSpeed)), 0) fixtures: Box { id: shipfixer width: ship.width height: ship.height categories: items.shipCategory collidesWith: ship.visible ? items.submarineCategory : Fixture.None density: 1 friction: 0 restitution: 0 onBeginContact: ship.collide() } } } Image { id: rock2 width: background.width / 6 height: rock2.width * 0.48 z: 5 visible: (bar.level > 4) ? true : false anchors.bottom: crown.bottom anchors.left: crown.right source: "qrc:/gcompris/src/activities/mining/resource/stone2.svg" transform: Rotation { origin.x: rock2.width / 2; origin.y: rock2.height / 2 axis { x: 0; y: 0; z: 1 } angle: 180 } Body { id: rock2Body target: rock2 bodyType: Body.Static sleepingAllowed: true linearDamping: 0 fixtures: Box { id: rock2Fixer categories: items.rockCategory collidesWith: rock2.visible ? items.submarineCategory : Fixture.None x: rock2.width / 8 y: rock2.height / 12 width: rock2.width / 1.2 height: rock2.height / 1.5 density: 1 friction: 0 restitution: 0 } } } /* Just a space */ Rectangle { id: space width: bar.level < 8 ? rock1.width : rock1.width * (1 - (Math.random() * 0.5)) height: rock1.height color: "transparent" anchors { right: crown.left bottom: crown.bottom } } Image { id: rock1 width: rock2.width height: rock2.width * 0.46 z: 5 visible: (bar.level > 6) ? true : false anchors.bottom: crown.bottom anchors.right: space.left source: "qrc:/gcompris/src/activities/mining/resource/stone1.svg" Body { id: rock1Body target: rock1 bodyType: Body.Static sleepingAllowed: true linearDamping: 0 fixtures: [ Circle { id: rock1FixerLeft categories: items.rockCategory collidesWith: rock1.visible ? items.submarineCategory : Fixture.None x: rock1.width / 10 radius: rock1.width / 4 density: 1 friction: 0 restitution: 0 },Circle { id: rock1FixerRight categories: items.rockCategory collidesWith: rock1.visible ? items.submarineCategory : Fixture.None x: rock1.width / 1.6 y: rock1.height / 4 radius: rock1.width / 6 density: 1 friction: 0 restitution: 0 } ] } } Image { id: rock3 width: background.width height: background.height * 0.25 sourceSize.width: rock3.width sourceSize.height: rock3.height visible: (bar.level > 2) ? true : false anchors.top: crown.top anchors.horizontalCenter: crown.left // anchors.topMargin: height * 0.5 source: url + "rocks.svg" } Timer { /* * A delay is used since on setting fullscreen on/off * first the onWidthChanged is executed, followed by * the width change */ id: updateOnWidthReset repeat: false interval: 100 running: false onTriggered: { whale.reset() ship.reset() } } Controls { id: controls z: 10 enginePosition.x: background.width * 0.1 enginePosition.y: buttonPlusY + buttonSize * 0.2 engineWidth: background.width / 8 engineHeight: hori ? buttonSize * 1.8 : buttonSize * 2.5 submarineHorizontalSpeed: submarine.currentFinalVelocity * 1000 leftTankVisible: bar.level >= 7 ? true : false leftBallastTankPosition.x: background.width * 0.35 leftBallastTankPosition.y: enginePosition.y leftBallastTankWidth: background.width / 8 leftBallastTankHeight: engineHeight centralTankVisible: bar.level < 7 ? true : false centralBallastTankPosition.x: background.width * 0.45 centralBallastTankPosition.y: enginePosition.y centralBallastTankWidth: background.width / 8 centralBallastTankHeight: engineHeight rightTankVisible: bar.level >= 7 ? true : false rightBallastTankPosition.x: background.width * 0.6 rightBallastTankPosition.y: enginePosition.y rightBallastTankWidth: background.width / 8 rightBallastTankHeight: engineHeight divingPlaneVisible: true divingPlanePosition.x: background.width * 0.8 divingPlanePosition.y: enginePosition.y + (engineHeight * 0.5) - (divingPlaneHeight * 0.5) divingPlaneWidth: hori ? background.width * 0.08 : background.width * 0.12 divingPlaneHeight: divingPlaneWidth * 0.33 buttonSize: hori ? subSchemaImage.height * 0.3 : subSchemaImage.height * 0.2 buttonPlusY: hori ? background.height * 0.61 : background.height * 0.63 buttonMinusY: enginePosition.y + engineHeight - buttonSize * 0.8 } 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 onLoose: Activity.initLevel() Component.onCompleted: win.connect(Activity.nextLevel) } /* DebugDraw { id: debugDraw world: physicalWorld anchors.fill: parent opacity: 0.75 visible: false } MouseArea { id: debugMouseArea anchors.fill: parent onPressed: debugDraw.visible = !debugDraw.visible } */ } } diff --git a/src/activities/superbrain/Superbrain.qml b/src/activities/superbrain/Superbrain.qml index de6ca95ec..af4b63693 100644 --- a/src/activities/superbrain/Superbrain.qml +++ b/src/activities/superbrain/Superbrain.qml @@ -1,707 +1,707 @@ /* GCompris - Superbrain.qml * * Copyright (C) 2015 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (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.6 import "../../core" import "superbrain.js" as Activity import GCompris 1.0 ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Image { id: background source: Activity.baseUrl + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop focus: true readonly property double scaleFactor: 1 - readonly property bool isPortrait: (height > width) + readonly property bool isPortrait: (height >= width) signal start signal stop MouseArea { anchors.fill: parent onClicked: showChooser(false); } Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias colorsRepeater: colorsRepeater property alias chooserGrid: chooserGrid property alias guessModel: guessModel property alias guessColumn: guessColumn property alias currentRepeater: currentRepeater } onStart: { Activity.start(items) } onStop: { Activity.stop() } Column { id: colorsColumn anchors.left: parent.left anchors.leftMargin: 5 * ApplicationInfo.ratio anchors.top: parent.top anchors.topMargin: 5 * ApplicationInfo.ratio spacing: 3 * ApplicationInfo.ratio width: guessColumn.guessSize height: guessColumn.guessSize add: Transition { NumberAnimation { properties: "y"; duration: 1000; easing.type: Easing.OutBounce } } Repeater { id: colorsRepeater model: ListModel {} delegate: SearchItem { width: 40 * ApplicationInfo.ratio height: 40 * ApplicationInfo.ratio border.width: 2 border.color: "white" searchItemIndex: itemIndex } } } Rectangle { id: tooltipRect width: 100 * ApplicationInfo.ratio height: tooltipText.height + 10 * ApplicationInfo.ratio radius: 4 x: 0 y: 0 color: "lightgray" opacity: 0 z: 10 property alias text: tooltipText.text GCText { id: tooltipText anchors.centerIn: parent fontSize: 13 text: "" color: "black" onTextChanged: parent.width = width + 10 } Behavior on opacity { NumberAnimation { duration: 100 } } } function showTooltip(visible, status, mouseArea) { if (!visible || status === Activity.STATUS_UNKNOWN) { tooltipRect.opacity = 0; return; } showChooser(false); var obj = background.mapFromItem(mouseArea, mouseArea.mouseX, mouseArea.mouseY); if (status === Activity.STATUS_CORRECT) tooltipRect.text = qsTr("This item is well placed"); if (status === Activity.STATUS_MISPLACED) tooltipRect.text = qsTr("This item is misplaced"); tooltipRect.x = obj.x - 5 - tooltipRect.width; tooltipRect.y = obj.y - 5 - tooltipRect.height; tooltipRect.opacity = 0.9; } function showChooser(visible, guessIndex, item) { if (!visible) { chooserTimer.stop(); chooser.scale = 0; return; } var modelObj = guessModel.get(0).guess.get(guessIndex); var absolute = currentRow.mapToItem(background, item.x, item.y); chooserGrid.colIndex = modelObj.colIndex; chooserGrid.guessIndex = guessIndex; var chooserOffset = 0.5*chooser.width - item.width/2; var arrowOffset = 0; var targetX = item.x - chooserOffset; // beyond left screen border: if (absolute.x - chooserOffset < 0) { arrowOffset = absolute.x - chooserOffset; targetX -= arrowOffset; } // beyond right screen border: if (absolute.x + chooserOffset + item.width > background.width) { arrowOffset = absolute.x + chooserOffset + item.width - background.width; targetX -= arrowOffset; } chooser.x = targetX; chooser.arrowOffset = arrowOffset; var targetY = item.y - chooser.height - 15; var targetAbove = true; /* //only on top-level, at window border: if (targetY < 0) { targetY = item.y + guessColumn.guessSize + 10; targetAbove = false; }*/ chooser.y = targetY; chooser.above = targetAbove; chooser.scale = 1; chooser.visible = true; chooserTimer.restart(); //console.log("XXX chooser at item.x=" + item.x + " absolute.x=" + absolute.x + " chooser.x/w=" + chooser.x + "/" + chooser.width + " background.width=" + background.width + " currentRow.x/y/w/h=" + currentRow.x + "/" + currentRow.y + "/" + currentRow.width + "/" + currentRow.height + " guessIdx=" + guessIndex + " arrowOff=" + arrowOffset); } Item { id: currentWrapper width: currentRow.width height: currentRow.height z: 8 anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 20 * ApplicationInfo.ratio state: ApplicationSettings.isBarHidden ? "hidden" : "shown" states: [ State { name: "hidden" when: ApplicationSettings.isBarHidden AnchorChanges { target: currentWrapper; anchors.bottom: parent.bottom } }, State { name: "shown" when: !ApplicationSettings.isBarHidden AnchorChanges { target: currentWrapper; anchors.bottom: bar.top } } ] transitions: Transition { AnchorAnimation { duration: 800; easing.type: Easing.OutBounce } } Rectangle { id: chooser width: chooserGrid.width + 15 height: chooserGrid.height + 15 color: "darkgray" border.width: 0 border.color: "white" opacity: 1 scale: 0 visible: false z: 10 property bool above: true property real arrowOffset: 0 Rectangle { id: chooserArrow width: 10 height: 10 x: chooser.width / 2 - 5 + chooser.arrowOffset y: chooser.above ? (chooser.height - 5) : (-5) color: chooser.color z: chooser.z transform: Rotation { origin.x: 5; origin.y: 5; angle: 45} } GridView { id: chooserGrid cellWidth: guessColumn.guessSize * 2 cellHeight: guessColumn.guessSize * 2 width: Math.ceil(count / 2) * cellWidth height: 2 * cellHeight anchors.centerIn: parent z: 11 clip: false interactive: false verticalLayoutDirection: GridView.TopToBottom layoutDirection: Qt.LeftToRight flow: GridView.FlowLeftToRight property int colIndex: 0 property int guessIndex: 0 Timer { id: chooserTimer interval: 5000 onTriggered: showChooser(false); } model: new Array() delegate: SearchItem { id: chooserItem width: chooserGrid.cellWidth height: chooserGrid.cellWidth border.width: index == chooserGrid.colIndex ? 3 : 1 border.color: index == chooserGrid.colIndex ? "white" : "darkgray" highlightSymbol: index == chooserGrid.colIndex searchItemIndex: modelData radius: 5 MouseArea { id: chooserMouseArea anchors.fill: parent acceptedButtons: Qt.LeftButton z: 11 hoverEnabled: ApplicationInfo.isMobile ? false : true onClicked: { chooserGrid.colIndex = chooserItem.searchItemIndex; var obj = items.guessModel.get(0); obj.guess.setProperty(chooserGrid.guessIndex, "colIndex", chooserGrid.colIndex); showChooser(false); } } } } Behavior on scale { NumberAnimation { duration: 100 } } } Row { id: currentRow visible: true property double factor: 1.9 anchors.left: parent.left anchors.top: parent.top spacing: guessColumn.horizSpacing * factor height: guessColumn.guessSize * factor scale: 1 z: 9 Repeater { id: currentRepeater delegate: SearchItem { id: currentGuess width: guessColumn.guessSize * currentRow.factor height: guessColumn.guessSize * currentRow.factor border.width: 2 * currentRow.factor border.color: "lightgray" searchItemIndex: colIndex opacity: 1.0 z: 2 MouseArea { id: mouseArea anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton enabled: true z: 3 hoverEnabled: ApplicationInfo.isMobile ? false : true onPressAndHold: { if (guessColumn.count > 1) guessModel.get(0).guess.get(index).colIndex = guessModel.get(1).guess.get(index).colIndex; } onClicked: { var obj = items.guessModel.get(0).guess.get(index); if(chooserTimer.running && chooserGrid.guessIndex === index) { if (mouse.button == Qt.LeftButton) obj.colIndex = (obj.colIndex == Activity.currentIndeces.length - 1) ? 0 : obj.colIndex + 1; else obj.colIndex = (obj.colIndex == 0) ? Activity.currentIndeces.length - 1 : obj.colIndex - 1; } showChooser(true, index, parent); } } states: State { name: "scaled"; when: mouseArea.containsMouse PropertyChanges { target: currentGuess scale: 1.1 } } transitions: Transition { NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } } } } BarButton { id: okButton source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 66 * bar.barZoom width: guessColumn.guessSize * currentRow.factor height: guessColumn.guessSize * currentRow.factor visible: true z: 8 onClicked: { showChooser(false); Activity.checkGuess(); } } } } ListModel { id: guessModel dynamicRoles: true } ListView { id: guessColumn anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: currentWrapper.top anchors.bottomMargin: 10 * ApplicationInfo.ratio boundsBehavior: Flickable.DragOverBounds verticalLayoutDirection: ListView.BottomToTop readonly property int guessSize: 30 * ApplicationInfo.ratio readonly property int vertSpacing: 15 * ApplicationInfo.ratio readonly property int horizSpacing: 15 * ApplicationInfo.ratio readonly property int statusMargin: 5 * ApplicationInfo.ratio readonly property int resultSize: 10 * ApplicationInfo.ratio readonly property int guessColWidth: Activity.maxPieces * (guessSize + (2 * guessColumn.statusMargin)) + (Activity.maxPieces-1) * horizSpacing; readonly property int resultColWidth: Activity.maxPieces * resultSize + (Activity.maxPieces-1) * 2; spacing: vertSpacing width: guessColWidth + 10 + (2 * horizSpacing) + resultColWidth height: count * (guessSize + vertSpacing) displaced: Transition { NumberAnimation { easing.type: Easing.OutCubic; properties: "y"; duration: 300 } } model: guessModel delegate: Row { id: guessRow width: guessColumn.width height: guessColumn.guessSize spacing: guessColumn.horizSpacing property int rowIndex: index visible: index != 0 Item { id: guessRowSpacer width: guessColumn.guessColWidth - (guessRepeater.count * (guessColumn.guessSize + (2 * guessColumn.statusMargin) + guessColumn.horizSpacing)) height: parent.height } Repeater { id: guessRepeater anchors.left: parent.left anchors.top: parent.top model: guess delegate: Item { // wrapper needed for singleGuessStatusRect's opacity id: singleGuessWrapper width: guessColumn.guessSize + (2 * guessColumn.statusMargin); height: guessColumn.guessSize + (2 * guessColumn.statusMargin); Rectangle { id: singleGuessStatusRect border.width: 2 border.color: (status == Activity.STATUS_CORRECT) ? "white" : "black"; anchors.fill: parent radius: 3 color: (status == Activity.STATUS_CORRECT) ? "black" : "white"; opacity: (status == Activity.STATUS_UNKNOWN) ? 0 : 0.9 z: 1 MouseArea { id: mouseAreaRect anchors.fill: parent acceptedButtons: Qt.LeftButton enabled: guessRow.rowIndex > 0 z: 4 hoverEnabled: ApplicationInfo.isMobile ? false : true Timer { id: tooltipTimer repeat: false interval: 500 onTriggered: showTooltip(true, status, mouseAreaRect) } onEntered: tooltipTimer.restart() onExited: { tooltipTimer.stop() showTooltip(false) } onClicked: showTooltip(true, status, mouseAreaRect); onDoubleClicked: Activity.ackColor(index, colIndex); } } SearchItem { id: singleGuess width: guessColumn.guessSize height: guessColumn.guessSize anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: guessColumn.statusMargin anchors.topMargin: guessColumn.statusMargin border.width: 2 border.color: "lightgray" searchItemIndex: colIndex opacity: 1.0 z: 2 Image { id: okImage visible: isAcked width: parent.width / 2 height: parent.height / 2 anchors.centerIn: parent source: Activity.baseUrl + "apply.svg" } MouseArea { id: ackMouseArea anchors.fill: parent acceptedButtons: Qt.LeftButton enabled: status == Activity.STATUS_UNKNOWN visible: status == Activity.STATUS_UNKNOWN z: 3 hoverEnabled: ApplicationInfo.isMobile ? false : true onDoubleClicked: Activity.ackColor(index, colIndex); } } } } Item { id: guessRowSpacer2 width: 10 height: guessColumn.guessSize } Column { id: guessResultColumn width: guessColumn.resultColWidth height: guessColumn.guessSize spacing: 2 Item { id: guessResultColSpacer width: guessResultColumn.width height: (guessResultColumn.height - 2 * (guessColumn.resultSize)) } Row { id: guessResultCorrectRow width: guessResultColumn.width height: guessColumn.resultSize spacing: 2 Repeater { id: guessResultCorrectRepeater model: result.correct delegate: Rectangle { id: singleCorrectResult width: guessColumn.resultSize height: guessColumn.resultSize radius: width * 0.5 border.width: 1 border.color: "white" color: "black" } } } Row { id: guessResultMisplacedRow width: guessResultColumn.width height: guessColumn.resultSize spacing: 2 Repeater { id: guessResultMisplacedRepeater model: result.misplaced delegate: Rectangle { id: singleMisplacedResult width: guessColumn.resultSize height: guessColumn.resultSize radius: width * 0.5 border.width: 1 border.color: "black" color: "white" } } } } } } DialogHelp { id: dialogHelp onClose: home() } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias modeBox: modeBox property var availableModes: [ { "text": qsTr("Colors"), "value": "color" }, { "text": qsTr("Shapes"), "value": "symbol" } ] Flow { id: flow spacing: 5 width: dialogActivityConfig.width GCComboBox { id: modeBox model: availableModes background: dialogActivityConfig label: qsTr("Select your mode") } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["mode"]) { Activity.mode = dataToSave["mode"]; } } onSaveData: { var newMode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.modeBox.currentIndex].value; if (newMode !== Activity.mode) { chooserGrid.model = new Array(); Activity.mode = newMode; dataToSave = {"mode": Activity.mode}; Activity.initLevel(); } } function setDefaultValues() { for(var i = 0 ; i < dialogActivityConfig.configItem.availableModes.length ; i ++) { if(dialogActivityConfig.configItem.availableModes[i].value === Activity.mode) { dialogActivityConfig.configItem.modeBox.currentIndex = i; break; } } } } Bar { id: bar content: BarEnumContent { value: help | home | level | config } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true // Set default values dialogActivityConfig.setDefaultValues(); displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } Score { id: score anchors.bottom: undefined anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.topMargin: 10 * ApplicationInfo.ratio anchors.left: undefined anchors.top: parent.top anchors.right: parent.right } } } diff --git a/src/activities/tangram/Tangram.qml b/src/activities/tangram/Tangram.qml index cb408bf19..5db23154b 100644 --- a/src/activities/tangram/Tangram.qml +++ b/src/activities/tangram/Tangram.qml @@ -1,366 +1,366 @@ /* 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.6 import GCompris 1.0 import "../../core" import "tangram.js" as Activity import "dataset.js" as Dataset import "." ActivityBase { id: activity onStart: focus = true onStop: {} Keys.onPressed: Activity.processPressedKey(event) pageComponent: Item { id: background anchors.fill: parent - property bool horizontalLayout: background.width > background.height + 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 + "tangram/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: items.editionMode /* 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 selectedItem property var currentTans: Dataset.dataset[bar.level - 1] property int numberOfLevel: Dataset.dataset.length property bool editionMode: false } onStart: { Activity.start(items) } onStop: { Activity.stop() } Image { id: bgData source: items.currentTans.bg ? Activity.url + items.currentTans.bg : '' sourceSize.width: 1000 * background.playRatio sourceSize.height: 1000 * background.playRatio width: 1000 * background.playRatio height: width anchors.centerIn: parent } RotateMouseArea {} DropArea { id: dropableArea anchors.left: background.left anchors.bottom: background.bottom width: background.width height: background.height } Repeater { id: modelList model: items.currentTans.pieces Item { anchors.fill: background Image { id: tansModel x: background.playX + background.playWidth * modelData.x - width / 2 y: background.playY + background.playHeight * modelData.y - height / 2 source: Activity.url + "m-" + modelData.img sourceSize.width: modelData.width * background.playWidth sourceSize.height: modelData.height * background.playWidth z: index rotation: modelData.rotation mirror: modelData.flipping ? true : false visible: true } } } Repeater { id: userList model: items.currentTans.pieces Item { id: tansItem x: background.playX + background.playWidth * xRatio - tans.width / 2 y: background.playY + background.playHeight * yRatio - tans.height / 2 width: tans.width height: tans.height z: 100 + index property real xRatio: !items.editionMode ? modelData.initX : modelData.x property real yRatio: !items.editionMode ? modelData.initY : modelData.y property bool selected: false property int animDuration: 48 property bool flippable: modelData.flippable property bool rotable: modelData.moduloRotation != 0 property alias tans: tans rotation: !items.editionMode ? modelData.initRotation : modelData.rotation property alias mirror: tans.mirror function restoreZindex() { z = 100 + index } onSelectedChanged: { if(!selected) restoreZindex() } function positionToTans() { return [ (x + width / 2 - background.playX) / background.playWidth, (y + height / 2 - background.playY) / background.playHeight ] } // After a drag the [x, y] positions are addressed directly breaking our // binding. Call me to reset the binding. function restoreBindings() { x = Qt.binding(function() { return background.playX + background.playWidth * xRatio - width / 2}) y = Qt.binding(function() { return background.playY + background.playHeight * yRatio - height / 2 }) } Image { id: tans mirror: !items.editionMode ? modelData.initFlipping : modelData.flipping source: Activity.url + modelData.img sourceSize.width: modelData.width * background.playWidth sourceSize.height: modelData.height * background.playWidth } // Manage to return a base rotation as it was provided in the model function rotationToTans() { // moduloRotation == 0 to disable rotation, assume 360 in this case var mod = modelData.moduloRotation ? modelData.moduloRotation : 360 if(modelData.flipable || modelData.flipping || !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 { 'img': modelData.img, 'flipping': mirror, 'x': positionToTans()[0], 'y': positionToTans()[1], 'rotation': rotationToTans() } } function flipMe() { if(flippable) mirror = !mirror background.checkWin() } 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: { tansItem.z = 200 if(items.selectedItem && items.selectedItem != tansItem) items.selectedItem.selected = false items.selectedItem = tansItem tansItem.selected = true background.checkWin() } onDoubleClicked: { flipMe() } onReleased: { parent.Drag.drop() var posTans = positionToTans() var closest = Activity.getClosest(posTans) if(closest && !items.editionMode) { tansItem.xRatio = closest[0] tansItem.yRatio = closest[1] } else { tansItem.xRatio = posTans[0] tansItem.yRatio = posTans[1] } tansItem.restoreBindings() background.checkWin() } } Image { id: rotateButton source: "qrc:/gcompris/src/core/resource/bar_reload.svg" x: - width y: parent.height / 2 - height / 2 visible: tansItem.selected && tansItem.rotable sourceSize.width: 40 * ApplicationInfo.ratio z: tansItem.z + 1 RotateMouseArea {} } Image { id: flip source: "qrc:/gcompris/src/activities/tangram/resource/tangram/flip.svg" x: parent.width / 2 - width / 2 y: parent.height - height / 2 visible: tansItem.selected && tansItem.flippable sourceSize.width: 40 * ApplicationInfo.ratio z: tansItem.z + 1 MouseArea { anchors.fill: parent onClicked: tansItem.flipMe() } } Behavior on x { PropertyAnimation { duration: animDuration easing.type: Easing.InOutQuad } } Behavior on y { PropertyAnimation { duration: 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 } } // We use a timer here because we have to check only once the potential // animation are over Timer { id: checkWinTimer interval: 200 property bool alreadyStarted: false onTriggered: { if(Activity.check() && !alreadyStarted) { alreadyStarted = true if(!items.editionMode) bonus.good('flower') } } } function checkWin() { checkWinTimer.start() } GCText { anchors.top: parent.top anchors.left: parent.left text: items.currentTans.name visible: items.editionMode } DialogHelp { id: dialogHelp onClose: home() } File { id: file } Bar { id: bar content: BarEnumContent { value: help | home | level | (items.editionMode ? repeat : 0) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onRepeatClicked: file.write(Activity.toDataset(), "/tmp/" + items.currentTans.name) } Bonus { id: bonus interval: 1600 Component.onCompleted: win.connect(nextLevel) function nextLevel() { checkWinTimer.alreadyStarted = false Activity.nextLevel() } } } }