diff --git a/src/activities/leftright/Leftright.qml b/src/activities/leftright/Leftright.qml index a2ad4d6b8..01dec2c99 100644 --- a/src/activities/leftright/Leftright.qml +++ b/src/activities/leftright/Leftright.qml @@ -1,200 +1,200 @@ /* GCompris - Leftright.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Pascal Georges (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 "leftright.js" as Activity ActivityBase { id: activity onStart: focus = true; Keys.onLeftPressed: Activity.leftClickPressed() Keys.onRightPressed: Activity.rightClickPressed() pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/leftright/resource/back.svg" sourceSize.width: Math.max(parent.width, parent.height) focus: true signal start signal stop fillMode: Image.PreserveAspectCrop QtObject { id: items property alias bar: bar property alias bonus: bonus property GCSfx audioEffects: activity.audioEffects property alias imageAnimOff: imageAnimOff property alias leftButton: leftButton property alias rightButton: rightButton property alias score: score property bool buttonsBlocked: false } Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } onStart: { Activity.start(items) } onStop: { Activity.stop() } Item { id: topBorder height: background.height * 0.08 } Image { id: blackBoard anchors.horizontalCenter: parent.horizontalCenter anchors.top: topBorder.bottom fillMode: Image.PreserveAspectFit sourceSize.width: Math.min(background.width, (background.height - leftButton.height - bar.height) * 1.3) source: "qrc:/gcompris/src/activities/leftright/resource/blackboard.svg" Image { id: handImage anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter fillMode: Image.PreserveAspectFit scale: blackBoard.height / 300 * 0.8 opacity: 0 } Image { id: lightImage source: "qrc:/gcompris/src/activities/leftright/resource/light.svg" sourceSize.width: parent.width sourceSize.height: parent.height anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 40 opacity: 0 } ParallelAnimation { id: imageAnimOff onRunningChanged: { if (!imageAnimOff.running) { handImage.source = Activity.getCurrentHandImage() handImage.rotation = Activity.getCurrentHandRotation() imageAnimOn.start() } } NumberAnimation { target: handImage property: "opacity" from: 1; to: 0 duration: 300 easing.type: Easing.InOutQuad } NumberAnimation { target: lightImage property: "opacity" from: 0.2; to: 0 duration: 300 easing.type: Easing.InOutQuad } } ParallelAnimation { id: imageAnimOn onStopped: bonus.isPlaying ? items.buttonsBlocked = true : items.buttonsBlocked = false NumberAnimation { target: handImage property: "opacity" from: 0; to: 1.0 duration: 300 easing.type: Easing.InOutQuad } NumberAnimation { target: lightImage property: "opacity" from: 0; to: 0.2 duration: 300 easing.type: Easing.InOutQuad } } AnswerButton { id: leftButton width: blackBoard.width * 0.45 height: background.height * 0.15 anchors.left: blackBoard.left anchors.top: blackBoard.bottom anchors.margins: 10 textLabel: qsTr("Left hand") audioEffects: activity.audioEffects onPressed: items.buttonsBlocked = true onCorrectlyPressed: Activity.leftClick() blockAllButtonClicks: items.buttonsBlocked - onReadyWrong: items.buttonsBlocked = false + onIncorrectlyPressed: items.buttonsBlocked = false } AnswerButton { id: rightButton width: blackBoard.width * 0.45 height: background.height * 0.15 anchors.right: blackBoard.right anchors.top: blackBoard.bottom anchors.margins: 10 audioEffects: activity.audioEffects textLabel: qsTr("Right hand") onPressed: items.buttonsBlocked = true onCorrectlyPressed: Activity.rightClick() blockAllButtonClicks: items.buttonsBlocked - onReadyWrong: items.buttonsBlocked = false + onIncorrectlyPressed: items.buttonsBlocked = false } } 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 onStart: items.buttonsBlocked = true onStop: items.buttonsBlocked = false } Score { id: score anchors.top: background.top anchors.bottom: undefined } } } diff --git a/src/activities/missing-letter/MissingLetter.qml b/src/activities/missing-letter/MissingLetter.qml index cb94a9e21..ae70ee6a7 100644 --- a/src/activities/missing-letter/MissingLetter.qml +++ b/src/activities/missing-letter/MissingLetter.qml @@ -1,375 +1,375 @@ /* GCompris - missing-letter.qml * * Copyright (C) 2014 "Amit Tomar" * * Authors: * "Pascal Georges" (GTK+ version) * "Amit Tomar" (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 "missing-letter.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} // When going on configuration, it steals the focus and re set it to the activity. // We need to set it back to the textinput item in order to have key events. onFocusChanged: { if(focus) { Activity.focusTextInput() } } pageComponent: Image { id: background source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop // system locale by default property string locale: "system" property bool englishFallback: false property bool downloadWordsNeeded: false 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 score: score property alias questionImage: questionImage property alias questionText: questionText property alias answers: answers property GCAudio audioVoices: activity.audioVoices property alias englishFallbackDialog: englishFallbackDialog property alias parser: parser property alias locale: background.locale property string answer property alias textinput: textinput property bool isGoodAnswer: false property bool buttonsBlocked: false } onStart: { Activity.init(items) Activity.focusTextInput() Activity.start() } onStop: { Activity.stop() } TextInput { // Helper element to capture composed key events like french รด which // are not available via Keys.onPressed() on linux. Must be // disabled on mobile! id: textinput anchors.centerIn: background enabled: !ApplicationInfo.isMobile focus: true visible: false onTextChanged: { if (text != '') { var typedText = text var answerText = Activity.getCurrentQuestion().answer if(ApplicationSettings.fontCapitalization === Font.AllUppercase) typedText = text.toLocaleUpperCase() else if(ApplicationSettings.fontCapitalization === Font.AllLowercase) typedText = text.toLocaleLowerCase() if(!items.isGoodAnswer && (typedText === answerText)) { questionAnim.start() Activity.showAnswer() } text = ''; } } } // Buttons with possible answers shown on the left of screen Column { id: buttonHolder spacing: 10 * ApplicationInfo.ratio x: holder.x - width - 10 * ApplicationInfo.ratio y: holder.y add: Transition { NumberAnimation { properties: "y"; from: holder.y; duration: 500 } } Repeater { id: answers AnswerButton { width: 120 * ApplicationInfo.ratio height: (holder.height - buttonHolder.spacing * answers.model.length) / answers.model.length textLabel: modelData blockAllButtonClicks: items.buttonsBlocked isCorrectAnswer: modelData === items.answer onCorrectlyPressed: questionAnim.start() onPressed: { items.buttonsBlocked = true if(!items.isGoodAnswer) { modelData == items.answer ? Activity.showAnswer() : '' } } - onReadyWrong: items.buttonsBlocked = false + onIncorrectlyPressed: items.buttonsBlocked = false } } } // Picture holder for different images being shown Rectangle { id: holder width: Math.max(questionImage.width * 1.1, questionImage.height * 1.1) height: questionTextBg.y + questionTextBg.height x: (background.width - width - 130 * ApplicationInfo.ratio) / 2 + 130 * ApplicationInfo.ratio y: 20 color: "black" radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#80FFFFFF" } GradientStop { position: 0.9; color: "#80EEEEEE" } GradientStop { position: 1.0; color: "#80AAAAAA" } } Item { id: spacer height: 20 } Image { id: questionImage anchors.horizontalCenter: holder.horizontalCenter anchors.top: spacer.bottom width: Math.min((background.width - 120 * ApplicationInfo.ratio) * 0.7, (background.height - 100 * ApplicationInfo.ratio) * 0.7) height: width } Rectangle { id: questionTextBg width: holder.width height: questionText.height * 1.1 anchors.horizontalCenter: holder.horizontalCenter anchors.top: questionImage.bottom 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" } } GCText { id: questionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter style: Text.Outline styleColor: "black" color: "white" fontSize: largeSize wrapMode: Text.WordWrap width: holder.width SequentialAnimation { id: questionAnim NumberAnimation { target: questionText property: 'scale' to: 1.05 duration: 500 easing.type: Easing.OutQuad } NumberAnimation { target: questionText property: 'scale' to: 0.95 duration: 1000 easing.type: Easing.OutQuad } NumberAnimation { target: questionText property: 'scale' to: 1.0 duration: 500 easing.type: Easing.OutQuad } ScriptAction { script: Activity.nextSubLevel() } } } } } Score { id: score anchors.bottom: undefined anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.top: parent.top } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column spacing: 10 width: parent.width Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig label: qsTr("Select your locale") } } } } } onClose: home() onLoadData: { if(dataToSave && dataToSave["locale"]) { background.locale = dataToSave["locale"]; } } onSaveData: { var oldLocale = background.locale; var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; // Remove .UTF-8 if(newLocale.indexOf('.') != -1) { newLocale = newLocale.substring(0, newLocale.indexOf('.')) } dataToSave = {"locale": newLocale } background.locale = newLocale; // Restart the activity with new information if(oldLocale !== newLocale) { background.stop(); background.start(); } } function setDefaultValues() { var localeUtf8 = background.locale; if(background.locale != "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { dialogActivityConfig.loader.item.localeBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | repeat | config } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onRepeatClicked: Activity.playCurrentWord() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus onStart: items.buttonsBlocked = true onStop: items.buttonsBlocked = false Component.onCompleted: win.connect(Activity.nextLevel) } JsonParser { id: parser onError: console.error("missing letter: Error parsing json: " + msg); } Loader { id: englishFallbackDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("We are sorry, we don't have yet a translation for your language.") + " " + qsTr("GCompris is developed by the KDE community, you can translate GCompris by joining a translation team on %2").arg("http://l10n.kde.org/") + "

" + qsTr("We switched to English for this activity but you can select another language in the configuration dialog.") onClose: background.englishFallback = false } anchors.fill: parent focus: true active: background.englishFallback onStatusChanged: if (status == Loader.Ready) item.start() } } } diff --git a/src/core/AnswerButton.qml b/src/core/AnswerButton.qml index 85eb04489..a094e9487 100644 --- a/src/core/AnswerButton.qml +++ b/src/core/AnswerButton.qml @@ -1,279 +1,259 @@ /* Copyed in GCompris from Touch'n'learn Touch'n'learn - Fun and easy mobile lessons for kids Copyright (C) 2010, 2011 by Alessandro Portale http://touchandlearn.sourceforge.net This file is part of Touch'n'learn Touch'n'learn 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. Touch'n'learn 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 Touch'n'learn; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ import QtQuick 2.6 import GCompris 1.0 /** * A QML component to display an answer button. * * AnswerButton consists of a text (@ref textLabel) * and animations on pressed. * Mostly used to present more than one option to select from * consisting of both good and bad answers. * * @inherit QtQuick.Item */ Item { id: button /** * type:string * Text to display on the button. * * @sa label.text */ property string textLabel /** * type:boolean * * Set to true when this element contains good answer. */ property bool isCorrectAnswer: false /** * type:color * * Color of the container in normal state. */ property color normalStateColor: "#fff" /** * type:color * * Color of the container on good answer selection. */ property color correctStateColor: "#09f" /** * type:color * * Color of the container on bad answer selection. */ property color wrongStateColor: "#f66" /** * type: bool * * Set the external conditions to this variable during which the clicks on button are to be blocked. */ property bool blockAllButtonClicks: false /** * type:bool * * This variable holds the overall events during which the clicks on button will be blocked. */ readonly property bool blockClicks: correctAnswerAnimation.running || wrongAnswerAnimation.running || blockAllButtonClicks /** * type:int * * Amplitude of the shake animation on wrong answer selection. */ property int wrongAnswerShakeAmplitudeCalc: width * 0.2 /** * type:int * * Minimum amplitude of the shake animation on wrong answer selection. */ property int wrongAnswerShakeAmplitudeMin: 45 /** * type:int * * Amplitude of the shake animation on wrong answer. * Selects min. from wrongAnswerShakeAmplitudeMin && wrongAnswerShakeAmplitudeCalc. */ property int wrongAnswerShakeAmplitude: wrongAnswerShakeAmplitudeCalc < wrongAnswerShakeAmplitudeMin ? wrongAnswerShakeAmplitudeMin : wrongAnswerShakeAmplitudeCalc // If you want the sound effects just pass the audioEffects property GCSfx audioEffects /** - * Emitted when button is pressed as a good answer. + * Emitted after button is pressed as a good answer. * - * Triggers correctAnswerAnimation. + * Triggered at the end of correctAnswerAnimation. */ signal correctlyPressed /** - * Emitted when button is pressed as a bad answer. + * Emitted after button is pressed as a bad answer. * - * Triggers wrongAnswerAnimation. + * Triggered at the end of wrongAnswerAnimation. */ signal incorrectlyPressed - - /** - * Emitted after animation of correct answer - */ - signal readyCorrect - - /** - * Emitted after animation of incorrect answer - */ - signal readyWrong /** * Emitted when answer button is clicked. */ signal pressed onPressed: { if (isCorrectAnswer) { if(audioEffects) audioEffects.play("qrc:/gcompris/src/core/resource/sounds/win.wav") correctAnswerAnimation.start(); } else { if(audioEffects) audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav") wrongAnswerAnimation.start(); } } Rectangle { id: rect anchors.fill: parent color: normalStateColor opacity: 0.5 } ParticleSystemStarLoader { id: particles } Image { source: "qrc:/gcompris/src/core/resource/button.svg" sourceSize { height: parent.height; width: parent.width } width: sourceSize.width height: sourceSize.height smooth: false } GCText { id: label anchors.verticalCenter: parent.verticalCenter // We need to manually horizonally center the text, because in wrongAnswerAnimation, // the x of the text is changed, which would not work if we use an anchor layout. property int horizontallyCenteredX: (button.width - contentWidth) >> 1; width: button.width x: horizontallyCenteredX; fontSizeMode: Text.Fit font.bold: true text: textLabel color: "#373737" } MouseArea { id: mouseArea anchors.fill: parent enabled: !blockClicks onPressed: button.pressed() } SequentialAnimation { id: correctAnswerAnimation - onStopped: button.readyCorrect() + onStopped: correctlyPressed() ScriptAction { script: { if (typeof(feedback) === "object") feedback.playCorrectSound(); if (typeof(particles) === "object") particles.burst(40); } } PropertyAction { target: rect property: "color" value: correctStateColor } PropertyAnimation { target: rect property: "color" to: normalStateColor duration: 700 } PauseAnimation { duration: 300 // Wait for particles to finish } - ScriptAction { - script: { - correctlyPressed(); - } - } } SequentialAnimation { id: wrongAnswerAnimation - onStopped: button.readyWrong() + onStopped: incorrectlyPressed() ParallelAnimation { SequentialAnimation { PropertyAction { target: rect property: "color" value: wrongStateColor } ScriptAction { script: { if (typeof(feedback) === "object") feedback.playIncorrectSound(); } } PropertyAnimation { target: rect property: "color" to: normalStateColor duration: 600 } } SequentialAnimation { PropertyAnimation { target: label property: "x" to: label.horizontallyCenteredX - wrongAnswerShakeAmplitude easing.type: Easing.InCubic duration: 120 } PropertyAnimation { target: label property: "x" to: label.horizontallyCenteredX + wrongAnswerShakeAmplitude easing.type: Easing.InOutCubic duration: 220 } PropertyAnimation { target: label property: "x" to: label.horizontallyCenteredX easing { type: Easing.OutBack; overshoot: 3 } duration: 180 } } } PropertyAnimation { target: rect property: "color" to: normalStateColor duration: 450 } - ScriptAction { - script: { - incorrectlyPressed(); - } - } } }