diff --git a/src/activities/drawing/Drawing.qml b/src/activities/drawing/Drawing.qml index cec062961..e839cd58e 100644 --- a/src/activities/drawing/Drawing.qml +++ b/src/activities/drawing/Drawing.qml @@ -1,891 +1,869 @@ /* GCompris - Drawing.qml * * Copyright (C) 2016 Toncu Stefan * 2018 Amit Sagtani * 2019 Timothée Giet * * Authors: * Stefan Toncu * Amit Sagtani * Timothée Giet * * 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 QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.0 import "../../core" import "drawing.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core // TODO: (optional): Shape creator: press on the canvas to draw lines; at the end, press on the starting point to create a shape ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#cacaca" signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } property bool started: false // When the width/height is changed, paint the last image on the canvas onWidthChanged: { if (items.background.started) { Activity.preserveImage() } } onHeightChanged: { if (items.background.started) { items.foldablePanels.activePanel = "null" Activity.preserveImage() } } File { id: file onError: console.error("File error: " + msg); } - Timer { - id: timer - interval: 1 - running: true - repeat: true - - property int index: 0 - - onTriggered: { - index ++ - - if (index >= 10) - stop() - } - } - - function reloadSelectedPen() { - timer.index = 0 - timer.start() - } - SaveToFilePrompt { id: saveToFilePrompt z: -1 } // 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 canvas: canvas - property alias timer: timer property alias area: area property alias inputText: inputTextFrame.inputText property alias inputTextFrame: inputTextFrame property alias parser: parser property alias gridView2: loadSavedPainting.gridView2 property alias file: file property alias load: load property alias mainRegion: main property alias shape: shape property alias backgroundColorPalette: backgroundColorPalette property int activeColorIndex: 1 property alias foldablePanels: foldablePanels property alias toolsMode: foldablePanels.toolsMode property alias saveToFilePrompt: saveToFilePrompt property alias stampGhostImage: stampGhostImage property alias onBoardText: onBoardText property alias fileDialog: fileDialog property color paintColor: "#000000" property color selectedColor: "#000000" property color backgroundColor: "#ffffff" property string urlImage property int urlImageWidth property int urlImageHeight property bool loadSavedImage: false property bool initSave: false property bool nothingChanged: true property bool widthHeightChanged: false property bool mainAnimationOnX: true property bool eraserMode: false property bool undoLock: false property int sizeS: 2 property int index: 0 property real globalOpacityValue: 1 property string toolSelected: "pencil" property string patternType: "dot" property string lastToolSelected: "pencil" property string toolCategory: "Brush" } JsonParser { id: parser onError: console.error("Paint: Error parsing JSON: " + msg); } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.onPressed: Activity.handleKeyNavigations(event) DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | reload } onHelpClicked: { displayDialog(dialogHelp) } onHomeClicked: { saveToFilePrompt.buttonPressed = "home" if (!items.nothingChanged) { saveToFilePrompt.text = qsTr("Do you want to save your painting?") saveToFilePrompt.opacity = 1 saveToFilePrompt.z = 200 } else { if (main.x == 0) load.opacity = 0 activity.home() } } onReloadClicked: { if (!items.nothingChanged) { saveToFilePrompt.buttonPressed = "reload" saveToFilePrompt.text = qsTr("Do you want to save your painting before reseting the board?") saveToFilePrompt.opacity = 1 saveToFilePrompt.z = 200 } else { Activity.initLevel() } } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } Item { id: main width: parent.width height: parent.height Behavior on x { enabled: items.mainAnimationOnX NumberAnimation { target: main property: "x" duration: 800 easing.type: Easing.InOutQuad } } Behavior on y { NumberAnimation { target: main property: "y" duration: 800 easing.type: Easing.InOutQuad } } // Text input box. TextInputTool { id: inputTextFrame } Canvas { id: canvas width: parent.width height: parent.height scale: 1 x: 0 y: 0 renderStrategy: Canvas.Threaded renderTarget: Canvas.FramebufferObject property real lastX property real lastY // for brush property var lastPoint property var currentPoint property var ctx property string url: "" // For bucket-fill Tool property int startX: -1 property int startY: -1 property int finishX: -1 property int finishY: -1 property bool isBucketDone: true GCText { id: onBoardText text: "" color: items.paintColor font.pointSize: items.sizeS * 10 z: -1 opacity: 0 } Image { id: stampGhostImage source: items.toolsMode.activeStampImageSource width: items.toolsMode.activeStampWidth height: items.toolsMode.activeStampHeight fillMode: Image.PreserveAspectFit z: -1 opacity: items.globalOpacityValue visible: items.toolSelected === "stamp" onSourceChanged: { items.toolsMode.activeStampDimensionRatio = sourceSize.width / sourceSize.height items.canvas.loadImage(items.toolsMode.activeStampImageSource) } } onImageLoaded: { // rules when calling loadImage, which happens in various cases (load stamp image, load undo and redo, load file...) if (items.undoLock == true) { canvas.ctx.globalAlpha = 1 canvas.ctx.drawImage(items.urlImage, 0, 0, items.urlImageWidth, items.urlImageHeight) requestPaint() items.undoLock = false } else if (items.toolSelected != "stamp" && items.urlImage != "") { canvas.ctx.globalAlpha = 1 canvas.ctx.fillStyle = items.backgroundColor canvas.ctx.fillRect(0, 0, items.background.width, items.background.height) canvas.ctx.drawImage(items.urlImage, 0, 0, canvas.width, canvas.height) // mark the loadSavedImage as finished // items.loadSavedImage = false requestPaint() // unloadImage(items.urlImage) items.mainAnimationOnX = true // items.urlImage = "" } } function resetShape () { area.currentShape.rotationn = 0 area.currentShape.x = 0 area.currentShape.y = 0 area.currentShape.width = 0 area.currentShape.height = 0 area.endX = 0 area.endY = 0 canvas.lastX = 0 canvas.lastY = 0 } function midPointBtw(p1, p2) { return { x: p1.x + (p2.x - p1.x) / 2, y: p1.y + (p2.y - p1.y) / 2 }; } function distanceBetween(point1, point2) { return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)); } function angleBetween(point1, point2) { return Math.atan2( point2.x - point1.x, point2.y - point1.y ); } function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function removeShadow() { // remove the shadow effect canvas.ctx.shadowColor = 'rgba(0,0,0,0)' canvas.ctx.shadowBlur = 0 canvas.ctx.shadowOffsetX = 0 canvas.ctx.shadowOffsetY = 0 } MouseArea { id: areaSafe anchors.fill: parent enabled: items.foldablePanels.activePanel != "null" onClicked: { items.foldablePanels.foldAnimation.start() items.foldablePanels.activePanel = "null" } } MouseArea { id: area anchors.fill: parent enabled: !areaSafe.enabled hoverEnabled: items.toolSelected === "text" || items.toolSelected === "stamp" property var mappedMouse: mapToItem(parent, mouseX, mouseY) property var currentShape: items.toolSelected == "circle" ? circle : rectangle property real originalX property real originalY property real endX property real endY Timer { id: moveOnBoard property var moveTarget: items.toolSelected === "text" ? items.onBoardText : items.stampGhostImage property real topMargin: moveTarget === items.onBoardText ? onBoardText.height * 0.8 : stampGhostImage.height / 2 property real leftMargin: moveTarget === items.onBoardText ? 0 : stampGhostImage.width / 2 interval: 1 repeat: true running: false triggeredOnStart: { moveTarget.x = area.realMouseX - leftMargin moveTarget.y = area.realMouseY - topMargin } } property real realMouseX: mouseX property real realMouseY: mouseY // functions for starting tools in onPressed: function startGeometric(selected){ // set the origin coordinates for current shape currentShape.x = mapToItem(parent, mouseX, mouseY).x currentShape.y = mapToItem(parent, mouseX, mouseY).y originalX = currentShape.x originalY = currentShape.y // set the current color for the current shape currentShape.color = items.paintColor if (selected == "line") currentShape.height = items.sizeS } function initMouse() { canvas.lastX = mouseX canvas.lastY = mouseY } onPressed: { if (items.nothingChanged) items.nothingChanged = false mappedMouse = mapToItem(parent, mouseX, mouseY) print("tools: ",items.toolSelected) canvas.ctx = canvas.getContext("2d") tempCanvas.ctx = tempCanvas.getContext("2d") //always make sure that alpha is set to slider value for tools actions canvas.ctx.globalAlpha = items.globalOpacityValue canvas.ctx.strokeStyle = items.paintColor tempCanvas.ctx.strokeStyle = items.eraserMode ? items.backgroundColor : items.paintColor if (items.toolCategory == "Geometric") { startGeometric(items.toolSelected) } else if (items.toolSelected == "pattern") { canvas.ctx.strokeStyle = "#ffffff" // very important! initMouse() Activity.points.push({x: mouseX, y: mouseY}) } else if (items.toolSelected == "brush3") { initMouse() canvas.lastPoint = { x: mouseX, y: mouseY } } else if (items.toolSelected == "brush4") { canvas.ctx.strokeStyle = "#ffffff" Activity.points.push({x: mouseX, y: mouseY}) } else if (items.toolSelected == "brush5") { Activity.connectedPoints.push({x: mouseX, y: mouseY}) } else { initMouse() print("ON Pressed - default tool init") } } onReleased: { // for line tool mappedMouse = mapToItem(parent, mouseX, mouseY) area.endX = mappedMouse.x area.endY = mappedMouse.y // Reset text elements: // hide the text onBoardText.opacity = 0 onBoardText.z = -1 // stop the text following the cursor if (moveOnBoard.running) moveOnBoard.stop() if (items.toolSelected == "pencil" || items.toolSelected == "eraser") { var tempImage = tempCanvas.ctx.getImageData(0, 0, tempCanvas.width, tempCanvas.height) canvas.ctx.drawImage(tempImage, 0, 0) tempCanvas.ctx.clearRect(0, 0, tempCanvas.width, tempCanvas.height) tempCanvas.requestPaint() canvas.requestPaint() } if (items.toolSelected == "line") { canvas.removeShadow() canvas.ctx.fillStyle = items.paintColor canvas.ctx.beginPath() var angleRad = (360 - area.currentShape.rotationn) * Math.PI / 180 var auxX = items.sizeS * Math.sin(angleRad) var auxY = items.sizeS * Math.cos(angleRad) canvas.ctx.moveTo(area.currentShape.x,area.currentShape.y) canvas.ctx.lineTo(area.endX,area.endY) canvas.ctx.lineTo(area.endX + auxX,area.endY + auxY) canvas.ctx.lineTo(area.currentShape.x + auxX,area.currentShape.y + auxY) canvas.ctx.closePath() canvas.ctx.fill() canvas.resetShape() canvas.requestPaint() } if (items.toolSelected == "circle") { canvas.removeShadow() canvas.ctx.beginPath(); canvas.ctx.arc(area.currentShape.x + area.currentShape.width / 2, area.currentShape.y + area.currentShape.width / 2, area.currentShape.width / 2, 0, 2 * Math.PI, false); canvas.ctx.fillStyle = items.paintColor canvas.ctx.fill(); canvas.resetShape() canvas.requestPaint() } if (items.toolSelected == "rectangle" || items.toolSelected == "lineShift") { canvas.removeShadow() canvas.ctx.fillStyle = items.paintColor canvas.ctx.beginPath() canvas.ctx.moveTo(area.currentShape.x,area.currentShape.y) canvas.ctx.lineTo(area.currentShape.x + area.currentShape.width,area.currentShape.y) canvas.ctx.lineTo(area.currentShape.x + area.currentShape.width,area.currentShape.y + area.currentShape.height) canvas.ctx.lineTo(area.currentShape.x,area.currentShape.y + area.currentShape.height) canvas.ctx.closePath() canvas.ctx.fill() canvas.resetShape() canvas.requestPaint() } if (items.toolSelected == "text") { canvas.removeShadow() canvas.ctx.fillStyle = items.paintColor canvas.ctx.font = inputTextFrame.font console.log(inputTextFrame.font) canvas.ctx.fillText(onBoardText.text,area.realMouseX,area.realMouseY) onBoardText.text = "" Activity.selectTool("Brush") canvas.requestPaint() } // reset the "points" array if (items.toolSelected == "pattern" || items.toolSelected == "brush4") Activity.points = [] if (items.toolSelected == "brush5") Activity.connectedPoints = [] if (items.toolSelected != "stamp" && items.toolSelected != "fill") { Activity.pushToUndo() Activity.resetRedo() } } onPositionChanged: { canvas.ctx.globalCompositeOperation = 'source-over' if(items.eraserMode) { canvas.ctx.fillStyle = items.backgroundColor } if (items.toolSelected == "pencil" || items.toolSelected == "eraser") { canvas.removeShadow() tempCanvas.ctx.globalAlpha = 1 tempCanvas.ctx.lineWidth = items.toolSelected == "eraser" ? items.sizeS * 4 : items.sizeS tempCanvas.ctx.lineCap = 'round' tempCanvas.ctx.lineJoin = 'round' tempCanvas.ctx.beginPath() tempCanvas.ctx.moveTo(canvas.lastX, canvas.lastY) canvas.lastX = area.mouseX canvas.lastY = area.mouseY tempCanvas.ctx.lineTo(canvas.lastX, canvas.lastY) tempCanvas.ctx.stroke() tempCanvas.requestPaint() } else if (items.toolSelected == "rectangle") { mappedMouse = mapToItem(parent, mouseX, mouseY) var width = mappedMouse.x - area.originalX var height = mappedMouse.y - area.originalY if (Math.abs(width) > Math.abs(height)) { if (width < 0) { currentShape.x = area.originalX + width currentShape.y = area.originalY } if (height < 0) currentShape.y = area.originalY + height currentShape.width = Math.abs(width) currentShape.height = Math.abs(height) } else { if (height < 0) { currentShape.x = area.originalX currentShape.y = area.originalY + height } if (width < 0) currentShape.x = area.originalX + width currentShape.height = Math.abs(height) currentShape.width = Math.abs(width) } } else if (items.toolSelected == "circle") { mappedMouse = mapToItem(parent, mouseX, mouseY) var width = mappedMouse.x - area.originalX var height = mappedMouse.y - area.originalY if (height < 0 && width < 0) { currentShape.x = area.originalX - currentShape.width currentShape.y = area.originalY - currentShape.height } else if (height < 0) { currentShape.x = area.originalX currentShape.y = area.originalY - currentShape.height } else if (width < 0) { currentShape.x = area.originalX - currentShape.width currentShape.y = area.originalY } else { currentShape.x = area.originalX currentShape.y = area.originalY } currentShape.height = currentShape.width = Math.max(Math.abs(width), Math.abs(height)) } else if (items.toolSelected == "line") { mappedMouse = mapToItem(parent, mouseX, mouseY) var width = mappedMouse.x - area.originalX var height = mappedMouse.y - area.originalY var distance = Math.sqrt( Math.pow(width,2) + Math.pow(height,2) ) var p1x = area.originalX var p1y = area.originalY var p2x = area.originalX + 200 var p2y = area.originalY var p3x = mappedMouse.x var p3y = mappedMouse.y var p12 = Math.sqrt(Math.pow((p1x - p2x),2) + Math.pow((p1y - p2y),2)) var p23 = Math.sqrt(Math.pow((p2x - p3x),2) + Math.pow((p2y - p3y),2)) var p31 = Math.sqrt(Math.pow((p3x - p1x),2) + Math.pow((p3y - p1y),2)) var angleRad = Math.acos((p12 * p12 + p31 * p31 - p23 * p23) / (2 * p12 * p31)) var angleDegrees if (height < 0) angleDegrees = angleRad * 180 / Math.PI else angleDegrees = 360 - angleRad * 180 / Math.PI currentShape.rotationn = 360 - angleDegrees currentShape.width = distance } else if (items.toolSelected == "lineShift") { mappedMouse = mapToItem(parent, mouseX, mouseY) var width = mappedMouse.x - area.originalX var height = mappedMouse.y - area.originalY if (Math.abs(width) > Math.abs(height)) { if (height < 0) currentShape.y = area.originalY if (width < 0) { currentShape.x = area.originalX + width currentShape.y = area.originalY } currentShape.width = Math.abs(width) currentShape.height = items.sizeS } else { if (width < 0) currentShape.x = area.originalX if (height < 0) { currentShape.x = area.originalX currentShape.y = area.originalY + height } currentShape.height = Math.abs(height) currentShape.width = items.sizeS } } else if (items.toolSelected == "pattern") { canvas.removeShadow() canvas.ctx.strokeStyle = canvas.ctx.createPattern(shape.toDataURL(), 'repeat') Activity.points.push({x: mouseX, y: mouseY}) canvas.ctx.lineWidth = items.sizeS * 5 canvas.ctx.lineJoin = canvas.ctx.lineCap = 'round' var p1 = Activity.points[0] var p2 = Activity.points[1] if (!p1 || !p2) return canvas.ctx.beginPath() canvas.ctx.moveTo(p1.x, p1.y) for (var i = 1, len = Activity.points.length; i < len; i++) { var midPoint = canvas.midPointBtw(p1, p2); canvas.ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); p1 = Activity.points[i]; p2 = Activity.points[i+1]; } canvas.ctx.lineTo(p1.x, p1.y); canvas.ctx.stroke(); canvas.requestPaint() } else if (items.toolSelected == "spray") { canvas.removeShadow() canvas.lastX = mouseX canvas.lastY = mouseY canvas.ctx.lineWidth = items.sizeS * 5 canvas.ctx.lineJoin = canvas.ctx.lineCap = 'round' canvas.ctx.moveTo(canvas.lastX, canvas.lastY) canvas.ctx.fillStyle = items.paintColor for (var i = 50; i--; i >= 0) { var radius = items.sizeS * 5; var offsetX = canvas.getRandomInt(-radius, radius); var offsetY = canvas.getRandomInt(-radius, radius); canvas.ctx.fillRect(canvas.lastX + offsetX, canvas.lastY + offsetY, 1, 1); } canvas.requestPaint() } else if (items.toolSelected == "brush3") { canvas.removeShadow() canvas.lastX = mouseX canvas.lastY = mouseY canvas.ctx.lineWidth = items.sizeS * 1.2 canvas.ctx.lineJoin = canvas.ctx.lineCap = 'round'; canvas.ctx.beginPath(); canvas.ctx.globalAlpha = 1; canvas.ctx.moveTo(canvas.lastPoint.x, canvas.lastPoint.y); canvas.ctx.lineTo(canvas.lastX, canvas.lastY); canvas.ctx.stroke(); canvas.ctx.moveTo(canvas.lastPoint.x - 3, canvas.lastPoint.y - 3); canvas.ctx.lineTo(canvas.lastX - 3, canvas.lastY - 3); canvas.ctx.stroke(); canvas.ctx.moveTo(canvas.lastPoint.x - 2, canvas.lastPoint.y - 2); canvas.ctx.lineTo(canvas.lastX - 2, canvas.lastY - 2); canvas.ctx.stroke(); canvas.ctx.moveTo(canvas.lastPoint.x + 2, canvas.lastPoint.y + 2); canvas.ctx.lineTo(canvas.lastX + 2, canvas.lastY + 2); canvas.ctx.stroke(); canvas.ctx.moveTo(canvas.lastPoint.x + 3, canvas.lastPoint.y + 3); canvas.ctx.lineTo(canvas.lastX + 3, canvas.lastY + 3); canvas.ctx.stroke(); canvas.lastPoint = { x: canvas.lastX, y: canvas.lastY }; canvas.requestPaint() } else if(items.toolSelected == "brush4") { canvas.removeShadow() Activity.points.push({x: mouseX, y: mouseY}) canvas.ctx.lineJoin = canvas.ctx.lineCap = 'round' canvas.ctx.fillStyle = items.paintColor canvas.ctx.lineWidth = items.sizeS / 4 for (var i = 0; i < Activity.points.length; i++) { canvas.ctx.beginPath(); canvas.ctx.arc(Activity.points[i].x, Activity.points[i].y, 5 * items.sizeS, 0, Math.PI * 2, false); canvas.ctx.fill(); canvas.ctx.stroke(); } canvas.requestPaint() } else if(items.toolSelected == "brush5") { canvas.removeShadow() Activity.connectedPoints.push({x: mouseX, y: mouseY}) canvas.ctx.lineJoin = canvas.ctx.lineCap = 'round'; canvas.ctx.lineWidth = 1 var p1 = Activity.connectedPoints[0] var p2 = Activity.connectedPoints[1] if (!p1 || !p2) return canvas.ctx.beginPath() canvas.ctx.moveTo(p1.x, p1.y) for (var i = 1, len = Activity.connectedPoints.length; i < len; i++) { var midPoint = canvas.midPointBtw(p1, p2) canvas.ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y) p1 = Activity.connectedPoints[i] p2 = Activity.connectedPoints[i+1] } canvas.ctx.lineTo(p1.x, p1.y) canvas.ctx.stroke() for (var i = 0; i < Activity.connectedPoints.length; i++) { var dx = Activity.connectedPoints[i].x - Activity.connectedPoints[Activity.connectedPoints.length-1].x; var dy = Activity.connectedPoints[i].y - Activity.connectedPoints[Activity.connectedPoints.length-1].y; var d = dx * dx + dy * dy; if (d < 1000) { canvas.ctx.beginPath(); canvas.ctx.strokeStyle = 'rgba(0,0,0,0.8)'; canvas.ctx.moveTo( Activity.connectedPoints[Activity.connectedPoints.length-1].x + (dx * 0.1), Activity.connectedPoints[Activity.connectedPoints.length-1].y + (dy * 0.1)); canvas.ctx.lineTo( Activity.connectedPoints[i].x - (dx * 0.1), Activity.connectedPoints[i].y - (dy * 0.1)); canvas.ctx.stroke(); } } canvas.requestPaint() } else if (items.toolSelected == "blur") { canvas.ctx.lineJoin = canvas.ctx.lineCap = 'round'; canvas.ctx.shadowBlur = 10 canvas.ctx.shadowColor = items.paintColor canvas.ctx.lineWidth = items.sizeS canvas.ctx.strokeStyle = items.paintColor canvas.ctx.beginPath() canvas.ctx.moveTo(canvas.lastX, canvas.lastY) canvas.lastX = area.mouseX canvas.lastY = area.mouseY canvas.ctx.lineTo(canvas.lastX, canvas.lastY) canvas.ctx.stroke() canvas.requestPaint() } } onClicked: { if (items.toolSelected === "fill") { canvas.startX = mouseX canvas.startY = mouseY Activity.paintBucket() Activity.pushToUndo() Activity.resetRedo() }else if(items.toolSelected === "stamp") { canvas.ctx.drawImage(stampGhostImage.source, stampGhostImage.x + (stampGhostImage.width-stampGhostImage.paintedWidth) / 2, stampGhostImage.y + (stampGhostImage.height-stampGhostImage.paintedHeight) / 2, stampGhostImage.paintedWidth, stampGhostImage.paintedHeight) canvas.requestPaint() activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav') Activity.pushToUndo() Activity.resetRedo() console.log("stamp clicked...") } } } Rectangle { id: rectangle color: items.paintColor enabled: items.toolSelected == "rectangle" || items.toolSelected == "line"|| items.toolSelected == "lineShift" visible: enabled opacity: items.globalOpacityValue property real rotationn: 0 transform: Rotation { id: rotationRect origin.x: 0 origin.y: 0 angle: rectangle.rotationn } } Rectangle { id: circle radius: width / 2 color: items.paintColor enabled: items.toolSelected == "circle" visible: enabled opacity: items.globalOpacityValue property real rotationn: 0 } } } LoadDrawings { id: load } LoadSavedDrawings { id: loadSavedPainting } Canvas { id: shape opacity: 0 } Canvas { id: tempCanvas opacity: items.globalOpacityValue width: parent.width height: parent.height scale: 1 x: 0 y: 0 property var ctx } FoldablePanels { id: foldablePanels } BackgroundColorsPalette { id: backgroundColorPalette visible: false } FileDialog { id: fileDialog title: qsTr("Choose an image") selectMultiple: false nameFilters: [ qsTr("Image files (*.jpg *.png *.svg)")] onAccepted: { items.toolsMode.activeStampImageSource = fileDialog.fileUrl console.log("You choose " + fileDialog.fileUrl) } } } } diff --git a/src/activities/drawing/FoldablePanels.qml b/src/activities/drawing/FoldablePanels.qml index 505bce8ad..e3fc99a91 100644 --- a/src/activities/drawing/FoldablePanels.qml +++ b/src/activities/drawing/FoldablePanels.qml @@ -1,630 +1,629 @@ /* GCompris - FoldablePanels.qml * * Copyright (C) 2018 Amit Sagtani * * Authors: * Amit Sagtani * Timothée Giet * * 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 QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import "../../core" import "drawing.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core Item { id: root property int tabWidth: background.width * 0.18 property int tabHeight: background.height * 0.06 property alias colorModel: colorModel property alias mainPanel: mainPanel property alias foldAnimation: foldAnimation property string activePanel: "null" property alias toolsMode: toolsMode property color panelColor: "#1A1A1B" property string activeToolIconSource: "qrc:/gcompris/src/activities/drawing/resource/pen.svg" function colorUpdate(){ items.paintColor = colorPicker.currentColorCode items.selectedColor = colorPicker.currentColorCode if (items.toolSelected == "pattern") Activity.getPattern() } ListModel { id: menuModel ListElement { itemName: qsTr("Save") imgSource: "qrc:/gcompris/src/activities/drawing/resource/filesaveas.svg" } ListElement { itemName: qsTr("Load") imgSource: "qrc:/gcompris/src/activities/drawing/resource/fileopen.svg" } ListElement { itemName: qsTr("Erase all") imgSource: "qrc:/gcompris/src/activities/drawing/resource/empty.svg" } ListElement { itemName: qsTr("Background color") imgSource: "qrc:/gcompris/src/activities/drawing/resource/color_wheel.svg" } ListElement { itemName: qsTr("Export to PNG") imgSource: "qrc:/gcompris/src/activities/drawing/resource/empty.svg" } } ListModel { id: toolsModel // ListElement { itemName: qsTr("Pencil") // imgSource: "qrc:/gcompris/src/activities/drawing/resource/pen.svg" } ListElement { itemName: qsTr("Brush") imgSource: "qrc:/gcompris/src/activities/drawing/resource/brush_paint.png" } ListElement { itemName: qsTr("Geometric") imgSource: "qrc:/gcompris/src/activities/drawing/resource/empty.svg" } ListElement { itemName: qsTr("Text") imgSource: "qrc:/gcompris/src/activities/drawing/resource/empty.svg" } ListElement { itemName: qsTr("Eraser") imgSource: "qrc:/gcompris/src/activities/drawing/resource/erase.svg" } ListElement { itemName: qsTr("Bucket fill") imgSource: "qrc:/gcompris/src/activities/drawing/resource/fill.svg" } ListElement { itemName: qsTr("Stamp") imgSource: "qrc:/gcompris/src/activities/drawing/resource/empty.svg" } } ListModel { id: colorModel ListElement {colorCode: "#ff0000"} ListElement {colorCode: "#000000"} ListElement {colorCode: "#0000ff"} ListElement {colorCode: "#ffff00"} ListElement {colorCode: "#00ffff"} ListElement {colorCode: "#ff00ff"} ListElement {colorCode: "#800000"} ListElement {colorCode: "#000080"} ListElement {colorCode: "#ff4500"} ListElement {colorCode: "#A0A0A0"} ListElement {colorCode: "#d2691e"} ListElement {colorCode: "#8b008b"} } Rectangle { id: menuTitle width: root.tabWidth height: root.tabHeight radius: 10 color: panelColor border.color: "white" y: -7 MouseArea { anchors.fill: parent enabled: (mainPanel.y < -5 && activePanel != "mainPanel") || (mainPanel.y > -5 && activePanel === "mainPanel") onClicked: { animTarget = menuTitle colorGrid.visible = false menuGrid.visible = true root.activePanel = "mainPanel" if(mainPanel.panelUnFolded) { foldAnimation.start() } else { menuGrid.model = menuModel menuTitle.visible = true menuGrid.visible = true unfoldAnimation.start() } } } Rectangle { anchors.left: parent.left anchors.bottom: parent.bottom width: parent.width - parent.height height: parent.height color: "#00000000" GCText { text: qsTr("Menu") anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: Text.Fit color: "white" } } Rectangle { id: menuIconLayout height: parent.height * 0.5 width: height color: "#00000000" anchors.right: parent.right anchors.rightMargin: parent.height * 0.3 anchors.verticalCenter: parent.verticalCenter Rectangle { color: "#ffffff" width: parent.width height: parent.height * 0.1 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top } Rectangle { color: "#ffffff" width: parent.width height: parent.height * 0.1 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } Rectangle { color: "#ffffff" width: parent.width height: parent.height * 0.1 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom } } } Rectangle { id: toolsTitle width: root.tabWidth height: root.tabHeight radius: 10 color: panelColor border.color: "white" x: width + 2 y: -7 MouseArea { anchors.fill: parent enabled: (mainPanel.y < -5 && activePanel != "toolsPanel") || (mainPanel.y > -5 && activePanel === "toolsPanel") onClicked: { animTarget = toolsTitle colorGrid.visible = false menuGrid.visible = true root.activePanel = "toolsPanel" if(mainPanel.panelUnFolded) { foldAnimation.start() } else { toolsTitle.visible = true menuGrid.model = toolsModel menuGrid.visible = true unfoldAnimation.start() } } } Rectangle { anchors.left: parent.left anchors.bottom: parent.bottom width: parent.width - parent.height height: parent.height color: "#00000000" GCText { text: qsTr("Tools") anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: Text.Fit color: "white" } } Image { id: activeTool source: activeToolIconSource height: parent.height * 0.8 width: height sourceSize.height: height sourceSize.width: width fillMode: Image.PreserveAspectFit anchors.right: parent.right anchors.rightMargin: parent.height * 0.1 anchors.verticalCenter: parent.verticalCenter } } Rectangle { id: colorsTitle width: root.tabWidth height: root.tabHeight radius: 10 color: panelColor border.color: "white" x: background.width - 2 * width - 2 y: -7 z: mainPanel.z - 1 MouseArea { anchors.fill: parent enabled: (mainPanel.y < -5 && activePanel != "colorPanel") || (mainPanel.y > -5 && activePanel === "colorPanel") onClicked: { animTarget = colorsTitle menuGrid.visible = false colorGrid.visible = true root.activePanel = "colorPanel" if(mainPanel.panelUnFolded) { foldAnimation.start() } else { colorsTitle.visible = true unfoldAnimation.start() } } } Rectangle { anchors.left: parent.left anchors.bottom: parent.bottom width: parent.width - parent.height height: parent.height color: "#00000000" GCText { text: qsTr("Color") anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: Text.Fit color: "white" } } Rectangle { id: activeColor height: parent.height * 0.8 width: height color: colorPicker.currentColorCode radius: 10 anchors.right: parent.right anchors.rightMargin: parent.height * 0.1 anchors.verticalCenter: parent.verticalCenter border.width: 2 border.color: "white" } } Rectangle { id: toolOptionsTitle width: root.tabWidth height: root.tabHeight radius: 10 color: panelColor border.color: "white" x: background.width - width y: -7 MouseArea { anchors.fill: parent enabled: (mainPanel.y < -5 && activePanel != "toolOptions") || (mainPanel.y > -5 && activePanel === "toolOptions") onClicked: { animTarget = toolOptionsTitle root.activePanel = "toolOptions" menuGrid.visible = false colorGrid.visible = false if(mainPanel.panelUnFolded) { foldAnimation.start() } else { toolOptionsTitle.visible = true unfoldAnimation.start() } } } Rectangle { anchors.left: parent.left anchors.bottom: parent.bottom width: parent.width - parent.height height: parent.height color: "#00000000" GCText { text: qsTr("Options") anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: Text.Fit color: "white" } } Rectangle { id: optionsIconLayout height: parent.height * 0.8 width: height color: "#00000000" radius: 10 opacity: items.globalOpacityValue anchors.right: parent.right anchors.rightMargin: parent.height * 0.1 anchors.verticalCenter: parent.verticalCenter Rectangle{ id: optionsIcon height: parent.height * items.sizeS * 0.04 width: height radius: height * 0.5 color: "#ffffff" opacity: items.globalOpacityValue anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } } } Rectangle { id: undoPanel width: root.tabHeight * 2.4 height: root.tabHeight radius: 10 color: panelColor border.color: "white" x: background.width * 0.5 - width * 0.5 y: -7 //z: mainPanel.z - 1 MouseArea { anchors.fill: parent } Image { id: undo source: "qrc:/gcompris/src/activities/drawing/resource/undo.svg" height: parent.height * 0.8 width: height sourceSize.width: height sourceSize.height: height anchors.left: parent.left anchors.leftMargin: parent.width * 0.1 anchors.verticalCenter: parent.verticalCenter mipmap: true MouseArea { anchors.fill: parent hoverEnabled: true onPressed: undo.scale = 0.9 onReleased: undo.scale = 1 onClicked: { Activity.selectTool("Undo") activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') } } } Image { id: redo source: "qrc:/gcompris/src/activities/drawing/resource/redo.svg" height: undo.height width: height sourceSize.width: height sourceSize.height: height anchors.right: parent.right anchors.rightMargin: parent.width * 0.1 anchors.verticalCenter: parent.verticalCenter mipmap: true MouseArea { anchors.fill: parent hoverEnabled: true onPressed: redo.scale = 0.9 onReleased: redo.scale = 1 onClicked: { Activity.selectTool("Redo") activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') } } } } property var animTarget: menuTitle NumberAnimation { id: unfoldTitle target: animTarget property: "y" to: mainPanel.height - 7 duration: 200 easing.type: Easing.InOutQuad } NumberAnimation { id: foldTitle target: animTarget property: "y" to: -7 duration: 200 easing.type: Easing.InOutQuad onStopped: root.activePanel = "null" } Rectangle { id: mainPanel anchors.leftMargin: 5 width: background.width height: background.height / 2.4 color: panelColor y: -height border.color: "white" property bool panelUnFolded: y >= -5 NumberAnimation { id: foldAnimation target: mainPanel property: "y" to: - mainPanel.height duration: 200 easing.type: Easing.InOutQuad onStarted: { foldTitle.start() activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') } } // This mouseArea overrides the canvas region and avoid drawing through panel. MouseArea { anchors.fill: parent } NumberAnimation { id: unfoldAnimation target: mainPanel property: "y" to: 0 duration: 200 easing.type: Easing.InOutQuad onStarted: { unfoldTitle.start() activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/scroll.wav') } } GridView { id: menuGrid width: parent.width * 0.75 height: parent.height * 0.80 anchors.centerIn: parent visible: root.activePanel == "mainPanel" || root.activePanel == "toolsPanel" anchors.topMargin: 30 cellWidth: width / 4 cellHeight: height / 2.2 model: menuModel delegate:Item { Image { id: img source: imgSource sourceSize.height: menuGrid.cellHeight * 0.60 fillMode: Image.PreserveAspectFit MouseArea { anchors.fill: parent hoverEnabled: true onEntered: parent.scale = 1.1 onExited: parent.scale = 1.0 onClicked: { console.log(itemName) if (root.activePanel == "toolsPanel"){ activeToolIconSource = img.source; items.toolCategory = itemName } Activity.selectTool(itemName) foldAnimation.start() } } } GCText { text: itemName width: root.tabWidth anchors.horizontalCenter: img.horizontalCenter horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.top: img.bottom wrapMode: Text.WordWrap fontSize: tinySize color: "white" } } } GridView { id: colorGrid width: parent.width * 0.75 height: parent.height * 0.80 anchors.left: selectedColor.right anchors.verticalCenter: mainPanel.verticalCenter anchors.leftMargin: 30 anchors.rightMargin: 10 anchors.topMargin: 10 cellWidth: width / 4.7 cellHeight: height / 3.6 interactive: false model: colorModel visible: root.activePanel == "colorPanel" z: 1800 delegate: Rectangle { id: root1 radius: 8 width: colorGrid.cellWidth * 0.80 height: colorGrid.cellHeight * 0.90 color: modelData border.width: items.activeColorIndex === index ? "3" : "1" border.color: "#eeeeee" scale: items.activeColorIndex === index ? 1.1 : 1 MouseArea { anchors.fill: parent // set this color as current paint color onClicked: { items.activeColorIndex = index items.paintColor = root1.color colorPicker.updateColor((items.paintColor).toString()) root.colorUpdate() - background.reloadSelectedPen() foldAnimation.start() } } } } ColorDialog { id: colorPicker anchors.left: mainPanel.left anchors.verticalCenter: mainPanel.verticalCenter visible: colorGrid.visible anchors.leftMargin: 20 onColorChanged: { root.colorUpdate() } } Rectangle { id: selectedColor width: mainPanel.width * 0.08 height: mainPanel.height * 0.30 visible: colorGrid.visible radius: 8 border.width: 3 border.color: "#eeeeee" z: colorGrid.z anchors.left: colorPicker.right anchors.leftMargin: 10 anchors.bottom: colorGrid.bottom anchors.bottomMargin: 30 color: colorPicker.currentColorCode MouseArea { anchors.fill: parent onClicked: { items.paintColor = selectedColor.color animTarget = colorsTitle foldAnimation.start() } } } Button { style: GCButtonStyle { theme: "light" } text: qsTr("Save") width: selectedColor.width anchors.left: selectedColor.left anchors.bottomMargin: 30 visible: colorGrid.visible anchors.bottom: selectedColor.top onClicked: { root.colorModel.remove(items.activeColorIndex) root.colorModel.insert(items.activeColorIndex, {colorCode: (colorPicker.currentColor()).toString()}) items.paintColor = (colorPicker.currentColor()).toString() foldAnimation.start() } } ToolsMode { id: toolsMode visible: root.activePanel == "toolOptions" } } Rectangle { width: root.tabWidth height: 8 x: animTarget.x y: animTarget.y color: panelColor } } diff --git a/src/activities/drawing/ToolItem.qml b/src/activities/drawing/ToolItem.qml index 9bacee71b..327562a99 100644 --- a/src/activities/drawing/ToolItem.qml +++ b/src/activities/drawing/ToolItem.qml @@ -1,53 +1,47 @@ /* GCompris - ToolItem.qml * * Copyright (C) 2016 Toncu Stefan * * 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 "drawing.js" as Activity Image { id: toolItem sourceSize.width: 70; sourceSize.height: 70 width: 70; height: 70 source: Activity.url + name + ".svg" opacity: items.toolSelected == name ? 1 : 0.4 property string name signal click MouseArea { anchors.fill: parent onClicked: { items.toolSelected = name items.lastToolSelected = name showSelected.x = toolItem.x + row2.spacing showSelected.y = toolItem.y + row2.spacing * 0.5 // make the hover over the canvas false area.hoverEnabled = false - // change the selectBrush tool - timer.index = 0 - timer.start() - - background.reloadSelectedPen() - click() } } } diff --git a/src/activities/drawing/ToolsMode.qml b/src/activities/drawing/ToolsMode.qml index 8d306b56d..8bf811cef 100644 --- a/src/activities/drawing/ToolsMode.qml +++ b/src/activities/drawing/ToolsMode.qml @@ -1,182 +1,178 @@ /* GCompris - ToolsMode.qml * * Copyright (C) 2018 Amit Sagtani * * 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 import "../../core" import "drawing.js" as Activity Item { id: toolsMode width: parent.width height: parent.height anchors.centerIn: parent property alias modesModel: modes.model property alias pencilModes: pencilModes property alias geometricModes: geometricModes property alias stampsModel: stampsModel property alias opacitySliderValue: opacitySlider.value property string activeStampImageSource: "qrc:/gcompris/src/activities/solar_system/resource/sun_clip.svg" property real activeStampDimensionRatio: 1.0 property real activeStampHeight: 180.0 property real activeStampWidth: activeStampHeight //* activeStampDimensionRatio ListModel { id: pencilModes ListElement { name: "pencil"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/pencil.png" } ListElement { name: "dot"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/pattern1.png" } ListElement { name: "pattern2"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/pattern3.png" } ListElement { name: "pattern3"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/pattern3.png" } ListElement { name: "spray"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/spray.png" } ListElement { name: "brush3"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/brush3.png" } ListElement { name: "brush4"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/brush4.png" } ListElement { name: "brush5"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/brush5.png" } ListElement { name: "blur"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/blur.png" } } ListModel { id: geometricModes ListElement { name: "rectangle"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/rectangle.png" } ListElement { name: "circle"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/circle.png" } ListElement { name: "lineShift"; imgSource: "qrc:/gcompris/src/activities/drawing/resource/line_straight.svg" } ListElement { name: "line" imgSource: "qrc:/gcompris/src/activities/drawing/resource/line_free.png" } } ListModel { id: stampsModel ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/sun_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/sun_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/mercury_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/mercury_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/venus_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/venus_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/earth_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/earth_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/mars_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/mars_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/jupiter_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/jupiter_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/saturn_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/saturn_real.png" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/uranus_clip.svg"} ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/uranus_real.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/neptune_clip.svg" } ListElement { imgSource: "qrc:/gcompris/src/activities/solar_system/resource/neptune_real.svg" } } GridView { id: modes width: foldablePanels.mainPanel.width * 0.50 height: background.height * 0.30 cellWidth: width / 4 cellHeight: height / 3 anchors.top: parent.top anchors.topMargin: 15 anchors.left: parent.left anchors.leftMargin: 20 model: pencilModes delegate: modesComponent } Component { id: modesComponent Rectangle { width: modes.cellWidth height: modes.cellHeight color: (modes.model !== stampsModel && items.toolSelected == name) ? "lightblue" : "transparent" radius: 10 Image { source: imgSource width: parent.width * 0.80 height: parent.height * 0.80 anchors.centerIn: parent fillMode: Image.PreserveAspectFit } MouseArea { anchors.fill: parent onClicked: { if(modes.model !== stampsModel) { items.toolSelected = name items.lastToolSelected = name console.log("Click on " + name) - // change the selectBrush tool - timer.index = 0 - timer.start() - background.reloadSelectedPen() Activity.selectMode(name) } else if(modes.model === stampsModel) { activeStampImageSource = imgSource foldablePanels.foldAnimation.start() } } } } } ToolsSize { id: toolsTipSize anchors.left: modes.right anchors.top: parent.top anchors.topMargin: items.toolSelected === "stamp" ? 100 : 30 anchors.leftMargin: 30 } GCSlider { id: opacitySlider width: toolsTipSize.width anchors.top: toolsTipSize.bottom anchors.horizontalCenter: toolsTipSize.horizontalCenter anchors.topMargin: 30 value: 1 minimumValue: 0 maximumValue: 1 stepSize: 0.1 onValueChanged: { items.globalOpacityValue = value } } GCText { width: background.width > background.height ? opacitySlider.width / 3 : opacitySlider.width / 2 anchors.horizontalCenter: opacitySlider.horizontalCenter horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.top: opacitySlider.bottom anchors.topMargin: 10 fontSize: tinySize color: "white" text: qsTr("Opacity") } } diff --git a/src/activities/drawing/drawing.js b/src/activities/drawing/drawing.js index 713861e7e..dc4ab867a 100644 --- a/src/activities/drawing/drawing.js +++ b/src/activities/drawing/drawing.js @@ -1,591 +1,577 @@ /* GCompris - drawing.js * * Copyright (C) 2016 Toncu Stefan * 2018 Amit Sagtani * 2019 Timothée Giet * * Authors: * Stefan Toncu * Amit Sagtani * Timothée Giet * * 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 . */ .pragma library .import QtQuick 2.0 as Quick .import GCompris 1.0 as GCompris .import "qrc:/gcompris/src/core/core.js" as Core var url = "qrc:/gcompris/src/activities/drawing/resource/" var currentLevel = 0 var numberOfLevel = 4 var items var loadImagesSource = [ "qrc:/gcompris/src/activities/photo_hunter/resource/photo1.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo2.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo3.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo4.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo5.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo6.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo7.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo8.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo9.svg", "qrc:/gcompris/src/activities/photo_hunter/resource/photo10.svg" ] var undo = [] var undoWidth = [] var undoHeight = [] var redo = [] var redoWidth = [] var redoHeight = [] var userFile = "file://" + GCompris.ApplicationInfo.getSharedWritablePath() + "/paint/" + "levels-user.json" var dataset = null var ctx var points = [] var connectedPoints = [] function start(items_) { items = items_ currentLevel = 0 items.toolSelected = "pencil" initLevel() items.foldablePanels.colorUpdate() } function stop() { } function initLevel() { dataset = null points = [] connectedPoints = [] undo = [] undoWidth = [] undoHeight = [] redo = [] redoWidth = [] redoHeight = [] //ctx = items.canvas.getContext("2d") //load saved paintings from file parseImageSaved() items.gridView2.model = dataset getPattern() items.background.started = true resetCanvas() //add empty undo item to restore empty canvas undo = undo.concat(items.canvas.toDataURL()) undoWidth = undoWidth.concat(items.canvas.width) undoHeight = undoHeight.concat(items.canvas.height) } function resetCanvas() { // clear all drawings from the board ctx = items.canvas.getContext("2d"); ctx.globalAlpha = 1; ctx.fillStyle = items.backgroundColor; ctx.fillRect(0, 0, items.canvas.width, items.canvas.height); items.canvas.requestPaint(); } function preserveImage() { // if the width/height is changed, the drawing is reset and repainted the last image saved ctx = items.canvas.getContext("2d"); ctx.globalAlpha = 1; ctx.fillStyle = items.backgroundColor; ctx.fillRect(0, 0, items.background.width, items.background.height); ctx.drawImage(items.urlImage, 0, 0); items.canvas.requestPaint() } function getPattern() { var dotWidth = 20, dotDistance = 5 var patternCtx = items.shape.getContext("2d") patternCtx.clearRect(0, 0, items.shape.width, items.shape.height) items.shape.width = items.shape.height = dotWidth + dotDistance patternCtx.fillStyle = items.paintColor patternCtx.beginPath() patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); patternCtx.closePath() patternCtx.fill() items.shape.requestPaint() } function getPattern2() { var lineSize = 10, lineWidth = 5 var patternCtx = items.shape.getContext("2d") patternCtx.clearRect(0, 0, items.shape.width, items.shape.height) items.shape.width = items.shape.height = lineSize patternCtx.strokeStyle = items.paintColor patternCtx.lineWidth = lineWidth patternCtx.beginPath() patternCtx.moveTo(0, lineWidth) patternCtx.lineTo(lineSize, lineWidth) patternCtx.closePath() patternCtx.stroke() } function getPattern3() { var lineSize = 20, lineWidth = 10 var ctx = items.shape.getContext("2d") ctx.clearRect(0, 0, items.shape.width, items.shape.height) items.shape.width = lineWidth; items.shape.height = lineSize; ctx.fillStyle = items.paintColor ctx.fillRect(lineWidth / 2, 0, lineWidth, lineSize); } function getSprayPattern() { var patternCtx = items.shape.getContext("2d") patternCtx.clearRect(0, 0, items.shape.width, items.shape.height) items.shape.width = items.shape.height = 3 patternCtx.fillStyle = items.paintColor patternCtx.fillRect(0, 0, 2, 2); } function getCirclePattern() { var dotWidth = 10, dotDistance = 2.5 var patternCtx = items.shape.getContext("2d") patternCtx.clearRect(0, 0, items.shape.width, items.shape.height) items.shape.width = dotWidth * 0.6 + dotDistance * 2 items.shape.height = items.shape.width patternCtx.strokeStyle = items.paintColor patternCtx.lineWidth = 1 patternCtx.beginPath() patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); patternCtx.closePath() patternCtx.stroke() } // parse the content of the paintings saved by the user function parseImageSaved() { dataset = items.parser.parseFromUrl(userFile) if (dataset == null) { console.error("ERROR! dataset = []") dataset = [] return } } // if showMessage === true, then show the message Core.showMessageDialog(...), else don't show it function saveToFile(showMessage) { // verify if the path is good var path = userFile.substring(0, userFile.lastIndexOf("/")) if (!items.file.exists(path)) { if (!items.file.mkpath(path)) console.error("Could not create directory " + path) else console.debug("Created directory " + path) } // add current painting to the dataset if (showMessage) dataset = dataset.concat({"imageNumber": 1, "url": items.canvas.toDataURL()}) // save the dataset to json file if (!items.file.write(JSON.stringify(dataset), userFile)) { if (showMessage) Core.showMessageDialog(items.main, //~ singular Error saving %n level to your levels file (%1) //~ plural Error saving %n levels to your levels file (%1) qsTr("Error saving %n level(s) to your levels file (%1)", "", numberOfLevel) .arg(userFile), "", null, "", null, null) } else { if (showMessage) Core.showMessageDialog(items.main, //~ singular Saved %n level to your levels file (%1) //~ plural Saved %n levels to your levels file (%1) qsTr("Saved %n level(s) to your levels file (%1)", "", numberOfLevel) .arg(userFile), "", null, "", null, null) } items.initSave = false //reload the dataset: parseImageSaved() items.gridView2.model = dataset // reset nothingChanged items.nothingChanged = true } function handleKeyNavigations(event) { if(event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Z) { selectTool("Undo") } else if(event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Y) { selectTool("Redo") } else if(event.modifiers === Qt.ControlModifier && event.key === Qt.Key_N) { selectTool("Erase all") } else if(event.modifiers === Qt.ControlModifier && event.key === Qt.Key_S) { selectTool("Save") } else if(event.modifiers === Qt.ControlModifier && event.key === Qt.Key_O) { selectTool("Load") } } // Exports the current drawing in png format. function exportToPng() { var path = GCompris.ApplicationInfo.getSharedWritablePath() + "/drawing" if(!items.file.exists(path)) { if(!items.file.mkpath(path)) { Core.showMessageDialog(items.main, qsTr("Error: could not create the directory %1").arg(path), "", null, "", null, null) console.error("Could not create directory " + path) return; } else console.debug("Created directory " + path) } var i = 0; while(items.file.exists(path + "/drawing" + i.toString() + ".png")) { i += 1 } items.canvas.grabToImage(function(result) { if(result.saveToFile(path + "/drawing" + i.toString() + ".png")) { console.log("File drawing" + i + ".png saved successfully.") Core.showMessageDialog(items.main, qsTr("Saved drawing to %1").arg(path + "/drawing" + i.toString() + ".png"), "", null, "", null, null) } else { Core.showMessageDialog(items.main, qsTr("Error in saving the drawing."), "", null, "", null, null) } }) } function nextLevel() { if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } initLevel() } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel() } function pushToUndo() { // push the state of the current board on UNDO stack items.urlImage = items.canvas.toDataURL() undo = undo.concat(items.urlImage) undoWidth = undoWidth.concat(items.canvas.width) undoHeight = undoHeight.concat(items.canvas.height) } function resetRedo() { if (redo.length > 0) { print("resetting redo array!") redo = [] redoWidth = [] redoHeight = [] } } function undoAction() { if(undo.length > 1) { items.undoLock = true redo = redo.concat(undo.pop()) redoWidth = redoWidth.concat(undoWidth.pop()) redoHeight = redoHeight.concat(undoHeight.pop()) items.urlImage = undo[undo.length - 1] items.urlImageWidth = undoWidth[undoWidth.length - 1] items.urlImageHeight = undoHeight[undoHeight.length -1] items.canvas.ctx.globalCompositeOperation = 'source-over' ctx = items.canvas.getContext("2d") //always set alpha to 1 ctx.globalAlpha = 1 ctx.fillStyle = items.backgroundColor ctx.fillRect(0, 0, items.canvas.width, items.canvas.height) ctx.drawImage(items.urlImage, 0, 0, items.urlImageWidth, items.urlImageHeight, 0, 0, items.urlImageWidth, items.urlImageHeight) items.canvas.requestPaint() console.log("undo length: " + undo.length) } else { console.log("Undo array Empty!") } } function redoAction() { if(redo.length > 0) { items.undoLock = true items.urlImage = redo.pop() items.urlImageWidth = redoWidth.pop() items.urlImageHeight = redoHeight.pop() ctx = items.canvas.getContext("2d") //always set alpha to 1 ctx.globalAlpha = 1 ctx.drawImage(items.urlImage, 0, 0, items.urlImageWidth, items.urlImageHeight, 0, 0, items.urlImageWidth, items.urlImageHeight) undo = undo.concat(items.urlImage) undoWidth = undoWidth.concat(items.urlImageWidth) undoHeight = undoHeight.concat(items.urlImageHeight) items.canvas.requestPaint() console.log("undo length: " + undo.length) } else { console.log("Redo array Empty!") } } function selectTool(toolName) { console.log("Clicked on " + toolName) items.paintColor = items.selectedColor items.eraserMode = false - items.timer.stop() if(toolName === "Eraser") { items.eraserMode = true items.toolSelected = "eraser" items.toolsMode.modesModel = items.toolsMode.pencilModes - items.background.reloadSelectedPen() } else if(toolName === "Bucket fill") { items.toolSelected = "fill" - - // change the selectBrush tool - items.timer.index = 0 - items.timer.start() - - items.background.reloadSelectedPen() } else if(toolName === "Text") { items.toolSelected = "text" - items.background.reloadSelectedPen() // make visible the inputTextFrame items.inputTextFrame.visible = true items.inputTextFrame.enabled = true // restore input text to "" items.inputText.text = "" } else if(toolName === "Undo") { undoAction(); } else if(toolName === "Redo") { redoAction(); } else if(toolName === "Load") { if (items.load.opacity == 0) items.load.opacity = 1 // mark the pencil as the default tool items.toolSelected = "pencil" // move the main screen to right items.mainRegion.x = items.background.width } else if(toolName === "Save") { saveToFile(true) } else if(toolName === "More Colors") { items.colorPalette.visible = true } else if(toolName === "Modes") { items.toolsMode.visible = true } else if(toolName === "Size") { items.toolsSize.visible = true } else if(toolName === "Erase all") { if (!items.nothingChanged) { items.saveToFilePrompt.buttonPressed = "reload" items.saveToFilePrompt.text = qsTr("Do you want to save your painting before reseting the board?") items.saveToFilePrompt.opacity = 1 items.saveToFilePrompt.z = 200 } else { initLevel() } } else if(toolName === "Geometric") { items.toolSelected = "rectangle" items.lastToolSelected = "rectangle" - items.background.reloadSelectedPen() items.toolsMode.modesModel = items.toolsMode.geometricModes } else if(toolName === "Stamp") { items.toolSelected = "stamp" items.lastToolSelected = "stamp" items.toolsMode.modesModel = items.toolsMode.stampsModel items.stampGhostImage.z = 1500 items.stampGhostImage.x = items.area.realMouseX items.stampGhostImage.y = items.area.realMouseY items.canvas.loadImage(items.stampGhostImage.source) } else if(toolName === "Brush") { items.toolSelected = "pencil" items.lastToolSelected = "pencil" - items.background.reloadSelectedPen() items.toolsMode.modesModel = items.toolsMode.pencilModes } else if(toolName === "Export to PNG") { exportToPng() } else if(toolName === "Background color") { items.backgroundColorPalette.visible = true } } function selectMode(modeName) { if(modeName === "dot") { items.toolSelected = "pattern" items.patternType = "dot" items.lastToolSelected = "pattern" getPattern() - items.background.reloadSelectedPen() } else if(modeName === "pattern2") { items.toolSelected = "pattern" items.patternType = "horizLine" items.lastToolSelected = "pattern" getPattern2() - items.background.reloadSelectedPen() } else if(modeName === "pattern3") { items.toolSelected = "pattern" items.patternType = "vertLine" items.lastToolSelected = "pattern" getPattern3() - items.background.reloadSelectedPen() } } // Paint flood-fill algorithm(Stack based Implementation) function paintBucket() { console.log( "Flood fill started at " + new Date().toLocaleTimeString() ) items.canvas.isBucketDone = false; var reachLeft = false; var reachRight = false; var sX = parseInt(items.canvas.startX, 10) var sY = parseInt(items.canvas.startY, 10) var pixelStack = [[items.canvas.startX, items.canvas.startY]] var ctx = items.canvas.getContext('2d') var colorLayer = ctx.getImageData(0, 0,items.canvas.width, items.canvas.height) var begPixel = sX * 4 + sY * 4 * colorLayer.width var r1 = colorLayer.data[begPixel] var g1 = colorLayer.data[begPixel + 1] var b1 = colorLayer.data[begPixel + 2] if(r1 === (items.selectedColor.r * 255) && g1 === (items.selectedColor.g * 255) && b1 === (items.selectedColor.b * 255)) { items.canvas.isBucketDone = true; return; } var r2, b2, g2, newIndex, oPixel while(pixelStack.length) { var pixelToCheck = pixelStack.pop() sY = pixelToCheck[1] sX = pixelToCheck[0] begPixel = sX * 4 + sY * 4 * colorLayer.width reachLeft = false; reachRight = false; while(sY - 1 >= 0) { begPixel = sX * 4 + sY * 4 * colorLayer.width if (!((colorLayer.data[begPixel] === r1) && (colorLayer.data[begPixel + 1] === g1) && (colorLayer.data[begPixel + 2] === b1))) { break; } sY = sY - 1 } sY = sY + 1; ctx.fillRect(sX, sY, 1, 1) begPixel = sX * 4 + sY * 4 * colorLayer.width colorLayer.data[begPixel] = items.selectedColor.r * 255 colorLayer.data[begPixel + 1] = items.selectedColor.g * 255 colorLayer.data[begPixel + 2] = items.selectedColor.b * 255 colorLayer.data[begPixel + 3] = 255 while(sY + 1 < colorLayer.height) { sY = sY + 1 begPixel = sX * 4 + sY * 4 * colorLayer.width if(((colorLayer.data[begPixel ] === r1) && (colorLayer.data[begPixel + 1] === g1) && (colorLayer.data[ begPixel + 2 ] === b1))) { ctx.fillRect(sX, sY, 2, 2) colorLayer.data[begPixel] = items.selectedColor.r * 255 colorLayer.data[begPixel + 1] = items.selectedColor.g * 255 colorLayer.data[begPixel + 2] = items.selectedColor.b * 255 colorLayer.data[begPixel + 3] = 255 if(sX > 1) { oPixel = (sX - 1) * 4 + sY * 4 * colorLayer.width if((colorLayer.data[oPixel] === r1) && (colorLayer.data[oPixel + 1] === g1) && (colorLayer.data[oPixel + 2] === b1)) { if(!reachLeft) { pixelStack.push([sX -1, sY]) reachLeft = true; } } else { reachLeft = false; } } if(sX < items.canvas.width) { oPixel = (sX + 1) * 4 + sY * 4 * colorLayer.width if((colorLayer.data[oPixel ] === r1) && (colorLayer.data[oPixel + 1] === g1) && (colorLayer.data[ oPixel + 2 ] === b1)) { if (!reachRight) { pixelStack.push([sX + 1, sY]) reachRight = true; } } else { reachRight = false; } } } else { break; } } } ctx.drawImage(colorLayer, 0, 0) items.canvas.requestPaint() items.canvas.startX = -1 items.canvas.startY = -1 items.canvas.finishX = -1 items.canvas.finishY = -1 console.log( "Flood-fill completed at " + new Date().toLocaleTimeString() ) }