diff --git a/src/activities/menu/Menu.qml b/src/activities/menu/Menu.qml index 09ae6e51a..365c05a3e 100644 --- a/src/activities/menu/Menu.qml +++ b/src/activities/menu/Menu.qml @@ -1,782 +1,781 @@ /* 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); } Connections { // At the launch of the application, box2d check is performed after we // first initialize the menu. This connection is to refresh // automatically the menu at start. target: ApplicationInfo onIsBox2DInstalledChanged: { ActivityInfoTree.filterByTag(activity.currentTag) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } // @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 property string clickMode: "play" 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 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 activityCellWidth: horizontal ? background.width / Math.floor(background.width / iconWidth) : (background.width - section.width) / Math.floor((background.width - section.width) / iconWidth) property int activityCellHeight: iconWidth * 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(searchBar.visible) 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 enabled: clickMode === "play" || currentLevel != "" Rectangle { id: activityBackground width: parent.width height: parent.height anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { source: "qrc:/gcompris/src/activities/" + icon; anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter width: iconWidth - activitiesGrid.spacing height: width sourceSize.width: width fillMode: Image.PreserveAspectFit anchors.margins: 5 opacity: delegateItem.enabled ? 1 : 0.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 } } DialogChooseLevel { id: dialogChooseLevel onClose: { home() } onSaveData: { currentLevel = dialogChooseLevel.chosenLevel ApplicationSettings.setCurrentLevel(name, currentLevel) } onStartActivity: { clickMode = "play" selectCurrentItem() } } function selectCurrentItem() { if(pageView.busy || !delegateItem.enabled) return if(clickMode == "play") { particles.burst(50) ActivityInfoTree.currentActivity = ActivityInfoTree.menuTree[index] activityLoader.setSource("qrc:/gcompris/src/activities/" + ActivityInfoTree.currentActivity.name, { 'menu': activity, 'activityInfo': ActivityInfoTree.currentActivity, 'levelFolder': currentLevel }) if (activityLoader.status == Loader.Ready) loadActivity() } else { dialogChooseLevel.currentActivity = ActivityInfoTree.menuTree[index] - dialogChooseLevel.chosenLevel = currentLevel; displayDialog(dialogChooseLevel); } } } 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: clickMode === "activityConfig" || 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; } } GCText { id: activitySettingsLabel text: qsTr("Activity Settings") visible: clickMode === "activityConfig" width: parent.width height: paintedHeight anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter color: "black" } TextField { id: searchTextField width: parent.width visible: activity.currentTag === "search" 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 | activityConfig | 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) } onActivityConfigClicked: { if(clickMode == "play") { clickMode = "activityConfig" } else { clickMode = "play" } } 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/reversecount/Reversecount.qml b/src/activities/reversecount/Reversecount.qml index dc7a43c30..ebdeb5ef7 100644 --- a/src/activities/reversecount/Reversecount.qml +++ b/src/activities/reversecount/Reversecount.qml @@ -1,273 +1,258 @@ /* GCompris - ReverseCount.qml * * Copyright (C) 2014 Emmanuel Charruau * * Authors: * Bruno Coudoin (GTK+ version) * Emmanuel Charruau (Qt Quick port) * Bruno Coudoin (Major rework) * * 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 "reversecount.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ff00a4b0" signal start signal stop Component.onCompleted: { - dialogActivityConfig.getInitialConfiguration() + dialogActivityConfig.initialize() 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 readonly property string resourceUrl: activity.resourceUrl property var levels: activity.datasetLoader.item.data property alias background: background property alias backgroundImg: backgroundImg property alias bar: bar property alias bonus: bonus property alias chooseDiceBar: chooseDiceBar property alias tux: tux property alias fishToReach: fishToReach property int clockPosition: 4 property string mode: "dot" } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.onEnterPressed: Activity.moveTux() Keys.onReturnPressed: Activity.moveTux() onWidthChanged: { if(Activity.fishIndex > 0) { // set x fishToReach.x = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][0] * background.width / 5 + (background.width / 5 - tux.width) / 2 // set y fishToReach.y = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][1] * (background.height - background.height/5) / 5 + (background.height / 5 - tux.height) / 2 // Move Tux Activity.moveTuxToIceBlock() } } onHeightChanged: { if(Activity.fishIndex > 0) { // set x fishToReach.x = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][0] * background.width / 5 + (background.width / 5 - tux.width) / 2 // set y fishToReach.y = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][1] * (background.height - background.height/5) / 5 + (background.height / 5 - tux.height) / 2 // Move Tux Activity.moveTuxToIceBlock() } } Image { id: backgroundImg source: activity.resourceUrl + Activity.backgrounds[0] sourceSize.height: parent.height * 0.5 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } // === The ice blocks === Repeater { model: Activity.iceBlocksLayout Image { x: modelData[0] * background.width / 5 y: modelData[1] * (background.height- background.height/5) / 5 width: background.width / 5 height: background.height / 5 source: activity.resourceUrl + "iceblock.svg" } } Tux { id: tux sourceSize.width: Math.min(background.width / 6, background.height / 6) z: 11 } Image { id: fishToReach sourceSize.width: Math.min(background.width / 6, background.height / 6) z: 10 property string nextSource property int nextX property int nextY function showParticles() { particles.burst(40) } ParticleSystemStarLoader { id: particles clip: false } onOpacityChanged: { if(opacity == 0) { source = ""; source = nextSource; } } onSourceChanged: { if(source != "") { x = nextX y = nextY opacity = 1 } } Behavior on opacity { NumberAnimation { duration: 500 } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar - content: BarEnumContent { value: help | home | level | config } + content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() - onConfigClicked: { - dialogActivityConfig.active = true - dialogActivityConfig.setDefaultValues() + onActivityConfigClicked: { displayDialog(dialogActivityConfig) } } Image { id: clock anchors { right: parent.right bottom: parent.bottom margins: 10 } sourceSize.width: 66 * bar.barZoom property int remainingLife: items.clockPosition onRemainingLifeChanged: if(remainingLife >= 0) clockAnim.restart() SequentialAnimation { id: clockAnim alwaysRunToEnd: true ParallelAnimation { NumberAnimation { target: clock; properties: "opacity"; to: 0; duration: 800; easing.type: Easing.OutCubic } NumberAnimation { target: clock; properties: "rotation"; from: 0; to: 180; duration: 800; easing.type: Easing.OutCubic } } PropertyAction { target: clock; property: 'source'; value: activity.resourceUrl + "flower" + items.clockPosition + ".svg" } ParallelAnimation { NumberAnimation { target: clock; properties: "opacity"; to: 1; duration: 800; easing.type: Easing.OutCubic } NumberAnimation { target: clock; properties: "rotation"; from: 180; to: 0; duration: 800; easing.type: Easing.OutCubic } } } } - DialogActivityConfig { + DialogChooseLevel { id: dialogActivityConfig - currentActivity: activity - content: Component { - ActivityConfig { - background: dialogActivityConfig - } - } - onClose: home() - onLoadData: { - if(dataToSave && dataToSave["mode"]) { - items.mode = dataToSave["mode"]; - } + currentActivity: activity.activityInfo + + onClose: { + home() } onSaveData: { - var newMode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.modeBox.currentIndex].value; - if (newMode !== items.mode) { - items.mode = newMode; - dataToSave = {"mode": items.mode}; - } - Activity.initLevel(); + levelFolder = dialogActivityConfig.chosenLevel + currentActivity.currentLevel = dialogActivityConfig.chosenLevel + ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) + home() } - // to be removed - 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; - } + onLoadData: { + if(activityData && activityData["mode"]) { + items.mode = activityData["mode"]; } } } ChooseDiceBar { id: chooseDiceBar mode: items.mode x: background.width / 5 + 20 y: (background.height - background.height/5) * 3 / 5 audioEffects: activity.audioEffects } Bonus { id: bonus winSound: "qrc:/gcompris/src/activities/ballcatch/resource/tuxok.wav" looseSound: "qrc:/gcompris/src/activities/ballcatch/resource/youcannot.wav" onWin: Activity.nextLevel() onLoose: Activity.initLevel() } } } diff --git a/src/core/DialogChooseLevel.qml b/src/core/DialogChooseLevel.qml index 6583ad23d..dc24c89c4 100644 --- a/src/core/DialogChooseLevel.qml +++ b/src/core/DialogChooseLevel.qml @@ -1,352 +1,353 @@ /* GCompris - DialogChooseLevel.qml * * Copyright (C) 2018 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 GCompris 1.0 /** * todo * @ingroup components * * todo * * @sa ApplicationSettings * @inherit QtQuick.Item */ Rectangle { id: dialogChooseLevel visible: false /* Public interface: */ /** * type:string * The name of the activity in case of per-activity config. * * Will be autogenerated unless set by the caller. */ property string activityName /// @cond INTERNAL_DOCS property bool isDialog: true /** * type:string * Title of the configuration dialog. */ readonly property string title: currentActivity ? qsTr("%1 settings").arg(currentActivity.title) : "" property var difficultiesModel: [] property QtObject currentActivity property string chosenLevel + property var activityData + onActivityDataChanged: loadData() /// @endcond /** * Emitted when the config dialog has been closed. */ signal close /** * Emitted when the config dialog has been started. */ signal start onStart: initialize() signal stop /** * Emitted when the settings are to be saved. * * The actual persisting of the settings in the settings file is done by * DialogActivityConfig. The activity has to take care to update its * internal state. */ signal saveData signal startActivity /** * Emitted when the config settings have been loaded. */ signal loadData color: "#696da3" border.color: "black" border.width: 1 function initialize() { activityName = currentActivity.name.split('/')[0] // dataset information chosenLevel = currentActivity.currentLevel difficultiesModel = [] for(var level in currentActivity.levels) { objectiveLoader.dataFiles.push({"level": currentActivity.levels[level], "file": "qrc:/gcompris/src/activities/"+activityName+"/resource/"+currentActivity.levels[level]+"/Data.qml"}) } objectiveLoader.start() - - // activity configuration information - configLoader.getInitialConfiguration() } Loader { id: objectiveLoader property var dataFiles: [] property var currentFile signal start signal stop onStart: { var file = dataFiles.shift() currentFile = file source = file.file.toString() } onLoaded: { difficultiesModel.push({"level": currentFile.level, "objective": item.objective, "difficulty": item.difficulty}) if(dataFiles.length != 0) { start() } else { stop() } } onStop: { difficultiesRepeater.model = difficultiesModel } } Row { visible: true spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogChooseLevel.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 Row { spacing: 2 padding: 8 Image { id: titleIcon anchors { left: parent.left top: parent.top margins: 4 * ApplicationInfo.ratio } } GCText { id: title text: dialogChooseLevel.title width: dialogChooseLevel.width - (30 + cancel.width) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } } // Header buttons Row { id: datasetOptionsRow height: datasetVisibleButton.height width: parent.width spacing: parent.width / 4 anchors.leftMargin: parent.width / 8 Button { id: datasetVisibleButton text: qsTr("Dataset") enabled: difficultiesRepeater.count != 0 width: parent.width / 3 property bool selected: true style: GCButtonStyle { selected: datasetVisibleButton.selected } onClicked: { selected = true; } } Button { id: optionsVisibleButton text: qsTr("Options") enabled: activityConfigFile.exists("qrc:/gcompris/src/activities/"+activityName+"/ActivityConfig.qml") width: parent.width / 3 style: GCButtonStyle { selected: !datasetVisibleButton.selected } onClicked: { datasetVisibleButton.selected = false; } //showOptions() } } // "Dataset"/"Options" content Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogChooseLevel.width - 30 height: dialogChooseLevel.height - (30 + title.height * 1.2) - saveAndPlayRow.height - datasetOptionsRow.height - 3 * parent.spacing border.color: "black" border.width: 2 Flickable { id: flick anchors.margins: 8 anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom flickableDirection: Flickable.VerticalFlick clip: true contentHeight: contentItem.childrenRect.height + 40 * ApplicationInfo.ratio Loader { id: configLoader visible: !datasetVisibleButton.selected active: optionsVisibleButton.enabled source: active ? "qrc:/gcompris/src/activities/"+activityName+"/ActivityConfig.qml" : "" onItemChanged: { if(item) { item.background = dialogChooseLevel - dialogChooseLevel.saveData.connect(save); + dialogChooseLevel.saveData.connect(save) + getInitialConfiguration() } } function getInitialConfiguration() { + activityData = Qt.binding(function() { return item.dataToSave }) if(item) { item.dataToSave = ApplicationSettings.loadActivityConfiguration(activityName) item.setDefaultValues() } } function save() { item.saveValues() ApplicationSettings.saveActivityConfiguration(activityName, item.dataToSave) } } Column { visible: datasetVisibleButton.selected spacing: 10 ExclusiveGroup { id: levelsGroup } Repeater { id: difficultiesRepeater delegate: Row { height: objective.height Image { id: difficultyIcon source: "qrc:/gcompris/src/core/resource/difficulty" + modelData.difficulty + ".svg"; sourceSize.height: objective.indicatorImageHeight anchors.verticalCenter: objective.verticalCenter } GCDialogCheckBox { id: objective width: dialogChooseLevel.width - 30 - difficultyIcon.width - 2 * flick.anchors.margins text: modelData.objective exclusiveGroup: levelsGroup - checked: chosenLevel == modelData.level + checked: chosenLevel === modelData.level onClicked: chosenLevel = modelData.level } } } } } // The scroll buttons GCButtonScroll { anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: flick.bottom anchors.bottomMargin: 5 * ApplicationInfo.ratio onUp: flick.flick(0, 1400) onDown: flick.flick(0, -1400) upVisible: flick.visibleArea.yPosition <= 0 ? false : true downVisible: flick.visibleArea.yPosition + flick.visibleArea.heightRatio >= 1 ? false : true } } // Footer buttons Row { id: saveAndPlayRow height: cancelButton.height width: parent.width spacing: parent.width / 16 Button { id: cancelButton text: qsTr("Cancel") width: parent.width / 4 property bool selected: true style: GCButtonStyle {} onClicked: dialogChooseLevel.close() } Button { id: saveButton text: qsTr("Save") width: parent.width / 4 property bool selected: true style: GCButtonStyle { } onClicked: { saveData(); } } Button { id: saveAndStartButton text: qsTr("Save and start") width: parent.width / 3 style: GCButtonStyle { } onClicked: { saveData(); startActivity(); } } } Item { width: 1; height: 10 } } } // The cancel button GCButtonCancel { id: cancel onClose: { parent.close() } } File { id: activityConfigFile } }