Warning, /education/gcompris/src/activities/frieze/Frieze.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - Frieze.qml
0002  *
0003  * SPDX-FileCopyrightText: 2023 Bruno ANSELME <be.root@free.fr>
0004  * SPDX-FileCopyrightText: 2024 Timothée Giet <animtim@gmail.com>
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  *
0007  * References : https://del-en-maternelle.fr/les-domaines/maths/les-algorithmes/
0008  *              https://irem.univ-nantes.fr/wp-content/uploads/2019/12/Algorithmes.pdf
0009  */
0010 import QtQuick 2.12
0011 import GCompris 1.0
0012 
0013 import "../../core"
0014 import "qrc:/gcompris/src/core/core.js" as Core
0015 import "frieze.js" as Activity
0016 
0017 ActivityBase {
0018     id: activity
0019 
0020     onStart: focus = true
0021     onStop: {}
0022 
0023     pageComponent: Image {
0024         id: background
0025         source: "qrc:/gcompris/src/activities/crane/resource/background.svg"
0026         anchors.fill: parent
0027         fillMode: Image.PreserveAspectCrop
0028         sourceSize.height: height
0029         signal start
0030         signal stop
0031 
0032         Component.onCompleted: {
0033             activity.start.connect(start)
0034             activity.stop.connect(stop)
0035         }
0036 
0037         // Add here the QML items you need to access in javascript
0038         QtObject {
0039             id: items
0040             property Item main: activity.main
0041             property alias background: background
0042             property alias bonus: bonus
0043             property alias score: score
0044             property GCSfx audioEffects: activity.audioEffects
0045 
0046             property var levels: activity.datasetLoader.data
0047             property int subLevelCount: 0
0048             property int currentLevel: activity.currentLevel
0049             property int currentSubLevel: 0
0050             property alias solution: solution
0051             property alias answer: answer
0052             property alias tokens: tokens
0053             property alias solutionModel: solutionModel
0054             property alias answerModel: answerModel
0055             property alias tokensModel: tokensModel
0056             property int currentAnswer: 0
0057             property alias currentToken: tokens.currentIndex
0058             property alias animationToken: animationToken
0059             property alias readyButton: readyButton
0060             property alias errorRectangle: errorRectangle
0061 
0062             property alias instruction: instruction
0063             property alias file: file
0064             property bool buttonsBlocked: false
0065 
0066             function toggleReady() {
0067                 solution.visible = !solution.visible
0068                 readyButton.enabled = solution.visible
0069                 tokens.visible = !tokens.visible
0070             }
0071         }
0072         property int baseMargins: 10 * ApplicationInfo.ratio
0073         property int baseSizeValue: 60 * ApplicationInfo.ratio
0074 
0075         onStart: { Activity.start(items) }
0076         onStop: { Activity.stop() }
0077 
0078         ListModel { id: solutionModel }
0079         ListModel { id: answerModel }
0080         ListModel {
0081             id: tokensModel
0082             function randPosition() { return (Math.floor(Math.random() * count)) }                                      // choose a random position
0083             function shuffleModel() { for (var i = 0 ; i < count; i++) { move(randPosition(), randPosition(), 1) } }    // shuffle elements
0084         }
0085 
0086         File {
0087             id: file
0088             onError: console.error("File error: " + msg)
0089         }
0090 
0091         Rectangle {
0092             id: instructionArea
0093             opacity: 1
0094             radius: background.baseMargins
0095             color: "#373737"
0096             height: 40 * ApplicationInfo.ratio
0097             width: Math.min(320 * ApplicationInfo.ratio, parent.width - 2 * background.baseMargins)
0098             anchors.horizontalCenter: parent.horizontalCenter
0099             anchors.top: parent.top
0100             anchors.topMargin: background.baseMargins
0101 
0102             GCText {
0103                 id: instruction
0104                 wrapMode: TextEdit.WordWrap
0105                 horizontalAlignment: Text.AlignHCenter
0106                 verticalAlignment: Text.AlignVCenter
0107                 height: parent.height - background.baseMargins
0108                 width: parent.width - 2 * background.baseMargins
0109                 fontSizeMode: Text.Fit
0110                 color: 'white'
0111                 anchors.centerIn: instructionArea
0112             }
0113         }
0114 
0115         Item {
0116             id: layoutArea
0117             anchors.top: instructionArea.bottom
0118             anchors.bottom: okButton.top
0119             anchors.left: background.left
0120             anchors.right: background.right
0121             anchors.margins: background.baseMargins
0122         }
0123 
0124         Item {
0125             id: referenceArea // used to calculate the ideal token and flow size for solution and answer
0126             width: layoutArea.width - background.baseMargins
0127             height: (layoutArea.height - 5 * background.baseMargins) * 0.33
0128             property int tokenSize: Math.floor(Math.min(Core.fitItems(width, height, solutionModel.count), background.baseSizeValue))
0129         }
0130 
0131         Rectangle {
0132             id: solutionRect
0133             width: solution.childrenRect.width + background.baseMargins
0134             height: solution.childrenRect.height + background.baseMargins
0135             anchors.top: layoutArea.top
0136             anchors.horizontalCenter: parent.horizontalCenter
0137             color: "#E9E9E9"
0138             radius: background.baseMargins
0139             Flow {
0140                 id: solution
0141                 width: referenceArea.width
0142                 height: referenceArea.height
0143                 anchors.top: parent.top
0144                 anchors.topMargin: background.baseMargins * 0.5
0145                 anchors.left: parent.left
0146                 anchors.leftMargin: anchors.topMargin
0147                 layoutDirection: (Core.isLeftToRightLocale(ApplicationSettings.locale)) ?  Qt.LeftToRight : Qt.RightToLeft
0148                 Repeater {
0149                     model: solutionModel
0150                     delegate: TokenFrieze {
0151                         width: referenceArea.tokenSize
0152                     }
0153                 }
0154             }
0155 
0156         }
0157 
0158         Rectangle {
0159             id: answerRect
0160             width: answer.childrenRect.width + background.baseMargins
0161             height: answer.childrenRect.height + background.baseMargins
0162             anchors.horizontalCenter: parent.horizontalCenter
0163             anchors.top: solutionRect.bottom
0164             anchors.topMargin: background.baseMargins
0165             color: "#E9E9E9"
0166             radius: background.baseMargins
0167             Flow {
0168                 id: answer
0169                 width: referenceArea.width
0170                 height: referenceArea.height
0171                 anchors.top: parent.top
0172                 anchors.topMargin: background.baseMargins * 0.5
0173                 anchors.left: parent.left
0174                 anchors.leftMargin: anchors.topMargin
0175                 layoutDirection: (Core.isLeftToRightLocale(ApplicationSettings.locale)) ?  Qt.LeftToRight : Qt.RightToLeft
0176                 Repeater {
0177                     model: answerModel
0178                     delegate: TokenFrieze {
0179                         width: referenceArea.tokenSize
0180                     }
0181                 }
0182             }
0183 
0184             ErrorRectangle {
0185                 id: errorRectangle
0186                 anchors.fill: parent
0187                 radius: background.baseMargins
0188                 imageSize: parent.height * 0.75
0189                 function releaseControls() {
0190                     items.buttonsBlocked = false;
0191                 }
0192             }
0193         }
0194 
0195         Item {
0196             id: controlsArea
0197             width: layoutArea.width
0198             height: referenceArea.height
0199             anchors.top: answerRect.bottom
0200             anchors.topMargin: background.baseMargins
0201             anchors.horizontalCenter: parent.horizontalCenter
0202             Item {
0203                 id: tokensArea
0204                 width: parent.width - background.baseSizeValue * 2 - background.baseMargins * 2
0205                 height: parent.height
0206                 property int tokenSize: Math.floor(Math.min(Core.fitItems(width, height, tokensModel.count), background.baseSizeValue))
0207             }
0208             Rectangle {
0209                 id: tokensRect
0210                 width: tokens.contentItem.childrenRect.width + background.baseMargins
0211                 height: tokens.contentItem.childrenRect.height + background.baseMargins
0212                 anchors.top: parent.top
0213                 anchors.horizontalCenter: parent.horizontalCenter
0214                 color: "#E9E9E9"
0215                 radius: background.baseMargins
0216                 enabled: !items.buttonsBlocked
0217                 GridView {
0218                     id: tokens
0219                     width: tokensArea.width
0220                     height: tokensArea.height
0221                     cellWidth: tokensArea.tokenSize
0222                     cellHeight: tokensArea.tokenSize
0223                     anchors.top: parent.top
0224                     anchors.topMargin: background.baseMargins * 0.5
0225                     anchors.left: parent.left
0226                     anchors.leftMargin: anchors.topMargin
0227                     boundsBehavior: Flickable.StopAtBounds
0228                     keyNavigationWraps: true
0229                     enabled: (items.currentAnswer < answerModel.count)
0230                     opacity: (enabled || items.buttonsBlocked) ? 1.0 : 0.3
0231                     model: tokensModel
0232                     delegate: TokenFrieze {
0233                         width: tokensArea.tokenSize
0234                     }
0235                     highlightMoveDuration: 0
0236                     highlight: Rectangle {
0237                         color: "#00FFFFFF"
0238                         radius: height * 0.1
0239                         border.color: "#80373737"
0240                         border.width: 2 * ApplicationInfo.ratio
0241                     }
0242                 }
0243                 TokenFrieze {       // Animated token visible during drops
0244                     id: animationToken
0245                     width: tokensArea.tokenSize
0246                     content: Activity.emptyToken
0247                     animated: true
0248                     clickable: false
0249                     shown: true
0250                     visible:false
0251                     z: 10
0252                     states: [
0253                     State {
0254                         name: "moveto"
0255                         PropertyChanges {
0256                             target: animationToken
0257                             visible: true
0258                             x: tokens.mapFromItem(answer.children[items.currentAnswer], 0, 0).x
0259                             y: tokens.mapFromItem(answer.children[items.currentAnswer], 0, 0).y
0260                         }
0261                     }
0262                     ]
0263                     transitions: [
0264                     Transition {
0265                         to: "moveto"
0266                         SequentialAnimation {
0267                             alwaysRunToEnd: true
0268                             SmoothedAnimation { properties: "x,y"; duration: 300 }
0269                             ScriptAction {
0270                                 script: {   // End of moveto
0271                                     animationToken.state = ""
0272                                     answerModel.setProperty(items.currentAnswer, "content_", animationToken.content)
0273                                     tokens.currentItem.opacity = 1.0
0274                                     items.currentAnswer++
0275                                     items.buttonsBlocked = false
0276                                 }
0277                             }
0278                         }
0279                     }
0280                     ]
0281                 }
0282             }
0283 
0284             GCButton {
0285                 id: readyButton
0286                 width: Math.min(2 * background.baseSizeValue, tokensRect.width)
0287                 height: Math.min(background.baseSizeValue, tokensRect.height)
0288                 anchors.centerIn: tokensRect
0289                 text: qsTr("I am Ready")
0290                 opacity: (!enabled) ? 0.0 : 1.0
0291                 theme: "dark"
0292                 MouseArea {
0293                     anchors.fill: parent
0294                     onClicked: items.toggleReady()
0295                     enabled: !items.buttonsBlocked
0296                 }
0297             }
0298 
0299             Image {
0300                 id: hintButton
0301                 source: "qrc:/gcompris/src/core/resource/bar_hint.svg"
0302                 smooth: true
0303                 width: background.baseSizeValue
0304                 height: background.baseSizeValue
0305                 sourceSize.width: background.baseSizeValue
0306                 sourceSize.height: background.baseSizeValue
0307                 fillMode: Image.PreserveAspectFit
0308                 anchors.right: tokensRect.left
0309                 anchors.rightMargin: background.baseMargins
0310                 anchors.verticalCenter: tokensRect.verticalCenter
0311                 enabled: !solution.visible
0312                 opacity: (!enabled) ? 0.0 : 1.0
0313                 MouseArea {
0314                     anchors.fill: parent
0315                     onClicked: items.toggleReady()
0316                     enabled: !items.buttonsBlocked
0317                 }
0318             }
0319 
0320             Image {
0321                 id: cancelButton
0322                 source: enabled ? "qrc:/gcompris/src/core/resource/cancel.svg" : "qrc:/gcompris/src/core/resource/cancel_disabled.svg"
0323                 smooth: true
0324                 width: background.baseSizeValue
0325                 height: background.baseSizeValue
0326                 sourceSize.width: background.baseSizeValue
0327                 sourceSize.height: background.baseSizeValue
0328                 fillMode: Image.PreserveAspectFit
0329                 anchors.left: tokensRect.right
0330                 anchors.leftMargin: background.baseMargins
0331                 anchors.verticalCenter: tokensRect.verticalCenter
0332                 enabled: (items.currentAnswer > 0) && (!readyButton.enabled)
0333                 MouseArea {
0334                     anchors.fill: parent
0335                     enabled: !items.buttonsBlocked
0336                     onClicked: Activity.cancelDrop()
0337                 }
0338             }
0339         }
0340 
0341         DialogChooseLevel {
0342             id: dialogActivityConfig
0343             currentActivity: activity.activityInfo
0344 
0345             onSaveData: {
0346                 levelFolder = dialogActivityConfig.chosenLevels
0347                 currentActivity.currentLevels = dialogActivityConfig.chosenLevels
0348                 ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels)
0349             }
0350             onClose: {
0351                 home()
0352             }
0353             onStartActivity: {
0354                 background.stop()
0355                 background.start()
0356             }
0357         }
0358 
0359         DialogHelp {
0360             id: dialogHelp
0361             onClose: home()
0362         }
0363 
0364         BarButton {
0365             id: okButton
0366             anchors.right: score.left
0367             anchors.rightMargin: background.baseMargins
0368             anchors.verticalCenter: score.verticalCenter
0369             source: "qrc:/gcompris/src/core/resource/bar_ok.svg"
0370             width: background.baseSizeValue
0371             height: background.baseSizeValue
0372             sourceSize.height: background.baseSizeValue
0373             sourceSize.width: background.baseSizeValue
0374             onClicked: Activity.checkResult()
0375             visible: (items.currentAnswer === answerModel.count)
0376             mouseArea.enabled: !items.buttonsBlocked
0377         }
0378 
0379         Score {
0380             id: score
0381             numberOfSubLevels: items.subLevelCount
0382             currentSubLevel: items.currentSubLevel
0383             anchors.top: undefined
0384             anchors.bottom: background.bottom
0385             anchors.bottomMargin: bar.height * 1.5
0386             anchors.right: background.right
0387             anchors.rightMargin: background.baseMargins
0388             onStop: {
0389                 Activity.nextSubLevel()
0390             }
0391         }
0392         Bar {
0393             id: bar
0394             level: items.currentLevel + 1
0395             content: BarEnumContent { value: help | home | level | activityConfig }
0396             onHelpClicked: displayDialog(dialogHelp)
0397             onActivityConfigClicked: displayDialog(dialogActivityConfig)
0398             onPreviousLevelClicked: Activity.previousLevel()
0399             onNextLevelClicked: Activity.nextLevel()
0400             onHomeClicked: activity.home()
0401         }
0402 
0403         Bonus {
0404             id: bonus
0405             Component.onCompleted: win.connect(Activity.nextLevel)
0406             onStop: (items.solution.visible = !items.levels[items.currentLevel].hidden)
0407         }
0408 
0409         Keys.onPressed: Activity.handleKeys(event)
0410     }
0411 }