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 }