diff --git a/src/activities/activities.txt b/src/activities/activities.txt index c0fe20266..134c5c1da 100644 --- a/src/activities/activities.txt +++ b/src/activities/activities.txt @@ -1,148 +1,149 @@ # The list of activities that will be loaded at GCompris start. # Keep it sorted advanced_colors algebra_by algebra_div algebra_minus algebra_plus algorithm align4 align4-2players alphabet-sequence babymatch babyshapes baby_wordprocessor balancebox ballcatch bargame bargame_2players braille_alphabets braille_fun calendar canal_lock categorization checkers checkers_2players chess chess_2players chess_partyend chronos clickanddraw clickgame click_on_letter click_on_letter_up clockgame color_mix color_mix_light colors crane details digital_electricity drawletters drawnumbers enumerate erase erase_2clic erase_clic explore_farm_animals explore_monuments explore_world_animals explore_world_music family family_find_relative fifteen find_the_day followline football geo-country geography gletters gnumch-equality gnumch-factors gnumch-inequality gnumch-multiples gnumch-primes graph-coloring guesscount guessnumber hangman hanoi hanoi_real hexagon imagename instruments intro_gravity land_safe lang leftright letter-in-word lightsoff louis-braille magic-hat-minus magic-hat-plus maze mazeinvisible mazerelative melody memory memory-case-association memory-case-association-tux memory-enumerate memory-math-add memory-math-add-minus memory-math-add-minus-mult-div memory-math-add-minus-mult-div-tux memory-math-add-minus-tux memory-math-add-tux memory-math-div memory-math-div-tux memory-math-minus memory-math-minus-tux memory-math-mult memory-math-mult-div memory-math-mult-div-tux memory-math-mult-tux memory-sound memory-sound-tux memory-tux memory-wordnumber mining missing-letter money money_back money_back_cents money_cents mosaic nine_men_morris nine_men_morris_2players number_sequence numbers-odd-even +paint paintings penalty photo_hunter planegame railroad readingh readingv redraw redraw_symmetrical renewable_energy reversecount roman_numerals scalesboard scalesboard_weight scalesboard_weight_avoirdupois share simplepaint smallnumbers smallnumbers2 submarine sudoku superbrain tangram target tic_tac_toe tic_tac_toe_2players traffic watercycle wordsgame diff --git a/src/activities/paint/ActivityInfo.qml b/src/activities/paint/ActivityInfo.qml new file mode 100644 index 000000000..1ac1adcdd --- /dev/null +++ b/src/activities/paint/ActivityInfo.qml @@ -0,0 +1,34 @@ +/* GCompris - ActivityInfo.qml + * + * Copyright (C) 2015 Your Name + * + * 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 GCompris 1.0 + +ActivityInfo { + name: "paint/Paint.qml" + difficulty: 1 + icon: "paint/paint.svg" + author: "Your Name <yy@zz.org>" + demo: true + title: "Tuxpaint" + description: "" + //intro: "put here in comment the text for the intro voice" + goal: "" + prerequisite: "" + manual: "" + credit: "" + section: "fun" +} diff --git a/src/activities/paint/CMakeLists.txt b/src/activities/paint/CMakeLists.txt new file mode 100644 index 000000000..f392f4c5e --- /dev/null +++ b/src/activities/paint/CMakeLists.txt @@ -0,0 +1 @@ +GCOMPRIS_ADD_RCC(activities/paint *.qml *.svg *.js resource/*) diff --git a/src/activities/paint/Expand.qml b/src/activities/paint/Expand.qml new file mode 100644 index 000000000..a0fd974c6 --- /dev/null +++ b/src/activities/paint/Expand.qml @@ -0,0 +1,80 @@ +/* GCompris - Expand.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.1 + +Rectangle { + id: expand + + property int sizeS + + anchors { + leftMargin: 10 + left: parent.right + } + + opacity: 0 + + width: 245 + height: 55 + border.color: "black" + border.width: 4 + color: "black" + + + Flow { + id: group + width: 230 + height: 50 + anchors.centerIn: parent + + spacing: 10 + + Repeater { + id: repeater + model: 4 +// anchors.centerIn: parent + + Rectangle { + + width: 50 + height: 50 + color: "grey" + + Rectangle { + id: option + width: index * 10 + 20 + height: 50 + anchors.centerIn: parent + color: "green" + } + + MouseArea { + anchors.fill: parent + + onClicked: { + //code + colorTools.z = 0 + tool.opac = 0 + expand.sizeS = index + 1 + } + } + } + } + } +} diff --git a/src/activities/paint/Paint.qml b/src/activities/paint/Paint.qml new file mode 100644 index 000000000..920fdd1fa --- /dev/null +++ b/src/activities/paint/Paint.qml @@ -0,0 +1,2027 @@ +/* GCompris - paint.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.2 +import GCompris 1.0 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.0 +import "../../core" +import "paint.js" as Activity +import "qrc:/gcompris/src/core/core.js" as Core + +// TODO1: undo/redo + +// TODO2: add size to Text Tool + +// 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: "lightblue" + 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) { + items.widthHeightChanged = true + Activity.initLevel() + } + } + onHeightChanged: { + if (items.background.started) { + items.widthHeightChanged = true + Activity.initLevel() + } + } + + 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() + + brushSelectCanvas.requestPaint() + + } + + } + + SaveToFilePrompt { + id: saveToFilePrompt + z: -1 + + onYes: { + Activity.saveToFile(true) + if (main.x == 0) + load.opacity = 0 + activity.home() + } + onNo: { + if (main.x == 0) + load.opacity = 0 + activity.home() + } + onCancel: { + saveToFilePrompt.z = -1 + saveToFilePrompt.opacity = 0 + main.opacity = 1 + } + } + + SaveToFilePrompt { + id: saveToFilePrompt2 + z: -1 + + onYes: { + cancel() + Activity.saveToFile(true) + Activity.initLevel() + } + onNo: { + cancel() + Activity.initLevel() + } + onCancel: { + saveToFilePrompt2.z = -1 + saveToFilePrompt2.opacity = 0 + main.opacity = 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 int sizeS: 2 + property color paintColor + property string toolSelected: "pencil" + property alias colorTools: colorTools + property alias rightPannel: rightPannel + property var urlImage + property bool next: false + property bool next2: false + property bool loadSavedImage: false + property alias file: file + property bool initSave: false + property alias parser: parser + property alias gridView2: gridView2 + property bool mainAnimationOnX: true + property bool undoRedo: false + property int index: 0 + property string patternType: "dot.jpg" + property bool nothingChanged: true + property string lastUrl + property bool widthHeightChanged: false + property alias shape: shape + property string lastToolSelected: "pencil" + property alias brushSelectCanvas: brushSelectCanvas + } + + JsonParser { + id: parser + onError: console.error("Paint: Error parsing JSON: " + msg); + } + + onStart: { Activity.start(items) } + onStop: { Activity.stop() } + + DialogHelp { + id: dialogHelp + onClose: home() + } + + Bar { + id: bar + content: BarEnumContent { value: help | home | reload } + onHelpClicked: { + displayDialog(dialogHelp) + } + onHomeClicked: { + if (!items.nothingChanged) { + saveToFilePrompt.text = "Do you want to save your painting?" + main.opacity = 0.5 + saveToFilePrompt.opacity = 1 + saveToFilePrompt.z = 200 + } else { + if (main.x == 0) + load.opacity = 0 + activity.home() + } + } + onReloadClicked: { + if (!items.nothingChanged) { + saveToFilePrompt2.text = "Do you want to save your painting before reseting the board?" + main.opacity = 0.5 + saveToFilePrompt2.opacity = 1 + saveToFilePrompt2.z = 200 + } else { + Activity.initLevel() + } + } + } + + Bonus { + id: bonus + Component.onCompleted: win.connect(Activity.nextLevel) + } + + Keys.onPressed: { + if (event.key == Qt.Key_Escape) { + if (main.x == 0) + load.opacity = 0 + } + } + + function hideExpandedTools () { + selectSize.z = -1 + selectSize.opacity = 0 + + selectBrush.z = -1 + selectBrush.opacity = 0 + + // hide the inputTextFrame + inputTextFrame.opacity = 0 + inputTextFrame.z = -1 + inputText.text = "" + } + +// function changeSelectedCoord(object) { +// var modified = object.mapToItem(background,object.x,object.y) +// selected.x = modified.x - 1 +// selected.y = object.y + 9 + colorTools.height + colorTools.anchors.margins // + 9 because of the margins (10) +// } + + Rectangle { + id: main + width: parent.width + height: parent.height + + color: "lightblue" + + + Behavior on x { + enabled: items.mainAnimationOnX + NumberAnimation { + target: main + property: "x" + duration: 800 + easing.type: Easing.InOutQuad + } +// PropertyAction { +// target: main; property: "opacity"; value: main.x == - background.width ? 0 : 1 +// } + } + + Behavior on y { + NumberAnimation { + target: main + property: "y" + duration: 800 + easing.type: Easing.InOutQuad + } + } + + +// Rectangle { +// id: selected +// color: "#ffb3ff" +// z: rightPannelFrame.z +// width: 50; height: 50 +// opacity: 0.8 +// } + + Rectangle { + id: inputTextFrame + color: background.color + width: inputText.width + okButton.width + inputText.height + 10 + height: inputText.height * 1.1 + anchors.centerIn: parent + radius: height / 2 + z: 1000 + opacity: 0 + + TextField { + id: inputText + anchors.left: parent.left + anchors.leftMargin: height / 1.9 + anchors.verticalCenter: parent.verticalCenter + height: 50 + width: 300 + placeholderText: qsTr("Type here") + font.pointSize: 32 + } + + //ok button + Image { + id: okButton + source:"qrc:/gcompris/src/core/resource/bar_ok.svg" + sourceSize.height: inputText.height + fillMode: Image.PreserveAspectFit + anchors.left: inputText.right + anchors.leftMargin: 10 + anchors.verticalCenter: inputText.verticalCenter + + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: inputTextFrame.opacity == 1 ? true : false + onClicked: { + onBoardText.text = inputText.text + // hide the inputTextFrame + inputTextFrame.opacity = 0 + inputTextFrame.z = -1 + + // show the text + onBoardText.opacity = 1 + onBoardText.z = 100 + + onBoardText.x = area.realMouseX + onBoardText.y = area.realMouseY - onBoardText.height * 0.8 + + // start the movement + moveOnBoardText.start() + } + } + } + } + + + Rectangle { + id: canvasBackground + z: 1 + anchors.fill: parent + anchors.margins: 8 + + color: "green" + + Canvas { + id: canvas + anchors.fill: parent + + property real lastX + property real lastY + + + // for brush2 + property var lastPoint + property var currentPoint + + property var ctx + property string url: "" + + Text { + id: onBoardText + text: "" + color: items.paintColor + font.family: "sans-serif" + // font.pointSize: (ApplicationSettings.baseFontSize + 32) * ApplicationInfo.fontRatio + font.pointSize: items.sizeS * 10 + z: -1 + opacity: 0 + } + + + function clearCanvas() { + // clear all drawings from the board + var ctx = getContext('2d') + ctx.beginPath() + ctx.clearRect(0, 0, canvasBackground.width, canvasBackground.height); + + paintWhite() + canvas.ctx.strokeStyle = "#ffffff" + } + + function paintWhite() { + print("painted canvas in white") + canvas.ctx = getContext("2d") + canvas.ctx.fillStyle = "#ffffff" + canvas.ctx.beginPath() + canvas.ctx.moveTo(0, 0) + canvas.ctx.lineTo(background.width, 0) + canvas.ctx.lineTo(background.width, background.height) + canvas.ctx.lineTo(0, background.height) + canvas.ctx.closePath() + canvas.ctx.fill() + } + + onImageLoaded: { + // load images from files + if (canvas.url != "") { + print("url != vid ") + canvas.clearCanvas() + + if (items.loadSavedImage) { + canvas.ctx.drawImage(canvas.url, 0, 0, canvas.width, canvas.height) + } else { + canvas.ctx.drawImage(canvas.url, canvas.width / 2 - canvas.height / 2, 0, canvas.height, canvas.height) + } + + // mark the loadSavedImage as finished + items.loadSavedImage = false + requestPaint() + items.toolSelected = "" + print("requestPaint onImageLoaded from FILE " + items.toolSelected) + items.lastUrl = canvas.url + unloadImage(canvas.url) + items.mainAnimationOnX = true + canvas.url = "" + + // undo and redo + } else if (items.undoRedo) { + ctx.drawImage(items.urlImage,0,0) + requestPaint() + print("requestPaint onImageLoaded UNDO REDO ") + items.lastUrl = canvas.url + unloadImage(items.urlImage) + items.undoRedo = false + } + } + + 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; + } + + onPaint: { + + if (items.toolSelected == "pattern") { + if (items.patternType == "dot.jpg") + Activity.getPattern() + if (items.patternType == "pattern2.png") + Activity.getPattern2() + if (items.patternType == "pattern3") + Activity.getPattern3() + } + + canvas.ctx = getContext('2d') + canvas.ctx.strokeStyle = items.toolSelected == "eraser" ? "#ffffff" : +// items.toolSelected == "pattern" ? ctx.createPattern(Activity.url + items.patternType, 'repeat') : + items.toolSelected == "pattern" ? ctx.createPattern(shape.toDataURL(), 'repeat') : + items.toolSelected == "brush4" ? "black" : + items.paintColor + + // remove the shadow effect + canvas.ctx.shadowColor = 'rgba(0,0,0,0)' + canvas.ctx.shadowBlur = 0 + canvas.ctx.shadowOffsetX = 0 + canvas.ctx.shadowOffsetY = 0 + +// print("items.toool ------------- ",items.toolSelected) + if (items.toolSelected == "pencil" || items.toolSelected == "eraser") { + canvas.ctx.lineWidth = items.toolSelected == "eraser" ? items.sizeS * 4 : items.sizeS + canvas.ctx.lineCap = 'round' + canvas.ctx.lineJoin = 'round' + + canvas.ctx.beginPath() + ctx.moveTo(lastX, lastY) + lastX = area.mouseX + lastY = area.mouseY + ctx.lineTo(lastX, lastY) + ctx.stroke() + } else if (items.toolSelected == "rectangle" || items.toolSelected == "lineShift") { + var itemm = area.currentShape +// canvas.ctx = getContext("2d") + canvas.ctx.fillStyle = items.paintColor + canvas.ctx.beginPath() + canvas.ctx.moveTo(itemm.x,itemm.y) + canvas.ctx.lineTo(itemm.x + itemm.width,itemm.y) + canvas.ctx.lineTo(itemm.x + itemm.width,itemm.y + itemm.height) + canvas.ctx.lineTo(itemm.x,itemm.y + itemm.height) + canvas.ctx.closePath() + canvas.ctx.fill() + resetShape() + } else if (items.toolSelected == "circle") { + var itemm = area.currentShape + canvas.ctx = canvas.getContext('2d') + + canvas.ctx.beginPath(); + canvas.ctx.arc(itemm.x + itemm.width / 2, itemm.y + itemm.width / 2, + itemm.width / 2, 0, 2 * Math.PI, false); + canvas.ctx.fillStyle = items.paintColor + canvas.ctx.fill(); + resetShape() + } else if (items.toolSelected == "line") { + var itemm = area.currentShape + 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(itemm.x,itemm.y) + canvas.ctx.lineTo(area.endX,area.endY) + canvas.ctx.lineTo(area.endX + auxX,area.endY + auxY) + canvas.ctx.lineTo(itemm.x + auxX,itemm.y + auxY) + canvas.ctx.closePath() + canvas.ctx.fill() + + resetShape() + } else if (items.toolSelected == "fill") { + canvas.ctx.fillStyle = items.paintColor + canvas.ctx.beginPath() + canvas.ctx.moveTo(0, 0) + canvas.ctx.lineTo(background.width, 0) + canvas.ctx.lineTo(background.width, background.height) + canvas.ctx.lineTo(0, background.height) + canvas.ctx.closePath() + canvas.ctx.fill() + } else if (items.toolSelected == "text") { + canvas.ctx.fillStyle = items.paintColor +// canvas.ctx.font = "" + onBoardText.fontSize + "px " + GCSingletonFontLoader.fontLoader.name + canvas.ctx.font = items.sizeS * 10 + "pt sans-serif" + canvas.ctx.fillText(onBoardText.text,area.realMouseX,area.realMouseY) + onBoardText.text = "" + } else if (items.toolSelected == "tools" ) { + canvas.ctx.lineWidth = items.sizeS + ctx.lineJoin = ctx.lineCap = 'round' + + var p1 = Activity.points[0] + var p2 = Activity.points[1] + + if (!p1 || !p2) + return + + ctx.beginPath() + ctx.moveTo(p1.x, p1.y) + + for (var i = 1, len = Activity.points.length; i < len; i++) { + var midPoint = midPointBtw(p1, p2) + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y) + p1 = Activity.points[i] + p2 = Activity.points[i+1] + } + ctx.lineTo(p1.x, p1.y) + ctx.stroke() + } else if (items.toolSelected == "brush2" ) { + ctx.lineJoin = ctx.lineCap = 'round' + + var dist = distanceBetween(lastPoint, currentPoint) + var angle = angleBetween(lastPoint, currentPoint) + + for (var i = 0; i < dist; i++) { + var xx = lastPoint.x + (Math.sin(angle) * i) - 25; + var yy = lastPoint.y + (Math.cos(angle) * i) - 25; + ctx.drawImage(Activity.url + "brush2.png", xx, yy, items.sizeS * 5, items.sizeS * 10); + } + + lastPoint = {x: currentPoint.x, y: currentPoint.y} + } else if (items.toolSelected == "pattern" ) { + ctx.lineWidth = items.sizeS * 5 + ctx.lineJoin = ctx.lineCap = 'round' + + var p1 = Activity.points[0] + var p2 = Activity.points[1] + + if (!p1 || !p2) + return + + ctx.beginPath() + ctx.moveTo(p1.x, p1.y) + + for (var i = 1, len = Activity.points.length; i < len; i++) { + var midPoint = midPointBtw(p1, p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + p1 = Activity.points[i]; + p2 = Activity.points[i+1]; + } + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + } else if (items.toolSelected == "spray" ) { + ctx.lineWidth = items.sizeS * 5 + ctx.lineJoin = ctx.lineCap = 'round' + ctx.moveTo(canvas.lastX, canvas.lastY) + ctx.fillStyle = items.paintColor + + for (var i = 50; i--; i >= 0) { + var radius = items.sizeS * 5; + var offsetX = getRandomInt(-radius, radius); + var offsetY = getRandomInt(-radius, radius); + ctx.fillRect(canvas.lastX + offsetX, canvas.lastY + offsetY, 1, 1); + } + } else if (items.toolSelected == "brush3" ) { + ctx.lineWidth = items.sizeS * 1.2 + ctx.lineJoin = ctx.lineCap = 'round'; + + ctx.beginPath(); + + ctx.globalAlpha = 1; + ctx.moveTo(lastPoint.x, lastPoint.y); + ctx.lineTo(canvas.lastX, canvas.lastY); + ctx.stroke(); + + ctx.moveTo(lastPoint.x - 3, lastPoint.y - 3); + ctx.lineTo(canvas.lastX - 3, canvas.lastY - 3); + ctx.stroke(); + + ctx.moveTo(lastPoint.x - 2, lastPoint.y - 2); + ctx.lineTo(canvas.lastX - 2, canvas.lastY - 2); + ctx.stroke(); + + ctx.moveTo(lastPoint.x + 2, lastPoint.y + 2); + ctx.lineTo(canvas.lastX + 2, canvas.lastY + 2); + ctx.stroke(); + + ctx.moveTo(lastPoint.x + 3, lastPoint.y + 3); + ctx.lineTo(canvas.lastX + 3, canvas.lastY + 3); + ctx.stroke(); + + lastPoint = { x: canvas.lastX, y: canvas.lastY }; + + } else if (items.toolSelected == "brush4"){ + ctx.lineJoin = ctx.lineCap = 'round' + ctx.fillStyle = items.paintColor + ctx.lineWidth = items.sizeS / 4 + for (var i = 0; i < Activity.points.length; i++) { + ctx.beginPath(); + ctx.arc(Activity.points[i].x, Activity.points[i].y, 5 * items.sizeS, 0, Math.PI * 2, false); + ctx.fill(); + ctx.stroke(); + } + } else if (items.toolSelected == "brush5"){ + ctx.lineJoin = ctx.lineCap = 'round'; + ctx.lineWidth = 1 + + var p1 = Activity.connectedPoints[0] + var p2 = Activity.connectedPoints[1] + + if (!p1 || !p2) + return + + ctx.beginPath() + ctx.moveTo(p1.x, p1.y) + + for (var i = 1, len = Activity.connectedPoints.length; i < len; i++) { + var midPoint = midPointBtw(p1, p2) + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y) + p1 = Activity.connectedPoints[i] + p2 = Activity.connectedPoints[i+1] + } + ctx.lineTo(p1.x, p1.y) + 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) { + ctx.beginPath(); + ctx.strokeStyle = 'rgba(0,0,0,0.8)'; + ctx.moveTo( Activity.connectedPoints[Activity.connectedPoints.length-1].x + (dx * 0.1), + Activity.connectedPoints[Activity.connectedPoints.length-1].y + (dy * 0.1)); + ctx.lineTo( Activity.connectedPoints[i].x - (dx * 0.1), + Activity.connectedPoints[i].y - (dy * 0.1)); + ctx.stroke(); + } + } + } else if (items.toolSelected == "reset"){ + items.toolSelected = "pencil" + items.paintColor = colors[0] + } else { + print("tool not known, resetting to Pencil") + items.toolSelected = "pencil" + } + } + + MouseArea { + id: area + anchors.fill: parent + + + hoverEnabled: false + property var mappedMouse: mapToItem(parent,mouseX,mouseY) + property var currentShape: items.toolSelected == "circle" ? circle : rectangle + property var originalX + property var originalY + property real endX + property real endY + + Timer { + id: moveOnBoardText + interval: 1 + repeat: true + running: false + triggeredOnStart: { + onBoardText.x = area.realMouseX + onBoardText.y = area.realMouseY - onBoardText.height * 0.8 + } + } + + property real realMouseX: mouseX + property real realMouseY: mouseY + + onPressed: { + + if (items.nothingChanged) + items.nothingChanged = false + + background.hideExpandedTools() + mappedMouse = mapToItem(parent,mouseX,mouseY) + + print("tools: ",items.toolSelected) + + if (items.toolSelected == "rectangle" || items.toolSelected == "circle" || items.toolSelected == "lineShift") { + // 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 + } else if (items.toolSelected == "line") { + // 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 + + currentShape.height = items.sizeS + + // set the current color for the current shape + currentShape.color = items.paintColor + } else if (items.toolSelected == "text") { + canvas.lastX = mouseX + canvas.lastY = mouseY + } else if (items.toolSelected == "tools") { + Activity.points.push({x: mouseX, y: mouseY}) + } else if (items.toolSelected == "brush2") { + canvas.currentPoint = { x: mouseX, y: mouseY } + canvas.lastPoint = { x: mouseX, y: mouseY } + } else if (items.toolSelected == "pattern") { + canvas.ctx.strokeStyle = "#ffffff" // very important! + canvas.lastX = mouseX + canvas.lastY = mouseY + Activity.points.push({x: mouseX, y: mouseY}) + } else if (items.toolSelected == "spray" ) { + canvas.lastX = mouseX + canvas.lastY = mouseY + } else if (items.toolSelected == "eraser") { + canvas.lastX = mouseX + canvas.lastY = mouseY + canvas.ctx.strokeStyle = "#ffefff" + // enable the hover so the points will be closer one to the other + area.hoverEnabled = true + } else if (items.toolSelected == "pencil"){ + canvas.lastX = mouseX + canvas.lastY = mouseY + // enable the hover so the points will be closer one to the other +// area.hoverEnabled = true +// canvas.ctx.lineCap = 'butt' +// canvas.ctx.lineJoin = 'bevel' + } else if (items.toolSelected == "brush3"){ + canvas.lastX = mouseX + canvas.lastY = mouseY + canvas.lastPoint = { x: mouseX, y: mouseY } + } else if (items.toolSelected == "brush4"){ + canvas.ctx.strokeStyle = "#ffefff" + Activity.points.push({x: mouseX, y: mouseY}) + } else if (items.toolSelected == "brush5"){ + Activity.connectedPoints.push({x: mouseX, y: mouseY}) + } else if (items.toolSelected == "blur"){ + canvas.lastX = mouseX + canvas.lastY = mouseY + } else { + canvas.lastX = mouseX + canvas.lastY = mouseY + print("ON Pressed - tool not known? ") + } + } + + 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 (moveOnBoardText.running) + moveOnBoardText.stop() + + // disable hover + area.hoverEnabled = false + ///////// reset text elements + + if (items.toolSelected == "rectangle" || items.toolSelected == "circle" || + items.toolSelected == "line" || items.toolSelected == "lineShift") { + // paint the rectangle/circle + canvas.requestPaint() + } + + if (items.toolSelected == "text" && onBoardText.text != "") + canvas.requestPaint() + + if (items.toolSelected == "tools" || + items.toolSelected == "pattern" || + items.toolSelected == "brush4") + Activity.points = [] + + if (items.toolSelected == "brush5") + Activity.connectedPoints = [] + + + // push the state of the current board on UNDO stack + items.urlImage = canvas.toDataURL() + items.lastUrl = items.urlImage + Activity.undo = Activity.undo.concat(items.urlImage) + + if (Activity.redo.length != 0) { + print(" reset redo") + Activity.redo = [] + } + + if (items.toolSelected != "circle" && + items.toolSelected != "rectangle" && + items.toolSelected != "line" && + items.toolSelected != "lineShift") + items.next = true + else items.next = false + + print("undo: " + Activity.undo.length + " redo: " + Activity.redo.length) + + area.hoverEnabled = false + } + + onPositionChanged: { + if (items.toolSelected == "pencil" || items.toolSelected == "eraser") { + canvas.requestPaint() + } else if (items.toolSelected == "rectangle") { +// currentShape.width = mappedMouse.x - currentShape.x +// currentShape.height = mappedMouse.y - currentShape.y + 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) + } +// print(currentShape.height + " " + currentShape.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 +// print("===========================> angleDegrees ", angleDegrees) + } else { + angleDegrees = 360 - angleRad * 180 / Math.PI +// print("===========================> angleDegrees ", angleDegrees) + } + + currentShape.rotationn = 360 - angleDegrees + currentShape.width = distance +// print(currentShape.height + " " + currentShape.width) + } 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 + } +// print(currentShape.height + " " + currentShape.width) + } else if (items.toolSelected == "tools") { + Activity.points.push({ x: mouseX, y: mouseY }) + canvas.requestPaint() + } else if (items.toolSelected == "brush2") { + canvas.currentPoint = { x: mouseX, y: mouseY } + canvas.requestPaint() + } else if (items.toolSelected == "pattern") { + Activity.points.push({x: mouseX, y: mouseY}) + canvas.requestPaint() + } else if (items.toolSelected == "spray" ) { + canvas.lastX = mouseX + canvas.lastY = mouseY + canvas.requestPaint() + } else if (items.toolSelected == "brush3" ) { + canvas.requestPaint() + canvas.lastX = mouseX + canvas.lastY = mouseY + } else if(items.toolSelected == "brush4" ) { + Activity.points.push({x: mouseX, y: mouseY}) + canvas.requestPaint() + } else if(items.toolSelected == "brush5" ) { + Activity.connectedPoints.push({x: mouseX, y: mouseY}) + canvas.requestPaint() + } else if (items.toolSelected == "blur") { + var ctx = canvas.getContext('2d') + ctx.lineJoin = ctx.lineCap = 'round'; + canvas.ctx.shadowBlur = 10 + canvas.ctx.shadowColor = items.paintColor + ctx.lineWidth = items.sizeS + ctx.strokeStyle = items.paintColor + ctx.beginPath() + ctx.moveTo(canvas.lastX, canvas.lastY) + canvas.lastX = area.mouseX + canvas.lastY = area.mouseY + ctx.lineTo(canvas.lastX, canvas.lastY) + ctx.stroke() + + canvas.requestPaint() + } + } + + onClicked: { + if (items.toolSelected == "fill") { + canvas.requestPaint() + print("requestPaint FILL ") + } + } + } + + Rectangle { + id: rectangle + color: items.paintColor + enabled: items.toolSelected == "rectangle" || items.toolSelected == "line"|| items.toolSelected == "lineShift" + opacity: items.toolSelected == "rectangle" || items.toolSelected == "line"|| items.toolSelected == "lineShift" ? 1 : 0 + + 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" + opacity: items.toolSelected == "circle" ? 1 : 0 + property real rotationn: 0 + } + } + } + + Rectangle { + id: colorTools + color: background.color + height: flow.height + flow.anchors.margins * 2 + anchors { + left: parent.left + top: parent.top + right: parent.right + margins: 0 + } + z: 2 + + Flow { + id: flow + anchors { + left: parent.left + top: parent.top + right: parent.right + margins: 8 + } + + spacing: 5 + + // colors in left panel + Repeater { + id: colorRepeater + model: Activity.colors + + Rectangle { + id: root + radius: width / 2 + width: dim - 5 > 50 ? + dim - 5 < 70 ? + dim - 5 : 70 : 60 + height: width + color: modelData + property real dim: (background.width - 16) / Activity.colors.length + property bool active: items.paintColor === color + border.color: active? "#595959" : "#f2f2f2" + border.width: 3 + + MouseArea { + anchors.fill :parent + onDoubleClicked: { + print("choose a color: ") + items.index = index + colorDialog.visible = true + } + onClicked: { + print("root.width: ",root.width) + root.active = true + items.paintColor = root.color + + for (var i = 0; i < colorRepeater.count; i++) + if (i != index) + colorRepeater.itemAt(i).active = false + + background.hideExpandedTools() + if (color == "#c2c2d6") { + print("choose a color: ") + items.index = index + colorDialog.visible = true + } else { + items.paintColor = color + } + } + } + } + } + } + } + + Rectangle { + id: selectSize + height: row.height * 1.1 + width: row.width * 1.2 + + x: rightPannelFrame.x - width + y: rightPannelFrame.y - height / 2 + sizeTool.height * 1.5 + + rightPannel.spacing * 2 + rightPannel.anchors.topMargin + + radius: width * 0.05 + opacity: 0 + + z: 100 + color: "lightblue" + + Row { + id: row + height: 90 + + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + + spacing: 10 + + Thickness { lineSize: 0.15 } + Thickness { lineSize: 0.3 } + Thickness { lineSize: 0.45 } + Thickness { lineSize: 0.6 } + } + } + + Rectangle { + id: selectBrush + height: row2.height * 1.15 + width: row2.width * 1.05 + + x: rightPannelFrame.x - width + y: rightPannelFrame.y - height / 2 + eraser.height * 3.5 + + rightPannel.spacing * 2 + rightPannel.anchors.topMargin + + radius: width * 0.02 + opacity: 0 + + z: 100 + color: "lightblue" + + Row { + id: row2 + height: pencil.height + + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + + spacing: 10 + + ToolItem { id: pencil; name: "pencil" } + ToolItem { + name: "dot" +// source: Activity.url + "dot.jpg" + opacity: items.toolSelected == "pattern" && items.patternType == "dot.jpg" ? 1 : 0.6 + onClick: { + items.toolSelected = "pattern" + items.patternType = "dot.jpg" + items.lastToolSelected = "pattern" + Activity.getPattern(1) + timer.index = 0 + timer.start() + } + } + ToolItem { + name: "pattern2" +// source: Activity.url + "pattern2.png" + opacity: items.toolSelected == "pattern" && items.patternType == "pattern2.png" ? 1 : 0.6 + onClick: { + items.toolSelected = "pattern" + items.patternType = "pattern2.png" + items.lastToolSelected = "pattern" + Activity.getPattern2(1) + timer.index = 0 + timer.start() + // brushSelectCanvas.requestPaint() + } + } + + ToolItem { + name: "pattern3" + opacity: items.toolSelected == "pattern" && items.patternType == "pattern3" ? 1 : 0.6 + onClick: { + items.toolSelected = "pattern" + items.patternType = "pattern3" + items.lastToolSelected = "pattern" + Activity.getPattern3(1) + timer.index = 0 + timer.start() + } + } + + ToolItem { name: "tools" } + ToolItem { name: "brush2" } + ToolItem { name: "spray" } + ToolItem { name: "brush3" } + ToolItem { name: "brush4" } + ToolItem { name: "brush5" } + ToolItem { name: "blur" } + } + } + + // tools from the right panel + Rectangle { + id: rightPannelFrame + width: rightPannel.width + rightPannel.anchors.margins * 2 + anchors { + right: parent.right + top: colorTools.bottom + bottom: parent.bottom + margins: 0 + } + z: 3 + color: background.color + + Column { + id: rightPannel + anchors { + right: parent.right + top: parent.top + topMargin: background.height - colorTools.height > rightPannel.height ? + (background.height - colorTools.height - rightPannel.height) / 2 : 0 + margins: 8 + } + + spacing: 5 + + // eraser tool + Image { + id: eraser + width: 48; height: 48 + source: Activity.url + "eraser.svg" + opacity: items.toolSelected == "eraser" ? 1 : 0.6 + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = "eraser" + background.hideExpandedTools() + print("anchors ",rightPannel.anchors.topMargin) + print("bool: ",background.height - colorTools.height > rightPannel.height) + } + } + } + + property alias sizeTool: sizeTool + + // select size + Image { + id: sizeTool + width: 48; height: 48 + source: Activity.url + "size.PNG" + opacity: 0.6 + + MouseArea { + id: toolArea + anchors.fill: parent + onClicked: { + if (selectSize.opacity == 0) { + sizeTool.opacity = 1 + selectSize.opacity = 0.9 + selectSize.z = 100 + } + else { + selectSize.opacity = 0 + selectSize.z = -1 + sizeTool.opacity = 0.6 + } + selectBrush.opacity = 0 + selectBrush.z = -1 + } + } + } + + + ToolItem { name: "fill"; width: 48; height: 48 } + + Canvas { + id: brushSelectCanvas + width: 48; height: 48 + function c(x) { + return width * x / 510 + } + + onPaint: { + var brushContext = items.brushSelectCanvas.getContext("2d") + brushContext.clearRect(0, 0, brushSelectCanvas.width, brushSelectCanvas.height) + + brushContext.save() + + brushContext.strokeStyle = '' + // create a triangle as clip region + brushContext.beginPath() + brushContext.moveTo( c(17), c(494)) + brushContext.lineTo( c(53), c(327)) + brushContext.lineTo(c(336), c(40)) + brushContext.lineTo(c(390), c(11)) + brushContext.lineTo(c(457), c(41)) + brushContext.lineTo(c(497), c(107)) + brushContext.lineTo(c(475), c(155)) + brushContext.lineTo(c(133), c(438)) + brushContext.lineTo( c(17), c(494)) + + brushContext.closePath() + brushContext.stroke() + + brushContext.clip() // create clip from triangle path + + if (items.patternType == "dot.jpg") + Activity.getPattern(1) + if (items.patternType == "pattern2.png") + Activity.getPattern2(1) + if (items.patternType == "pattern3") + Activity.getPattern3(1) + + brushContext.fillStyle = brushContext.createPattern(items.shape.toDataURL(), 'repeat') + brushContext.fillRect(0, 0, 500,500) + + brushContext.restore() + brushContext.drawImage(Activity.url + "pen.svg", 0, 0,48,48) + } + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + selectBrush.opacity = 0.9 + selectBrush.z = 100 + + selectSize.opacity = 0 + selectSize.z = -1 + } + + onPressAndHold: { + selectBrush.opacity = 0.9 + selectBrush.z = 100 + + selectSize.opacity = 0 + selectSize.z = -1 + } + /*onClicked: { + if ( selectBrush.opacity == 0) { + selectBrush.opacity = 0.9 + selectBrush.z = 100 + } else { + selectBrush.opacity = 0 + selectBrush.z = -1 + } + selectSize.opacity = 0 + selectSize.z = -1 + }*/ + + onClicked: { + items.toolSelected = items.lastToolSelected + background.hideExpandedTools() + } + } + Image { + x: 0; y: 0 + source: Activity.url + "pen.svg" + sourceSize.width: 48; sourceSize.height: 48 + width: 48; height: 48 + } + } +/* + Image { + id: brushSelector2 + width: 48; height: 48 + source: Activity.url + "pencil.svg" + opacity: items.toolSelected == "" ? 1 : 0.6 + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + selectBrush.opacity = 0.9 + selectBrush.z = 100 + + selectSize.opacity = 0 + selectSize.z = -1 + } + + onPressAndHold: { + selectBrush.opacity = 0.9 + selectBrush.z = 100 + + selectSize.opacity = 0 + selectSize.z = -1 + } +// onClicked: { +// if ( selectBrush.opacity == 0) { +// selectBrush.opacity = 0.9 +// selectBrush.z = 100 +// } else { +// selectBrush.opacity = 0 +// selectBrush.z = -1 +// } +// selectSize.opacity = 0 +// selectSize.z = -1 +// } + + onClicked: { + items.toolSelected = items.lastToolSelected + background.hideExpandedTools() + } + } + } +*/ + // draw a circle + Rectangle { + width: 48; height: 48 + radius: width / 2 + color: items.toolSelected == "circle" ? items.paintColor : "white" + border.color: "black" + border.width: 2 + opacity: items.toolSelected == "circle" ? 1 : 0.6 + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = "circle" + background.hideExpandedTools() + } + } + } + + // draw a rectangle + Rectangle { // border of the rectangle + width: 48; height: 48 + color: "transparent" + opacity: items.toolSelected == "rectangle" ? 1 : 0.6 + + Rectangle { // actual rectangle + width: 42; height: 27 + border.color: "black" + border.width: 2 + color: items.toolSelected == "rectangle" ? items.paintColor : "white" + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = "rectangle" + background.hideExpandedTools() + } + } + } + + // draw a line + Rectangle { // border of the line + width: 48; height: 48 + color: "transparent" + opacity: items.toolSelected == "line" ? 1 : 0.6 + + Rectangle { // actual line + width: 42; height: 10 + rotation: -30 + border.color: "black" + border.width: 2 + color: items.toolSelected == "line" ? items.paintColor : "grey" + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = "line" + background.hideExpandedTools() + } + } + } + + // draw a line + Rectangle { // border of the line + width: 48; height: 48 + color: "transparent" + opacity: items.toolSelected == "lineShift" ? 1 : 0.6 + + Rectangle { // actual line + width: 42; height: 10 + border.color: "black" + border.width: 2 + color: items.toolSelected == "lineShift" ? items.paintColor : "grey" + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = "lineShift" + background.hideExpandedTools() + } + } + } + + // write text + Rectangle { // background of text + width: 48; height: 48 + color: "transparent" + opacity: items.toolSelected == "text" ? 1 : 0.6 + + GCText { // text + text: "A" + color: items.toolSelected == "text" ? items.paintColor : "grey" + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = "text" + background.hideExpandedTools() + + // enable the text to follow the cursor movement + area.hoverEnabled = true + + // make visible the inputTextFrame + inputTextFrame.opacity = 1 + inputTextFrame.z = 1000 + + // restore input text to "" + inputText.text = "" + } + } + } + + // undo button + Image { + id: undoButton + sourceSize.width: 48 + sourceSize.height: 48 + width: 48; height: 48 + source: Activity.url + "back.svg" + opacity: 0.6 + + MouseArea { + anchors.fill: parent + onPressed: undoButton.opacity = 1 + onReleased: undoButton.opacity = 0.6 + onClicked: { + background.hideExpandedTools() + + if (Activity.undo.length > 0 && items.next || + Activity.undo.length > 1 && items.next == false) { + items.undoRedo = true + + // if (Activity.undo[Activity.undo.length - 1] == items.urlImage) { + // if (Activity.redo.length == 0) { + if (items.next) { + print("items.next: ",items.next) + Activity.redo = Activity.redo.concat(Activity.undo.pop()) + } + + items.next = false + items.next2 = true + + // pop the last image saved from "undo" array + items.urlImage = Activity.undo.pop() + + // load the image in the canvas + canvas.loadImage(items.urlImage) + + // save the image into the "redo" array + Activity.redo = Activity.redo.concat(items.urlImage) + + print("undo: " + Activity.undo.length + " redo: " + Activity.redo.length + " undo Pressed") + } + } + } + } + + // redo button + Image { + id: redoButton + sourceSize.width: 48 + sourceSize.height: 48 + width: 48; height: 48 + source: Activity.url + "forward.svg" + opacity: 0.6 + + MouseArea { + anchors.fill: parent + onPressed: redoButton.opacity = 1 + onReleased: redoButton.opacity = 0.6 + onClicked: { + background.hideExpandedTools() + + if (Activity.redo.length > 0) { + items.undoRedo = true + + if (items.next2) { + print("=======items.next: ",items.next) + Activity.undo = Activity.undo.concat(Activity.redo.pop()) + } + + + items.next = true + items.next2 = false + + items.urlImage = Activity.redo.pop() + + canvas.loadImage(items.urlImage) + Activity.undo = Activity.undo.concat(items.urlImage) + + print("undo: " + Activity.undo.length + " redo: " + Activity.redo.length + " redo Pressed") + } + } + } + } + + // load button + Image { + id: loadButton + sourceSize.width: 48 + sourceSize.height: 48 + width: 48; height: 48 + source: Activity.url + "load.svg" + opacity: 0.6 + + MouseArea { + anchors.fill: parent + onPressed: loadButton.opacity = 1 + onReleased: loadButton.opacity = 0.6 + onClicked: { + if (load.opacity == 0) + load.opacity = 1 + + background.hideExpandedTools() + + // mark the pencil as the default tool + items.toolSelected = "pencil" + + // move the main screen to right + main.x = background.width + print("background.width: ",background.width) + print("main.x: ", main.x) + print("load pressed") + } + } + } + + // save button + Image { + id: saveButton + sourceSize.width: 48 + sourceSize.height: 48 + width: 48; height: 48 + source: Activity.url + "save.svg" + opacity: 0.6 + + MouseArea { + anchors.fill: parent + onPressed: saveButton.opacity = 1 + onReleased: saveButton.opacity = 0.6 + onClicked: Activity.saveToFile(true) + } + } + } + } + } + + // load images screen + Rectangle { + id: load + color: "lightblue" + width: background.width + height: background.height + opacity: 0 + z: 5 + + anchors { + top: main.top + right: main.left + } + + GridView { + id: gridView + anchors.fill: parent + cellWidth: (background.width - exitButton.width) / 2 * slider1.value; cellHeight: cellWidth + model: Activity.loadImagesSource + + delegate: Item { + width: gridView.cellWidth + height: gridView.cellHeight + property alias loadImage: loadImage + Image { + id: loadImage + source: modelData + anchors.centerIn: parent + sourceSize.width: parent.width * 0.7 + sourceSize.height: parent.height * 0.7 + width: parent.width * 0.9 + height: parent.height * 0.9 + mirror: false + MouseArea { + anchors.fill: parent + onClicked: { + canvas.url = loadImage.source + canvas.loadImage(loadImage.source) + + main.x = 0 + } + } + } + } + } + + Behavior on x { + NumberAnimation { + target: load + property: "x" + duration: 800 + easing.type: Easing.InOutQuad + } + } + + + Behavior on y { + NumberAnimation { + target: load + property: "y" + duration: 800 + easing.type: Easing.InOutQuad + } + } + + GCButtonCancel { + id: exitButton + onClose: { + print("onClose") + items.mainAnimationOnX = true + main.x = 0 + } + } + + Image { + id: switchToSavedPaintings + source: "qrc:/gcompris/src/activities/paint/paint.svg" + anchors.right: parent.right + anchors.top: exitButton.bottom + smooth: true + sourceSize.width: 60 * ApplicationInfo.ratio + anchors.margins: 10 + + MouseArea { + anchors.fill: parent + onClicked: { + if (loadSavedPainting.opacity == 0) + loadSavedPainting.opacity = 1 + + items.mainAnimationOnX = false + + // move down the loadPaintings screen + main.y = main.height + + loadSavedPainting.anchors.left = load.left + + // change the images sources from "saved images" to "load images" + items.loadSavedImage = true + } + } + } + + + Slider { + id: slider1 + minimumValue: 0.3 + value: 0.65 + height: parent.height * 0.5 + width: 60 + + opacity: 1 + enabled: true + + anchors.right: parent.right + anchors.rightMargin: switchToSavedPaintings.width / 2 - 10 + anchors.top: switchToSavedPaintings.bottom + + orientation: Qt.Vertical + + style: SliderStyle { + handle: Rectangle { + height: 80 + width: height + radius: width / 2 + color: "lightblue" + } + + groove: Rectangle { + implicitHeight: slider1.width + implicitWidth: slider1.height + radius: height / 2 + border.color: "#6699ff" + color: "#99bbff" + + Rectangle { + height: parent.height + width: styleData.handlePosition + implicitHeight: 100 + implicitWidth: 6 + radius: height/2 + color: "#4d88ff" + } + } + } + } + } + + // load screen 2 + Rectangle { + id: loadSavedPainting + color: "lightblue" + width: background.width + height: background.height + opacity: 0 + z: 100 + + anchors { + bottom: main.top + left: load.left + } + + GCText { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + anchors.horizontalCenterOffset: - rightFrame.width / 2 + fontSize: largeSize + text: "No paintings saved" + opacity: gridView2.count == 0 + } + + GridView { + id: gridView2 + anchors.fill: parent + cellWidth: (main.width - sizeOfImages.width) * slider.value; cellHeight: main.height * slider.value + flow: GridView.FlowTopToBottom + z: 1 + + delegate: Rectangle { + width: gridView2.cellWidth + height: gridView2.cellHeight + color: "transparent" + + Image { + id: loadImage2 + source: modelData.url + anchors.centerIn: parent + sourceSize.width: parent.width + sourceSize.height: parent.height + width: parent.width * 0.9 + height: parent.height * 0.9 + + MouseArea { + anchors.fill: parent + onClicked: { + loadSavedPainting.anchors.left = main.left + + canvas.url = loadImage2.source + canvas.loadImage(loadImage2.source) + + main.x = 0 + main.y = 0 + } + } + + GCButtonCancel { + anchors.right: undefined + anchors.left: parent.left + sourceSize.width: 40 * ApplicationInfo.ratio + + onClose: { + Activity.dataset.splice(index,1) + gridView2.model = Activity.dataset + Activity.saveToFile(false) + } + } + } + } + } + + Behavior on x { NumberAnimation { target: loadSavedPainting; property: "x"; duration: 800; easing.type: Easing.InOutQuad } } + + Behavior on y { NumberAnimation { target: loadSavedPainting; property: "y"; duration: 800; easing.type: Easing.InOutQuad } } + + + Rectangle { + id: rightFrame + width: sizeOfImages.width + sizeOfImages.anchors.margins * 2 + color: background.color + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + } + z: 2 + + Image { + id: sizeOfImages + source: "qrc:/gcompris/src/activities/paint/paint.svg" + anchors.right: parent.right + anchors.top: parent.top + smooth: true + sourceSize.width: 60 * ApplicationInfo.ratio + anchors.margins: 10 + + MouseArea { + anchors.fill: parent + onClicked: { + items.mainAnimationOnX = true + + // move down the loadPaintings screen + main.y = 0 + + // change the images sources from "saved images" to "load images" + items.loadSavedImage = false + } + } + } + + Slider { + id: slider + minimumValue: 0.3 + value: 0.65 + height: parent.height * 0.5 + width: 60 + + opacity: 1 + enabled: true + + anchors.right: parent.right + anchors.rightMargin: sizeOfImages.width / 2 - 10 + anchors.top: sizeOfImages.bottom + + orientation: Qt.Vertical + + style: SliderStyle { + handle: Rectangle { + height: 80 + width: height + radius: width / 2 + color: "lightblue" + } + + groove: Rectangle { + implicitHeight: slider.width + implicitWidth: slider.height + radius: height / 2 + border.color: "#6699ff" + color: "#99bbff" + + Rectangle { + height: parent.height + width: styleData.handlePosition + implicitHeight: 100 + implicitWidth: 6 + radius: height/2 + color: "#4d88ff" + } + } + } + } + } + } + + ColorDialog { + id: colorDialog + title: "Please choose a color" + visible: false + + onAccepted: { + colorRepeater.itemAt(items.index).color = colorDialog.color + items.paintColor = colorDialog.color + + // if you want to save the custom colors for the next session; + // update the array from js + // Activity.colors[items.index] = items.paintColor + // then add it to the saved file containing the paintings + console.log("You chose: " + colorDialog.color) + } + onRejected: { + console.log("Canceled") + } + } + + Canvas { + id: shape + width: 300; height: 300 + opacity: 0 + onPaint: { + } + } + } +} diff --git a/src/activities/paint/SaveToFilePrompt.qml b/src/activities/paint/SaveToFilePrompt.qml new file mode 100644 index 000000000..18aadfeb3 --- /dev/null +++ b/src/activities/paint/SaveToFilePrompt.qml @@ -0,0 +1,174 @@ +/* GCompris - SaveToFilePrompt.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.1 +import "paint.js" as Activity +import "../../core" + +Rectangle { + id: saveToFilePrompt + width: parent.width * 0.6 + height: parent.height * 0.6 + radius: height / 30 + color: "#b3b3cc" + opacity: 0 + anchors.centerIn: parent + + property string text + + signal yes + signal no + signal cancel + + GCText { + text: saveToFilePrompt.text + width: parent.width + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + anchors { + verticalCenter: parent.verticalCenter + verticalCenterOffset: - (yes.anchors.bottomMargin + yes.height) / 2 + horizontalCenter: parent.horizontalCenter + } + } + + Rectangle { + id: yes + color: "#ffe6cc" + width: parent.width * 3 / 10 + height: parent.height / 6 + radius: height / 2 + anchors { + bottom: parent.bottom + bottomMargin: parent.height / 10 + horizontalCenter: parent.horizontalCenter + horizontalCenterOffset: - (width + (parent.width / 2 - width * 1.5) / 2) + } + + GCText { + text: "YES" + anchors.centerIn: parent + } + + MouseArea { + id: mouseArea1 + anchors.fill: parent + hoverEnabled: true + onClicked: { + saveToFilePrompt.yes() + } + + states: State { + name: "scaled"; when: mouseArea1.containsMouse + PropertyChanges { + target: yes + scale: 1.1 + } + } + + transitions: Transition { + NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } + } + } + } + + Rectangle { + id: no + color: "#ff6666" + width: parent.width * 3 / 10 + height: parent.height / 6 + radius: height / 2 + anchors { + bottom: parent.bottom + bottomMargin: parent.height / 10 + horizontalCenter: parent.horizontalCenter + } + + GCText { + text: "NO" + anchors.centerIn: parent + } + + MouseArea { + id: mouseArea2 + anchors.fill: parent + hoverEnabled: true + onClicked: { + saveToFilePrompt.no() + } + + states: State { + name: "scaled"; when: mouseArea2.containsMouse + PropertyChanges { + target: no + scale: 1.1 + } + } + + transitions: Transition { + NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } + } + } + } + + Rectangle { + id: cancel + color: "#ccff99" + width: parent.width * 3 / 10 + height: parent.height / 6 + radius: height / 2 + anchors { + bottom: parent.bottom + bottomMargin: parent.height / 10 + horizontalCenter: parent.horizontalCenter + horizontalCenterOffset: width + (parent.width / 2 - width * 1.5) / 2 + } + + GCText { + text: "Cancel" + anchors.centerIn: parent + } + + MouseArea { + id: mouseArea3 + anchors.fill: parent + hoverEnabled: true + onClicked: { + saveToFilePrompt.cancel() + } + + states: State { + name: "scaled"; when: mouseArea3.containsMouse + PropertyChanges { + target: cancel + scale: 1.1 + } + } + + transitions: Transition { + NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } + } + } + } + + Keys.onPressed: { + if (event.key == Qt.Key_Backspace) { + print("pressed backspace") + } + } +} diff --git a/src/activities/paint/Shape.qml b/src/activities/paint/Shape.qml new file mode 100644 index 000000000..ffa831b31 --- /dev/null +++ b/src/activities/paint/Shape.qml @@ -0,0 +1,52 @@ +/* GCompris - Shape.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.1 + +Item { + id: shape + property string shape + property string color: "black" + + Rectangle { + id: rectangle + color: shape.color + width: parent.width + height: parent.height + enabled: parent.shape == "rectangle" + opacity: parent.shape == "rectangle" ? 1 : 0 + } + + Rectangle { + id: circle + radius: width / 2 + color: shape.color + width: parent.width + height: parent.width + enabled: parent.shape == "circle" + opacity: parent.shape == "circle" ? 1 : 0 + } + + // creates a triangle, but works only with qt quick 2.7 +/* Path { + startX: 0; startY: 0 + PathSvg { path: "L 150 50 L 100 150 z" } + } +*/ + +} \ No newline at end of file diff --git a/src/activities/paint/Thickness.qml b/src/activities/paint/Thickness.qml new file mode 100644 index 000000000..07657103e --- /dev/null +++ b/src/activities/paint/Thickness.qml @@ -0,0 +1,71 @@ +/* GCompris - Thickness.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.1 + +Rectangle { + id: frame + color: items.sizeS == Math.floor(lineSize * 15) ? "#ffff66" : "#ffffb3" + width: 30 + height: 80 + radius: width * 0.35 + border.color: "#cccc00" + border.width: 2 + opacity: items.sizeS == Math.floor(lineSize * 15) ? 1 : 0.7 + + anchors.verticalCenter: parent.verticalCenter + + property real lineSize: 0.5 + + Rectangle { + id: thickness + color: "blue" + radius: width * 0.35 + width: parent.width * frame.lineSize + height: parent.height * 0.9 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + + onClicked: { + background.hideExpandedTools() + items.sizeS = parent.lineSize * 15 + print("frame.lineSize " + Math.floor(frame.lineSize * 15)) + print("items.sizeS: " + items.sizeS) + } + + states: State { + name: "scaled"; when: mouseArea.containsMouse + PropertyChanges { + target: frame + opacity: 1 + scale: 1.2 + } + } + + transitions: Transition { + NumberAnimation { properties: "scale"; easing.type: Easing.OutCubic } + } + } +} diff --git a/src/activities/paint/ToolItem.qml b/src/activities/paint/ToolItem.qml new file mode 100644 index 000000000..188140058 --- /dev/null +++ b/src/activities/paint/ToolItem.qml @@ -0,0 +1,44 @@ +/* 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.1 +import "paint.js" as Activity + +Image { + id: button + width: 60; height: 60 + source: Activity.url + name + ".svg" + opacity: items.toolSelected == name ? 1 : 0.6 + + property string name + signal click + + MouseArea { + anchors.fill: parent + onClicked: { + items.toolSelected = name + items.lastToolSelected = name + background.hideExpandedTools() + + // make the hover over the canvas false + area.hoverEnabled = false + + click() + } + } +} diff --git a/src/activities/paint/paint.js b/src/activities/paint/paint.js new file mode 100644 index 000000000..c1ad3be3b --- /dev/null +++ b/src/activities/paint/paint.js @@ -0,0 +1,238 @@ +/* GCompris - paint.js + * + * 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 . + */ + +.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/paint/resource/" + +var currentLevel = 0 +var numberOfLevel = 4 +var items +var colors = [ + /*blue*/ "#33B5E5", "#1a53ff", "#0000ff", "#0000b3", + /*green*/ "#bfff00", "#99CC00", "#86b300", "#608000", + /*orange*/ "#FFBB33", + /*red*/ "#ff9999", "#FF4444", "#ff0000", "#cc0000", + /*pink*/ "#ff00ff", + /*yellow*/ "#ffff00", + /*white*/ "#ffffff", + /*black*/ "#000000", + /*free spots*/ + "#c2c2d6", "#c2c2d6", "#c2c2d6", "#c2c2d6", + "#c2c2d6", "#c2c2d6", "#c2c2d6", "#c2c2d6" + ] +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 redo = [] + +var aux = ["1","2","3","4","5"] + +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 + initLevel() +} + +function stop() { +} + +function initLevel() { + dataset = null + points = [] + connectedPoints = [] + + undo = [] + redo = [] + + ctx = items.canvas.getContext("2d") + ctx.fillStyle = "#ffffff" + + ctx.beginPath() + ctx.clearRect(0, 0, items.background.width, items.background.height) + + ctx.moveTo(0, 0) + ctx.lineTo(items.background.width, 0) + ctx.lineTo(items.background.width, items.background.height) + ctx.lineTo(0, items.background.height) + ctx.closePath() + ctx.fill() + items.canvas.requestPaint() + + items.toolSelected = "" + items.paintColor = colors[0] + + items.next = false + items.next2 = false + + undo = ["data:image/pngbase64,iVBORw0KGgoAAAANSUhEUgAAA4sAAAOLCAYAAAD5ExZjAAAACXBIWXMAAA7EAAAOxAGVKw4bAAATLElEQVR4nO3XsQ0AIRDAsOf33/lokTIAFPYEabNmZj4AAAA4/LcDAAAAeI9ZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgzCIAAABhFgEAAAizCAAAQJhFAAAAwiwCAAAQZhEAAIAwiwAAAIRZBAAAIMwiAAAAYRYBAAAIswgAAECYRQAAAMIsAgAAEGYRAACAMIsAAACEWQQAACDMIgAAAGEWAQAACLMIAABAmEUAAADCLAIAABBmEQAAgDCLAAAAhFkEAAAgNq0MCxI5gV9wAAAAAElFTkSuQmCC"] + + // if the widht/height is changed, the drawing is reset and repainted the last image saved + if (items.widthHeightChanged) { + // load the image + items.canvas.url = items.lastUrl + items.canvas.loadImage(items.lastUrl) + // reset the flag to false + items.widthHeightChanged = false + } + + //load saved paintings from file + parseImageSaved() + items.gridView2.model = dataset + + getPattern() + items.background.started = true + +} + +function getPattern(size) { + var dotWidth = 20, dotDistance = 5 + if (size) { + dotWidth = 10; dotDistance = 2.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(size) { + var lineSize = 10, lineWidth = 5 + if (size) { + lineSize = 5; lineWidth = 2.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(size) { + var lineSize = 20, lineWidth = 10 + if (size) { + lineSize = 10; lineWidth = 5 + } + 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); +} + +// 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) + } else print("else") + + // 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 nextLevel() { + if(numberOfLevel <= ++currentLevel ) { + currentLevel = 0 + } + initLevel() +} + +function previousLevel() { + if(--currentLevel < 0) { + currentLevel = numberOfLevel - 1 + } + initLevel() +} diff --git a/src/activities/paint/paint.svg b/src/activities/paint/paint.svg new file mode 100644 index 000000000..a8cc9a9f4 --- /dev/null +++ b/src/activities/paint/paint.svg @@ -0,0 +1,171 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/paint/resource/README b/src/activities/paint/resource/README new file mode 100644 index 000000000..178ce1515 --- /dev/null +++ b/src/activities/paint/resource/README @@ -0,0 +1,34 @@ +Arrows left/right: +https://openclipart.org/detail/16963/arrowgoleft +https://openclipart.org/detail/16965/arrowgoright + +load: +https://openclipart.org/detail/244696/modern-load-symbol-2 + +save: +https://openclipart.org/detail/38959/save + +another icon: +https://openclipart.org/detail/12125/green-menu-icon-set + +tool: +https://openclipart.org/detail/210626/tools-awesome-pot + +patterns: +https://openclipart.org/detail/247586/prismatic-waves-background-no-background +https://openclipart.org/detail/231025/colorful-stitched-ducks-seamless-pattern +https://openclipart.org/detail/214475/color-braids + + +blur: +https://openclipart.org/detail/225289/antialiasing-3 + +brush4: +https://openclipart.org/detail/2606/brush + +brush5: +https://openclipart.org/detail/256801/red-jewel + + +pen +https://openclipart.org/detail/245366/Paint-Brush-with-Dye-17 \ No newline at end of file diff --git a/src/activities/paint/resource/back.svg b/src/activities/paint/resource/back.svg new file mode 100644 index 000000000..2f327c28b --- /dev/null +++ b/src/activities/paint/resource/back.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + arrow_go_left + 2008-05-26T11:32:36 + + https://openclipart.org/detail/16963/arrow_go_left-by-jean_victor_balin + + + jean_victor_balin + + + + + arrow + icon + + + + + + + + + + + diff --git a/src/activities/paint/resource/blur.svg b/src/activities/paint/resource/blur.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/blur.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/brush2.png b/src/activities/paint/resource/brush2.png new file mode 100644 index 000000000..e945f7f2a Binary files /dev/null and b/src/activities/paint/resource/brush2.png differ diff --git a/src/activities/paint/resource/brush2.svg b/src/activities/paint/resource/brush2.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/brush2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/brush3.svg b/src/activities/paint/resource/brush3.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/brush3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/brush4.svg b/src/activities/paint/resource/brush4.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/brush4.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/brush5.svg b/src/activities/paint/resource/brush5.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/brush5.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/dot.jpg b/src/activities/paint/resource/dot.jpg new file mode 100644 index 000000000..7d8dd78ac Binary files /dev/null and b/src/activities/paint/resource/dot.jpg differ diff --git a/src/activities/paint/resource/dot.svg b/src/activities/paint/resource/dot.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/dot.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/eraser.svg b/src/activities/paint/resource/eraser.svg new file mode 100644 index 000000000..3ad525e32 --- /dev/null +++ b/src/activities/paint/resource/eraser.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + eraser + 2007-04-17T19:52:58 + + https://openclipart.org/detail/3981/eraser-by-lmproulx + + + lmproulx + + + + + eraser + tool + + + + + + + + + + + diff --git a/src/activities/paint/resource/fill.svg b/src/activities/paint/resource/fill.svg new file mode 100644 index 000000000..fa89dade3 --- /dev/null +++ b/src/activities/paint/resource/fill.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/activities/paint/resource/forward.svg b/src/activities/paint/resource/forward.svg new file mode 100644 index 000000000..91520b18a --- /dev/null +++ b/src/activities/paint/resource/forward.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + arrow_go_right + 2008-05-26T11:33:25 + + https://openclipart.org/detail/16965/arrow_go_right-by-jean_victor_balin + + + jean_victor_balin + + + + + arrow + icon + + + + + + + + + + + diff --git a/src/activities/paint/resource/load.svg b/src/activities/paint/resource/load.svg new file mode 100644 index 000000000..2543dd8b6 --- /dev/null +++ b/src/activities/paint/resource/load.svg @@ -0,0 +1,134 @@ + + + + + Modern Load Symbol #2 + + + + + + + + image/svg+xml + + Modern Load Symbol #2 + + https://openclipart.org/user-detail/joede + A simple but more modern "Load" symbol (variant #2) for designing keypad buttons. Remember, who knows disc drives today... + + + keypad + store + save + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/paint/resource/pattern2.png b/src/activities/paint/resource/pattern2.png new file mode 100644 index 000000000..09ce1f871 Binary files /dev/null and b/src/activities/paint/resource/pattern2.png differ diff --git a/src/activities/paint/resource/pattern2.svg b/src/activities/paint/resource/pattern2.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/pattern2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/pattern3.svg b/src/activities/paint/resource/pattern3.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/pattern3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/pen.svg b/src/activities/paint/resource/pen.svg new file mode 100644 index 000000000..074e696c0 --- /dev/null +++ b/src/activities/paint/resource/pen.svg @@ -0,0 +1,123 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/activities/paint/resource/pencil.svg b/src/activities/paint/resource/pencil.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/pencil.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/save.svg b/src/activities/paint/resource/save.svg new file mode 100644 index 000000000..c53cbd624 --- /dev/null +++ b/src/activities/paint/resource/save.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Save + 2010-04-07T19:41:46 + + https://openclipart.org/detail/38959/save-by-mightyman + + + mightyman + + + + + button icon + floppy disk + icon + save + save file + toolbar + toolbar icon + + + + + + + + + + + diff --git a/src/activities/paint/resource/size.PNG b/src/activities/paint/resource/size.PNG new file mode 100644 index 000000000..3bbfc1745 Binary files /dev/null and b/src/activities/paint/resource/size.PNG differ diff --git a/src/activities/paint/resource/spray.svg b/src/activities/paint/resource/spray.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/spray.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/activities/paint/resource/tools.svg b/src/activities/paint/resource/tools.svg new file mode 100644 index 000000000..7173b2c8e --- /dev/null +++ b/src/activities/paint/resource/tools.svg @@ -0,0 +1,9 @@ + + + + + + + + +