diff --git a/src/activities/click_on_letter/Carriage.qml b/src/activities/click_on_letter/Carriage.qml index e9f679b69..9050d3249 100644 --- a/src/activities/click_on_letter/Carriage.qml +++ b/src/activities/click_on_letter/Carriage.qml @@ -1,178 +1,192 @@ /* GCompris - Carriage.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Pascal Georges (GTK+ version) * Bruno Coudoin (GTK+ Mostly full rewrite) * 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 GCompris 1.0 import QtGraphicalEffects 1.0 import "../../core" import "click_on_letter.js" as Activity Item { id: carriageItem property int nbCarriage property bool isCarriage: index <= nbCarriage property bool clickEnabled + property bool isSelected + property alias successAnimation: successAnimation + property alias failureAnimation: failureAnimation + property alias particle: particle Image { id: carriageImage sourceSize.width: carriageItem.width fillMode: Image.PreserveAspectFit source: isCarriage ? Activity.url + "carriage.svg": Activity.url + "cloud.svg" z: (state == 'scaled') ? 1 : -1 Rectangle { id: carriageBg visible: isCarriage width: parent.width - 8 height: parent.height / 1.8 anchors.bottom: parent.top anchors.bottomMargin: - parent.height / 1.5 radius: height / 10 color: "#f0d578" border.color: "#b98a1c" border.width: 3 } + Rectangle { + id: selector + z: 9 + visible: isSelected + anchors.fill: parent + radius: 5 + color: "#800000ff" + } + GCText { id: text anchors.horizontalCenter: isCarriage ? carriageBg.horizontalCenter : parent.horizontalCenter anchors.verticalCenter: isCarriage ? carriageBg.verticalCenter : parent.verticalCenter z: 11 text: letter font.pointSize: NaN // need to clear font.pointSize explicitly font.pixelSize: parent.width * 0.65 font.bold: true style: Text.Outline styleColor: "#2a2a2a" color: "white" } DropShadow { anchors.fill: text cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3 samples: 16 color: "#422a2a2a" source: text } Image { id: softFailure z: 12 source: "qrc:/gcompris/src/activities/tic_tac_toe/resource/cross.svg" width: parent.width height: width anchors.centerIn: text opacity: 0 visible: ApplicationInfo.useOpenGL ? false : true } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: ApplicationInfo.isMobile ? false : true onClicked: { if(carriageItem.clickEnabled) { if (Activity.checkAnswer(index)) { successAnimation.restart(); particle.burst(30) } else { failureAnimation.restart() } } } } ParticleSystemStarLoader { + z: 10 id: particle clip: false } states: State { name: "scaled"; when: mouseArea.containsMouse PropertyChanges { target: carriageItem scale: /*carriageImage.scale * */ 1.2 z: 2 } } transitions: Transition { NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } } SequentialAnimation { id: successAnimation NumberAnimation { target: carriageImage easing.type: Easing.InOutQuad property: "rotation" to: 20; duration: 100 } NumberAnimation { target: carriageImage easing.type: Easing.InOutQuad property: "rotation"; to: -20 duration: 100 } NumberAnimation { target: carriageImage easing.type: Easing.InOutQuad property: "rotation" to: 0; duration: 50 } } SequentialAnimation { id: failureAnimation NumberAnimation { target: ApplicationInfo.useOpenGL ? color : softFailure property: "opacity" to: 1; duration: 400 } NumberAnimation { target: ApplicationInfo.useOpenGL ? color : softFailure property: "opacity" to: 0; duration: 200 } } } Colorize { id: color z: 5 anchors.fill: carriageImage source: carriageImage hue: 0.0 saturation: 1 opacity: 0 } } diff --git a/src/activities/click_on_letter/ClickOnLetter.qml b/src/activities/click_on_letter/ClickOnLetter.qml index 327f45b6d..e045c5bbc 100644 --- a/src/activities/click_on_letter/ClickOnLetter.qml +++ b/src/activities/click_on_letter/ClickOnLetter.qml @@ -1,295 +1,344 @@ /* GCompris - ClickOnLetter.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Pascal Georges (GTK+ version) * Bruno Coudoin (GTK+ Mostly full rewrite) * 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 "click_on_letter.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity focus: true /* mode of the activity, either "lowercase" (click_on_letter) * or "uppercase" (click_on_letter_up): */ property string mode: "lowercase" onStart: focus = true pageComponent: Image { id: background source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop focus: true // system locale by default property string locale: "system" signal start signal stop signal voiceError signal voiceDone Component.onCompleted: { dialogActivityConfig.initialize() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property alias bar: bar property alias trainModel: trainModel property GCAudio audioVoices: activity.audioVoices property alias parser: parser property alias questionItem: questionItem property alias repeatItem: repeatItem property alias score: score property alias bonus: bonus property alias locale: background.locale property bool goToNextSubLevel: false property bool goToNextLevel: false + property bool keyNavigationMode: false } onVoiceDone: { if(items.goToNextSubLevel) { items.goToNextSubLevel = false; Activity.nextSubLevel(); } if(items.goToNextLevel) { items.goToNextLevel = false; items.bonus.good("flower"); } } onVoiceError: { - questionItem.visible = true - repeatItem.visible = false + questionItem.visible = true; + repeatItem.visible = false; } onStart: { - activity.audioVoices.done.connect(voiceDone) - activity.audioVoices.error.connect(voiceError) + activity.audioVoices.done.connect(voiceDone); + activity.audioVoices.error.connect(voiceError); Activity.start(items, mode); + eventHandler.forceActiveFocus(); } onStop: Activity.stop() + Item { + id: eventHandler + focus: true + Keys.enabled: !bonus.isPlaying + Keys.onPressed: { + if(event.key === Qt.Key_Tab) { + activity.audioVoices.clearQueue(); + activity.audioVoices.stop(); + Activity.playLetter(Activity.currentLetter); + } else { + background.handleKeys(event); + } + } + } + DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onClose: { - home() + home(); + eventHandler.forceActiveFocus(); } onSaveData: { - levelFolder = dialogActivityConfig.chosenLevels - currentActivity.currentLevels = dialogActivityConfig.chosenLevels - ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) + levelFolder = dialogActivityConfig.chosenLevels; + currentActivity.currentLevels = dialogActivityConfig.chosenLevels; + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels); } onLoadData: { if(activityData && activityData["activityLocale"]) { background.locale = activityData["activityLocale"]; } else { - background.locale = Core.resolveLocale(background.locale) + background.locale = Core.resolveLocale(background.locale); } } onStartActivity: { - background.stop() - background.start() + background.stop(); + background.start(); + eventHandler.forceActiveFocus(); } } DialogHelp { id: dialogHelpLeftRight onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: home() onActivityConfigClicked: { displayDialog(dialogActivityConfig) } } Score { id: score anchors.top: parent.top anchors.topMargin: 10 * ApplicationInfo.ratio anchors.left: parent.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottom: undefined anchors.right: undefined } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } BarButton { id: repeatItem source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; sourceSize.width: 80 * ApplicationInfo.ratio anchors { top: parent.top right: parent.right margins: 10 } onClicked: { activity.audioVoices.clearQueue(); activity.audioVoices.stop(); Activity.playLetter(Activity.currentLetter); } } Image { id: railway source: Activity.url + "railway.svg" fillMode: Image.PreserveAspectCrop anchors.bottom: bar.top anchors.left: parent.left anchors.right: parent.right height: 15 * ApplicationInfo.ratio sourceSize.width: Math.max(parent.width, parent.height) anchors.bottomMargin: 13 * ApplicationInfo.ratio } Item { id: questionItem anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.topMargin: parent.height * 0.25 z: 10 width: questionText.width * 2 height: questionText.height * 1.3 visible: false property alias text: questionText.text Rectangle { id: questionRect anchors.fill: parent border.color: "#FFFFFFFF" border.width: 2 color: "#000065" opacity: 0.31 radius: 10 } GCText { id: questionText anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter opacity: 1.0 z:11 text: "" fontSize: 44 font.bold: true style: Text.Outline styleColor: "#2a2a2a" color: "white" } DropShadow { anchors.fill: questionText cached: false horizontalOffset: 1 verticalOffset: 1 radius: 3 samples: 16 color: "#422a2a2a" source: questionText } } ListModel { id: trainModel } property int itemWidth: Math.min(parent.width / 7.5, parent.height / 5) property int itemHeight: itemWidth * 1.11 Image { id: engine source: Activity.url + "engine.svg" anchors.bottom: railway.bottom anchors.left: railway.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottomMargin: 5 * ApplicationInfo.ratio sourceSize.width: itemWidth fillMode: Image.PreserveAspectFit } Image { id: smoke source: Activity.url + "smoke.svg" anchors.bottom: engine.top anchors.left: railway.left anchors.leftMargin: 10 * ApplicationInfo.ratio anchors.bottomMargin: 5 * ApplicationInfo.ratio sourceSize.width: itemWidth fillMode: Image.PreserveAspectFit } GridView { id: train anchors.bottom: railway.bottom anchors.left: engine.right anchors.right: parent.right anchors.top: parent.top anchors.bottomMargin: 5 * ApplicationInfo.ratio cellWidth: itemWidth cellHeight: itemHeight clip: false interactive: false verticalLayoutDirection: GridView.BottomToTop layoutDirection: Qt.LeftToRight + keyNavigationWraps: true + currentIndex: -1 model: trainModel delegate: Carriage { width: background.itemWidth nbCarriage: (parent.width - engine.width) / background.itemWidth clickEnabled: activity.audioVoices.playbackState == 1 ? false : true + isSelected: train.currentIndex === index + } + } + + function handleKeys(event) { + if(!items.keyNavigationMode) { + activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); + items.keyNavigationMode = true; + train.currentIndex = 0; + } else if(event.key === Qt.Key_Right) { + activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); + train.moveCurrentIndexRight(); + } else if(event.key === Qt.Key_Left) { + activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); + train.moveCurrentIndexLeft(); + } else if(event.key === Qt.Key_Up) { + activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); + train.moveCurrentIndexUp(); + } else if(event.key === Qt.Key_Down) { + activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav'); + train.moveCurrentIndexDown(); + } else if(event.key === Qt.Key_Space && activity.audioVoices.playbackState != 1) { + if(Activity.checkAnswer(train.currentIndex)) { + train.currentItem.successAnimation.restart(); + train.currentItem.particle.burst(30); + } else { + train.currentItem.failureAnimation.restart(); + } } } JsonParser { id: parser onError: console.error("Click_on_letter: Error parsing JSON: " + msg); } } } diff --git a/src/activities/click_on_letter_up/ClickOnLetterUp.qml b/src/activities/click_on_letter_up/ClickOnLetterUp.qml index e7b676bb7..59949c5d8 100644 --- a/src/activities/click_on_letter_up/ClickOnLetterUp.qml +++ b/src/activities/click_on_letter_up/ClickOnLetterUp.qml @@ -1,34 +1,33 @@ /* GCompris - ClickOnLetterUp.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 GCompris 1.0 import "../../core" import "../click_on_letter" ClickOnLetter { id: activity - mode: "uppercase" }