diff --git a/src/core/AnswerButton.qml b/src/core/AnswerButton.qml index 4ebf3c700..616ffb1a5 100644 --- a/src/core/AnswerButton.qml +++ b/src/core/AnswerButton.qml @@ -1,186 +1,264 @@ /* 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 to true when to avoid misclicks or when correct/wrong answer + * animation are running. + */ property bool blockClicks: false + /** + * 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 GCAudio audioEffects + /** + * Emitted when button is pressed as a good answer. + * + * Triggers correctAnswerAnimation. + */ signal correctlyPressed + + /** + * Emitted when button is pressed as a bad answer. + * + * Triggers wrongAnswerAnimation. + */ signal incorrectlyPressed + /** + * Emitted when answer button is clicked. + */ signal pressed onPressed: { if (!blockClicks) { 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 onPressed: button.pressed() } SequentialAnimation { id: correctAnswerAnimation ScriptAction { script: { if (typeof(feedback) === "object") feedback.playCorrectSound(); blockClicks = true; 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: { blockClicks = false; correctlyPressed(); } } } SequentialAnimation { id: wrongAnswerAnimation 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(); } } } } diff --git a/src/core/Domino.qml b/src/core/Domino.qml index bbda55fc7..fc914af53 100644 --- a/src/core/Domino.qml +++ b/src/core/Domino.qml @@ -1,118 +1,144 @@ /* GCompris - Domino.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 +/** + * A QML component to display a domino. + * + * Domino consists of Flipable sides(front and back) + * and uses DominoNumber to display numbers. + * It is divided into two regions containing DominoNumbers. + * + * @inherit QtQuick.Flipable + */ Flipable { id: flipable + /** + * type:int + * Integer displayed in first region. + */ property alias value1: number1.value + + /** + * type:int + * Integer displayed in second region. + */ property alias value2: number2.value + + /** + * type:int + * Highest integer to display. + */ property int valueMax: 9 // Domino style property color color: "white" property color borderColor: "black" property int borderWidth: 2 property int radius: width * 0.05 property color backColor: "white" property color pointColor: "black" + // Set to true when to display on both sides. property bool flipEnabled: false + property bool flipped: true + // Set to false to prevent user inputs. property bool isClickable: true property GCAudio audioEffects front: Rectangle { anchors.fill: parent smooth: true; color: flipable.color border.color: "black" border.width: flipable.borderWidth radius: flipable.radius DominoNumber { id: number1 width: parent.width / 2 height: parent.height color: flipable.pointColor borderColor: "black" borderWidth: 0 radius: parent.height * 0.25 valueMax: flipable.valueMax onValueChanged: if(flipEnabled) flipable.flipped = !flipable.flipped isClickable: flipable.isClickable audioEffects: flipable.audioEffects } // Separation Rectangle { x: parent.width / 2 anchors.verticalCenter: parent.verticalCenter width: 2 height: parent.height * 0.7 color: flipable.borderColor } DominoNumber { id: number2 x: parent.width / 2 width: parent.width / 2 height: parent.height color: flipable.pointColor borderColor: "black" borderWidth: 0 radius: parent.height * 0.25 valueMax: flipable.valueMax onValueChanged: if(flipEnabled) flipable.flipped = !flipable.flipped isClickable: flipable.isClickable audioEffects: flipable.audioEffects } } back: Rectangle { anchors.fill: parent smooth: true; color: flipable.backColor border.width: flipable.borderWidth radius: flipable.radius } transform: Rotation { id: rotation origin.x: flipable.width/2 origin.y: flipable.height/2 axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis angle: 0 // the default angle } states: State { name: "back" PropertyChanges { target: rotation; angle: 180 } when: flipable.flipped onCompleted: flipable.flipped = false } transitions: Transition { NumberAnimation { target: rotation; property: "angle"; duration: 250 } } } diff --git a/src/core/DominoNumber.qml b/src/core/DominoNumber.qml index d2d7cb59a..39d2ebe5d 100644 --- a/src/core/DominoNumber.qml +++ b/src/core/DominoNumber.qml @@ -1,153 +1,205 @@ /* GCompris - DominoNumber.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 +/** + * A QML component to display integers(0-9) on Domino. + * Numbers are displayed in the form of number of circles. + * + * @inherit QtQuick.Item + */ Item { id: item + + /** + * type:int + * Integer to display on the domino + */ property int value + + /** + * type:int + * Highest number visible on domino. + */ property int valueMax + + /** + * type:color + * color of the dots to display an integer. + */ property color color + + /** + * type:color + * Border color of the dots to display an integer. + */ property color borderColor + + /** + * type:int + * Border width of the dots to display an integer. + */ property int borderWidth + + /** + * type:int + * Radius of the dots to display an integer. + */ property int radius + + /** + * type:boolean + * Set false to disable mouse/touch inputs on domino. + */ property bool isClickable: true // Default value + + /** + * type:GCAudio + * To play sound and audio effects. + */ property GCAudio audioEffects function isVisible(index) { var value = item.value var visible = false switch(index) { case 0: if(value >= 2) visible = true break case 1: if(value >= 6) visible = true break case 2: if(value >= 4) visible = true break case 3: if(value >= 8) visible = true break case 4: if(value == 1 || value == 3 || value == 5 || value == 7 || value == 9) visible = true break case 5: if(value >= 8) visible = true break case 6: if(value >= 4) visible = true break case 7: if(value >= 6) visible = true break case 8: if(value >= 2) visible = true break } return visible } Grid { columns: 3 spacing: 3 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter horizontalItemAlignment: Grid.AlignHCenter verticalItemAlignment: Grid.AlignVCenter Repeater { model: 9 Rectangle { width: radius height: radius border.width: item.borderWidth color: item.color border.color: item.borderColor radius: item.radius opacity: isVisible(index) Behavior on opacity { PropertyAnimation { duration: 200 } } } } } + // Increase the displayed integer value by one. function up() { audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') if(item.value == item.valueMax) item.value = 0 else item.value++ } + // Decrease the displayed integer by one. function down() { audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') if(item.value == 0) item.value = item.valueMax else item.value-- } MouseArea { enabled: !ApplicationInfo.isMobile && item.isClickable anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { if (mouse.button == Qt.LeftButton) up() else down() } } + /** + * type:boolean + * To check on touch devices to increase or decrease the integer value. + */ property bool goUp Timer { id: timer interval: 500 repeat: true onTriggered: goUp ? up() : down() } MultiPointTouchArea { enabled: ApplicationInfo.isMobile && item.isClickable anchors.fill: parent maximumTouchPoints: 1 onPressed: { goUp = true up() timer.start() } onTouchUpdated: { if(touchPoints.length) { var touch = touchPoints[0] if(touch.y < parent.y + parent.height) goUp = true else goUp = false } } onReleased: timer.stop() } } diff --git a/src/core/NumPad.qml b/src/core/NumPad.qml index 73fa27fcf..3b3dcfe57 100644 --- a/src/core/NumPad.qml +++ b/src/core/NumPad.qml @@ -1,268 +1,337 @@ /* GCompris - NumPad.qml * * Copyright (C) 2014 Aruna Sankaranarayanan * * Authors: * Aruna Sankaranarayanan * * 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 +/** +* A QML component providing an on screen numpad. +* +* Numpad displays integers from 0 to 9 that can be used +* in applications that need numerical inputs from the user. +* By default it shows integer 0 - 4 in a column on left and +* integers 5 - 9 on right in a column. +* It also contains the backspace button to remove the last input value. +* It is also only displayed when the "virtual keyboard" is enabled in the options. +* +* @inherit QtQuick.Item +*/ Item { id: containerPanel anchors.fill: parent + /** + * type:list + * + * Default keys-rectangle color used unless the user provides another. + */ property var colours: ["#ea7025", "#67c111", "#00bde3", "#bde300","#e3004c"] + + /** + * type:list + * + * Default sequence of numbers displayed unless the user provides another. + */ property var numbers: [0, 1, 2, 3, 4] + + /** + * type:string + * + * String containing the numbers selected by user. + */ property string answer: "" + + /** + * type:bool + * + * Set to true when good answer is submitted and to + * avoid the inputs until required. + */ property bool answerFlag: false + + /** + * type:var + * + * Column containing containing first half integers i.e. + * 0 - 4, displayed at the left edge of the activity window. + */ property var leftPanelComponent: leftPanel + + /** + * type:var + * + * Column containing containing second half integers i.e. + * 5 - 9, displayed at the right edge of the activity window. + */ property var rightPanelComponent: rightPanel + + /** + * type:var + * + * Button for displaying backSpace key. + * Removes last input from answer on clicked or pressed. + */ property var backspaceButtonComponent: backspaceButton + + /** + * type:int + * + * Stores the maximum length of the correct answer. + */ property int maxDigit: 2 + + /** + * type:int + * + * Stores the width of each key container. + */ property int columnWidth: 80 * ApplicationInfo.ratio signal answer visible: ApplicationSettings.isVirtualKeyboard Column { id: leftPanel width: columnWidth height: parent.height - 90 * ApplicationInfo.ratio opacity: 0.8 Repeater { model: 5 Rectangle{ width: parent.width height: parent.height/5 color: colours[index] border.color: Qt.darker(color) border.width: 2 GCText { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: numbers[index] fontSize: 28 font.bold: true } MouseArea { // Create a bigger area than the top rectangle to suit fingers anchors { left: parent.left top: parent.top bottom: parent.bottom } width: parent.width * 2 enabled: ApplicationSettings.isVirtualKeyboard && containerPanel.opacity > 0 onClicked: { if(answer.length < maxDigit) answer += numbers[index] } onPressed: { leftPanel.children[index].color = Qt.lighter(colours[index]) leftPanel.children[index].border.width = 5 } onReleased: { leftPanel.children[index].color = colours[index] leftPanel.children[index].border.width = 2 } } } } } Column { id: rightPanel width: columnWidth height: parent.height - 90 * ApplicationInfo.ratio x: parent.width - columnWidth opacity: 0.8 Repeater { model: 5 Rectangle { width: parent.width height: parent.height/5 color: colours[index] border.color: Qt.darker(color) border.width:2 GCText { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: numbers[index] + 5 fontSize: 28 font.bold: true } MouseArea { // Create a bigger area than the top rectangle to suit fingers anchors { right: parent.right top: parent.top bottom: parent.bottom } width: parent.width * 2 enabled: ApplicationSettings.isVirtualKeyboard && containerPanel.opacity > 0 onClicked: { if(answer.length < maxDigit) answer += numbers[index] + 5 } onPressed: { rightPanel.children[index].color = Qt.lighter(colours[index]) rightPanel.children[index].border.width = 5 } onReleased: { rightPanel.children[index].color = colours[index] rightPanel.children[index].border.width = 2 } } } } Rectangle { id: backspaceButton width: parent.width height: containerPanel.height - rightPanel.height color: "white" border.color: "black" border.width: 2 GCText { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: "←" fontSize: 28 font.bold: true } MouseArea { anchors.fill: parent enabled: ApplicationSettings.isVirtualKeyboard && containerPanel.opacity > 0 onClicked: { answer = answer.substring(0,answer.length - 1) } onPressed: { backspaceButton.color = Qt.lighter("white") backspaceButton.border.width = 5 } onReleased: { backspaceButton.color = "white" backspaceButton.border.width = 2 } } } } function resetText() { answer = "" } function updateAnswer(key, isKeyPressed) { var keyValue; switch(key) { case Qt.Key_0 : keyValue = 0; break; case Qt.Key_1: keyValue = 1; break; case Qt.Key_2: keyValue = 2; break; case Qt.Key_3: keyValue = 3; break; case Qt.Key_4: keyValue = 4; break; case Qt.Key_5: keyValue = 5; break; case Qt.Key_6: keyValue = 6; break; case Qt.Key_7: keyValue = 7; break; case Qt.Key_8: keyValue = 8; break; case Qt.Key_9: keyValue = 9; break; case Qt.Key_Backspace: keyValue = 10; } if(isKeyPressed && !answerFlag) { if(keyValue < 5 && answer.length < maxDigit) { answer += keyValue; leftPanel.children[keyValue].color = Qt.lighter(colours[keyValue]) leftPanel.children[keyValue].border.width = 5 } else if(keyValue < 10 && answer.length < maxDigit) { answer += keyValue; rightPanel.children[keyValue - 5].color = Qt.lighter(colours[keyValue - 5]) rightPanel.children[keyValue - 5].border.width = 5 } else if(keyValue === 10) { answer = answer.substring(0,answer.length - 1); backspaceButton.color = Qt.lighter("white") backspaceButton.border.width = 5 } } else { if(keyValue < 5) { leftPanel.children[keyValue].color = colours[keyValue] leftPanel.children[keyValue].border.width = 2 } else if(keyValue < 10) { rightPanel.children[keyValue - 5].color = colours[keyValue - 5] rightPanel.children[keyValue - 5].border.width = 2 } else if(keyValue === 10) { backspaceButton.color = "white" backspaceButton.border.width = 2 } } } }