Warning, /office/klevernotes/src/contents/ui/pages/PaintingPage.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 // SPDX-FileCopyrightText: 2023 Louis Schul <schul9louis@gmail.com>
0003 
0004 import QtQuick 2.15
0005 import QtQuick.Layouts 1.15
0006 import QtQuick.Controls 2.15 as Controls
0007 
0008 import org.kde.kirigami 2.19 as Kirigami
0009 
0010 import "qrc:/contents/ui/dialogs"
0011 import "qrc:/contents/ui/painting"
0012 
0013 import org.kde.Klever 1.0
0014 import WashiPad 1.0
0015 
0016 Kirigami.Page {
0017     id: root
0018 
0019     readonly property QtObject editorView: pageStack.get(0).editorView
0020     readonly property size size: Qt.size(sketchContent.width, sketchContent.height)
0021     readonly property point cursorPos: Qt.point(handler.point.x, handler.point.y)
0022     readonly property var penType: Stroke.Fill
0023 
0024     property bool cantLeave: false
0025     property bool isEraser: false
0026     property color penColor: mouseArea.lastButton === Qt.RightButton ? colorBar.secondaryColor : colorBar.primaryColor
0027 
0028     title: i18nc("@title:page", "Paint!")
0029 
0030     actions: [
0031         Kirigami.Action {
0032             text: i18nc("@label:button", "Save")
0033             icon.name: "document-save-symbolic"
0034 
0035             onTriggered: {
0036                 root.saveImage()
0037             }
0038         },
0039         Kirigami.Action {
0040             text: i18nc("@label:button, as in 'erase everything'", "Clear")
0041             icon.name: "edit-clear-symbolic"
0042             
0043             onTriggered: {
0044                 root.clearCanvas()
0045             }
0046         },
0047         Kirigami.Action {
0048             id: autoCropAction
0049 
0050             text: i18nc("@label:action", "Auto crop")
0051             checked: true
0052             checkable: true
0053             icon.name: "image-crop-symbolic"
0054         },
0055         Kirigami.Action {
0056             id: penToggler
0057 
0058             checked: !root.isEraser
0059             checkable: true
0060             icon.name: "draw-brush"
0061             
0062             onTriggered: {
0063                 handler.changePointer(0)
0064             }
0065         },
0066         Kirigami.Action {
0067             id: eraserToggler
0068 
0069             checked: root.isEraser
0070             checkable: true
0071             icon.name: "draw-eraser"
0072             
0073             onTriggered: {
0074                 handler.changePointer(1)
0075             }
0076         }
0077     ]
0078 
0079     onBackRequested: (event) => {
0080         if (root.cantLeave) {
0081             event.accepted = true;
0082             leavingDialog.open();
0083             return
0084         }
0085         applicationWindow().currentPageName = "Main"
0086         showImagePicker()
0087         clearCanvas()
0088     }
0089 
0090     LeavePaintingDialog {
0091         id: leavingDialog
0092 
0093         onAccepted: {
0094             root.saveImage()
0095         }
0096         onDiscarded: {
0097             leavingDialog.close()
0098             root.closePage()
0099         }
0100     }
0101 
0102     ColumnLayout {
0103         anchors.fill: parent
0104 
0105         Controls.ScrollView {
0106             Layout.fillWidth: true
0107             Layout.fillHeight: true
0108 
0109             Flickable {
0110                 id: flickable
0111 
0112                 contentWidth: sketchContent.width
0113                 contentHeight: sketchContent.height
0114 
0115                 clip:true
0116                 interactive: !mouseArea.isPress
0117 
0118                 Rectangle {
0119                     id: sketchContent
0120 
0121                     width: 1024
0122                     height: 1024
0123 
0124                     StrokeListItem {
0125                         id: fillStrokes
0126 
0127                         z: 0
0128                         anchors.fill: parent
0129 
0130                         type: Stroke.Fill
0131                         model: sketchModel
0132                     }
0133 
0134                     StrokeItem {
0135                         id: currentStroke
0136 
0137                         z: stroke.type === Stroke.Outline ? 1 : 0
0138                         anchors.fill: parent
0139                     }
0140                 }
0141 
0142                 MouseArea {
0143                     id: mouseArea
0144 
0145                     property var lastButton
0146                     property bool isPress: false
0147 
0148                     anchors.fill: sketchContent
0149 
0150                     enabled: true
0151                     acceptedButtons: Qt.LeftButton | Qt.RightButton
0152 
0153                     onPositionChanged: if (isPress) {
0154                             handler.mouseMoved(mouse.x-flickable.contentX, mouse.y-flickable.contentY)
0155                     }
0156                     onReleased: if (mouse.button === lastButton) {
0157                         isPress = false
0158 
0159                         handler.changeMousePress(isPress)
0160                     }
0161                     onPressed: if (!isPress) {
0162                         isPress = true
0163                         lastButton = mouse.button
0164 
0165                         handler.changeMousePress(isPress)
0166                     }
0167                 }
0168             }
0169         }
0170 
0171         ColorBar {
0172             id: colorBar
0173 
0174             Layout.fillWidth: true
0175             Layout.preferredHeight: 50
0176             Layout.topMargin: Kirigami.Units.largeSpacing
0177         }
0178     }
0179 
0180     Rectangle {
0181         id: cursor
0182 
0183         x: handler.point.x - width / 2
0184         y: handler.point.y - height / 2
0185         width: height
0186         height: pressureEquation.width * 1.5
0187 
0188         color: "transparent"
0189         border {
0190             color: root.isEraser ? Qt.rgba(1, 0, 0, 0.75) : Qt.rgba(0, 0, 1, 0.75)
0191             width: 10
0192         }
0193         radius: height / 2
0194         visible: mouseArea.isPress
0195     }
0196 
0197     SketchViewHandler {
0198         id: handler
0199 
0200         onPointChanged: {
0201             if (!pressed) return
0202 
0203             if (!root.isEraser){
0204                 addSample()
0205             } else {
0206                 eraseSamples()
0207             }
0208         }
0209         onIsEraserChanged: {
0210             root.isEraser = sketchViewIsEraser
0211         }
0212         onPressedChanged: {
0213             if (root.isEraser) return
0214 
0215             if (!pressed && !isEmpty(currentStroke.stroke.boundingRect())) {
0216                 addSample()
0217                 fillStrokes.addStroke(currentStroke.stroke)
0218                 currentStroke.stroke = createStroke(penType, root.penColor)
0219                 return
0220             }
0221 
0222             currentStroke.stroke = createStroke(penType, root.penColor)
0223         }
0224 
0225         function isEmpty(rect) {
0226             return rect.left === rect.right
0227                 && rect.top === rect.bottom
0228         }
0229 
0230         function createPoint() {
0231             return Qt.vector2d(point.x + flickable.contentX,
0232                         point.y + flickable.contentY)
0233         }
0234 
0235         function addSample() {
0236             if (!root.cantLeave) root.cantLeave = true
0237             var sample = createSample(createPoint(), pressureEquation.width)
0238             currentStroke.addSample(sample)
0239         }
0240 
0241         function eraseSamples() {
0242             var point = createPoint()
0243             var radius = cursor.height / 2
0244 
0245             fillStrokes.eraseArea(point, radius)
0246         }
0247     }
0248 
0249     PressureEquation {
0250         id: pressureEquation
0251 
0252         readonly property real minFillWidth: 4
0253         readonly property real maxFillWidth: 20
0254         readonly property real minEraserWidth: 8
0255         readonly property real maxEraserWidth: 80
0256 
0257         pressure: handler.point.pressure
0258         minWidth: root.isEraser ? minEraserWidth : minFillWidth
0259         maxWidth: root.isEraser ? maxEraserWidth : maxFillWidth
0260     }
0261 
0262     SketchModel {
0263         id: sketchModel
0264     }
0265 
0266     SketchSerializer {
0267         id: serializer
0268     }
0269 
0270     function clearCanvas() {
0271         const point = Qt.vector2d(512, 512)
0272         fillStrokes.eraseArea(point, 1024)
0273         root.cantLeave = false
0274     }
0275 
0276     function showImagePicker(imagePath, cropRect) {
0277         const imagePickerDialog = editorView.imagePickerDialog
0278         imagePickerDialog.open()
0279         imagePickerDialog.paintClipRect = cropRect
0280         imagePickerDialog.path = imagePath
0281         imagePickerDialog.paintedImageChoosen = true
0282         imagePickerDialog.storeCheckbox.checked = true
0283         imagePickerDialog.storeCheckbox.enabled = false
0284     }
0285 
0286     function closePage(imagePath, cropRect) {
0287         root.cantLeave = false
0288         clearCanvas()
0289         showImagePicker(imagePath, cropRect)
0290     }
0291 
0292     function saveImage() {
0293         let filePath
0294         let cropRect
0295         if (root.cantLeave) {
0296             let date = new Date().valueOf()
0297             const tmpFileName = "/KNtmpPaint"+date+".png"
0298 
0299             filePath = StandardPaths.writableLocation(StandardPaths.TempLocation)+tmpFileName
0300             serializer.serialize(sketchModel, Qt.size(1024, 1024), filePath)
0301 
0302             if (autoCropAction.checked) cropRect = serializer.getCropRect()
0303         }
0304         closePage(filePath, cropRect)
0305     }
0306 }