File indexing completed on 2024-04-28 04:33:43
0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> 0002 // 0003 // SPDX-License-Identifier: LGPL-2.1-or-later 0004 0005 #include "drawingarea.h" 0006 0007 #include <QSGNode> 0008 #include <QPointF> 0009 #include <QSGFlatColorMaterial> 0010 0011 #include <QSvgGenerator> 0012 #include <QPainter> 0013 0014 DrawingArea::DrawingArea(QQuickItem *parent) 0015 : QQuickItem(parent) 0016 { 0017 setObjectName("DrawingArea"); 0018 setFlag(QQuickItem::ItemHasContents); 0019 setAcceptedMouseButtons(Qt::LeftButton); 0020 } 0021 0022 DrawingArea::~DrawingArea() 0023 {} 0024 0025 void DrawingArea::saveSvg(const QUrl &file) 0026 { 0027 if (!file.isEmpty()) { 0028 QSvgGenerator generator; 0029 generator.setFileName(file.toLocalFile()); 0030 0031 QPainter painter; 0032 painter.begin(&generator); 0033 0034 for (const auto &drawEvent : qAsConst(m_drawEventList)) { 0035 painter.setPen(drawEvent.penColor); 0036 QPen pen = painter.pen(); 0037 pen.setWidth(drawEvent.penWidth); 0038 painter.setPen(pen); 0039 painter.drawPath(drawEvent.path); 0040 } 0041 painter.end(); 0042 } 0043 } 0044 0045 QColor DrawingArea::penColor() const 0046 { 0047 return m_penColor; 0048 } 0049 0050 void DrawingArea::setPenColor(const QColor &penColor) 0051 { 0052 if (m_penColor == penColor) { 0053 return; 0054 } 0055 m_penColor = penColor; 0056 Q_EMIT penColorChanged(); 0057 } 0058 0059 0060 double DrawingArea::penWidth() const 0061 { 0062 return m_penWidth; 0063 } 0064 0065 void DrawingArea::setPenWidth(double penWidth) 0066 { 0067 if (m_penWidth == penWidth) { 0068 return; 0069 } 0070 m_penWidth = penWidth; 0071 Q_EMIT penWidthChanged(); 0072 } 0073 0074 DrawingArea::Tool DrawingArea::tool() const 0075 { 0076 return m_tool; 0077 } 0078 0079 void DrawingArea::setTool(DrawingArea::Tool tool) 0080 { 0081 if (m_tool == tool) { 0082 return; 0083 } 0084 m_tool = tool; 0085 Q_EMIT toolChanged(); 0086 } 0087 0088 DrawEvent &DrawingArea::currentDrawEvent() 0089 { 0090 QTime currentTime = QTime::currentTime(); 0091 0092 if (m_drawEventCreationTime.isNull() || (m_drawEventCreationTime.msecsTo(currentTime) > 1000)) { 0093 m_drawEventCreationTime = currentTime; 0094 DrawEvent drawEvent(m_penWidth, m_penColor); 0095 drawEvent.path.moveTo(m_drawEventList.last().path.currentPosition()); 0096 m_drawEventList.append(drawEvent); 0097 ++m_eventIndex; 0098 } 0099 0100 return m_drawEventList.last(); 0101 } 0102 0103 void DrawingArea::mousePressEvent(QMouseEvent* event) 0104 { 0105 if (event->button() != Qt::LeftButton) { 0106 return; 0107 } 0108 0109 m_lastPoint = event->pos(); 0110 m_drawing = true; 0111 setCursor(m_drawCursor); 0112 m_drawEventCreationTime = QTime::currentTime(); 0113 DrawEvent drawEvent(m_penWidth, m_penColor); 0114 0115 switch (m_tool) { 0116 case Tool::Drawing: 0117 drawEvent.path.moveTo(event->pos()); 0118 break; 0119 case Tool::Rectangle: 0120 drawEvent.path.addRect(QRectF(event->pos().x(), event->pos().y(), 1, 1)); 0121 drawEvent.fill = true; 0122 break; 0123 case Tool::Circle: 0124 drawEvent.path.addEllipse(QRectF(event->pos().x(), event->pos().y(), 1, 1)); 0125 drawEvent.fill = true; 0126 break; 0127 } 0128 m_drawEventList.append(drawEvent); 0129 0130 m_eventIndex = m_drawEventList.count() - 1; 0131 } 0132 0133 void DrawingArea::mouseMoveEvent(QMouseEvent* e) 0134 { 0135 if (m_drawing || !(e->modifiers() & Qt::CTRL)) { 0136 setCursor(m_drawCursor); 0137 } 0138 if ((e->buttons() & Qt::LeftButton)) { 0139 QPoint currentPos = e->pos(); 0140 DrawEvent &drawEvent = currentDrawEvent(); 0141 0142 switch(m_tool) { 0143 case Tool::Drawing: 0144 drawEvent.lineTo(currentPos); 0145 break; 0146 case Tool::Rectangle: 0147 drawEvent.path.clear(); 0148 drawEvent.path.addRect(QRectF(currentPos.x(), currentPos.y(), 0149 m_lastPoint.x() - currentPos.x(), m_lastPoint.y() - currentPos.y())); 0150 break; 0151 case Tool::Circle: 0152 drawEvent.path.clear(); 0153 drawEvent.path.addEllipse(QRectF(currentPos.x(), currentPos.y(), 0154 m_lastPoint.x() - currentPos.x(), m_lastPoint.y() - currentPos.y())); 0155 break; 0156 } 0157 0158 m_drawEventCreationTime = QTime::currentTime(); 0159 update(); 0160 } 0161 } 0162 0163 void DrawingArea::mouseReleaseEvent(QMouseEvent* e) 0164 { 0165 if ((e->button() == Qt::LeftButton) && m_drawing) { 0166 QPoint currentPos = e->pos(); 0167 DrawEvent &drawEvent = currentDrawEvent(); 0168 switch(m_tool) { 0169 case Tool::Drawing: 0170 drawEvent.lineTo(currentPos); 0171 break; 0172 case Tool::Rectangle: 0173 drawEvent.path.clear(); 0174 drawEvent.path.addRect(QRectF(currentPos.x(), currentPos.y(), 0175 m_lastPoint.x() - currentPos.x(), m_lastPoint.y() - currentPos.y())); 0176 break; 0177 case Tool::Circle: 0178 drawEvent.path.clear(); 0179 drawEvent.path.addEllipse(QRectF(currentPos.x(), currentPos.y(), 0180 m_lastPoint.x() - currentPos.x(), m_lastPoint.y() - currentPos.y())); 0181 break; 0182 } 0183 0184 m_drawing = false; 0185 Q_EMIT canUndoChanged(); 0186 update(); 0187 } 0188 } 0189 0190 bool DrawingArea::canUndo() const 0191 { 0192 return !m_drawEventList.isEmpty(); 0193 } 0194 0195 void DrawingArea::undo() 0196 { 0197 m_drawing = false; 0198 if (!m_drawEventList.isEmpty()) { 0199 m_drawEventList.removeLast(); 0200 if (m_drawEventList.isEmpty()) { 0201 Q_EMIT canUndoChanged(); 0202 } 0203 } 0204 update(); 0205 } 0206 0207 0208 void DrawingArea::ensureNewDrawEvent() 0209 { 0210 m_drawEventCreationTime = QTime(); 0211 } 0212 0213 rust::Box<LyonBuilder> painterPathToBuilder(const QPainterPath &path) 0214 { 0215 auto lyonBuilder = new_builder(); 0216 for (int i = 0; i < path.elementCount(); i++) { 0217 const auto element = path.elementAt(i); 0218 if (element.isLineTo()) { 0219 lyonBuilder->line_to(LyonPoint { static_cast<float>(element.x), static_cast<float>(element.y) }); 0220 } else if (element.isMoveTo()) { 0221 lyonBuilder->move_to(LyonPoint { static_cast<float>(element.x), static_cast<float>(element.y) }); 0222 } else if (element.type == QPainterPath::ElementType::CurveToElement) { 0223 // Cubic is encoded with ctrl1 -> CurveToElement, ctrl2 -> CurveToDataElement and to -> CurveToDataElement 0224 Q_ASSERT(i + 2 < path.elementCount() && "CurveToElement doesn't have data"); 0225 const auto ctrl1 = path.elementAt(i); 0226 const auto ctrl2 = path.elementAt(i + 1); 0227 const auto to = path.elementAt(i + 2); 0228 lyonBuilder->cubic_bezier_to( 0229 LyonPoint { static_cast<float>(ctrl1.x), static_cast<float>(ctrl1.y) }, 0230 LyonPoint { static_cast<float>(ctrl2.x), static_cast<float>(ctrl2.y) }, 0231 LyonPoint { static_cast<float>(to.x), static_cast<float>(to.y) } 0232 ); 0233 i += 2; // we analysed tree elements instead of just one 0234 } else { 0235 Q_ASSERT(false && "Should not happen"); 0236 } 0237 } 0238 0239 return lyonBuilder; 0240 } 0241 0242 void geometryFromLyon(QSGGeometry *geometry, LyonGeometry lyonGeometry) 0243 { 0244 QSGGeometry::Point2D *points = geometry->vertexDataAsPoint2D(); 0245 std::size_t i = 0; 0246 for (const auto &vertice: lyonGeometry.vertices) { 0247 points[i].set(vertice.x, vertice.y); 0248 i++; 0249 } 0250 0251 quint16* indices = geometry->indexDataAsUShort(); 0252 i = 0; 0253 for (const auto indice: lyonGeometry.indices) { 0254 indices[i] = indice; 0255 i++; 0256 } 0257 } 0258 0259 QSGNode *DrawingArea::createRootNode() 0260 { 0261 QSGGeometryNode *root = nullptr; 0262 const int vertexCount = 4; 0263 const int indexCount = 2 * 3; 0264 root = new QSGGeometryNode; 0265 QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount, indexCount); 0266 geometry->setDrawingMode(GL_TRIANGLES); 0267 0268 geometry->vertexDataAsPoint2D()[0].set(0, 0); 0269 geometry->vertexDataAsPoint2D()[1].set(width(), 0); 0270 geometry->vertexDataAsPoint2D()[2].set(width(), height()); 0271 geometry->vertexDataAsPoint2D()[3].set(0, height()); 0272 0273 quint16 *indices = geometry->indexDataAsUShort(); 0274 indices[0] = 0; 0275 indices[1] = 1; 0276 indices[2] = 2; 0277 indices[3] = 0; 0278 indices[4] = 3; 0279 indices[5] = 2; 0280 0281 root->setGeometry(geometry); 0282 root->setFlag(QSGNode::OwnsGeometry); 0283 root->setFlag(QSGNode::OwnsMaterial); 0284 0285 QSGFlatColorMaterial *material = new QSGFlatColorMaterial; 0286 material->setColor(QColor("white")); 0287 root->setMaterial(material); 0288 0289 m_lastNumberOfEvent = m_drawEventList.count(); 0290 // crate drawing nodes 0291 for (const auto &drawEvent : qAsConst(m_drawEventList)) { 0292 auto node = new QSGGeometryNode; 0293 auto builder = painterPathToBuilder(drawEvent.path); 0294 LyonGeometry lyonGeometry; 0295 if (!drawEvent.fill) { 0296 lyonGeometry = build_stroke(std::move(builder), drawEvent.penWidth); 0297 } else { 0298 lyonGeometry = build_fill(std::move(builder)); 0299 } 0300 QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0301 lyonGeometry.vertices.size(), lyonGeometry.indices.size()); 0302 0303 geometry->setIndexDataPattern(QSGGeometry::StaticPattern); 0304 geometry->setDrawingMode(GL_TRIANGLES); 0305 node->setGeometry(geometry); 0306 node->setFlag(QSGNode::OwnsGeometry); 0307 0308 QSGFlatColorMaterial *material = new QSGFlatColorMaterial; 0309 material->setColor(drawEvent.penColor); 0310 node->setMaterial(material); 0311 node->setFlag(QSGNode::OwnsMaterial); 0312 root->appendChildNode(node); 0313 0314 geometryFromLyon(geometry, std::move(lyonGeometry)); 0315 node->markDirty(QSGNode::DirtyGeometry); 0316 } 0317 return root; 0318 } 0319 0320 QSGNode *DrawingArea::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData* updatePaintNodeData) 0321 { 0322 Q_UNUSED(updatePaintNodeData) 0323 0324 if(!oldNode) { 0325 return createRootNode(); 0326 } 0327 0328 auto root = static_cast<QSGGeometryNode *>(oldNode); 0329 if (m_needUpdate) { 0330 auto geometry = root->geometry(); 0331 geometry->vertexDataAsPoint2D()[0].set(0, 0); 0332 geometry->vertexDataAsPoint2D()[1].set(width(), 0); 0333 geometry->vertexDataAsPoint2D()[2].set(width(), height()); 0334 geometry->vertexDataAsPoint2D()[3].set(0, height()); 0335 } 0336 0337 int count = m_drawEventList.count(); 0338 if (m_lastNumberOfEvent > count) { 0339 // remove some nodes removed by undo 0340 for (int i = m_lastNumberOfEvent; i > count; i--) { 0341 root->removeChildNode(root->childAtIndex(i - 1)); 0342 } 0343 m_lastNumberOfEvent = count; 0344 } 0345 0346 // update already existing last child node since it is the only one who potentionally was updated 0347 if (m_lastNumberOfEvent != 0) { 0348 auto node = static_cast<QSGGeometryNode *>(root->childAtIndex(m_lastNumberOfEvent - 1)); 0349 const auto &drawEvent = m_drawEventList[m_lastNumberOfEvent - 1]; 0350 0351 auto builder = painterPathToBuilder(drawEvent.path); 0352 LyonGeometry lyonGeometry; 0353 if (!drawEvent.fill) { 0354 lyonGeometry = build_stroke(std::move(builder), drawEvent.penWidth); 0355 } else { 0356 lyonGeometry = build_fill(std::move(builder)); 0357 } 0358 auto geometry = node->geometry(); 0359 geometry->allocate(lyonGeometry.vertices.size(), lyonGeometry.indices.size()); 0360 0361 geometryFromLyon(geometry, std::move(lyonGeometry)); 0362 0363 node->markDirty(QSGNode::DirtyGeometry); 0364 } 0365 0366 for (int eventIndex = m_lastNumberOfEvent; eventIndex < m_drawEventList.count(); eventIndex++) { 0367 m_lastNumberOfEvent = m_drawEventList.count(); 0368 const auto drawEvent = m_drawEventList[eventIndex]; 0369 auto node = new QSGGeometryNode; 0370 auto builder = painterPathToBuilder(drawEvent.path); 0371 LyonGeometry lyonGeometry; 0372 if (!drawEvent.fill) { 0373 lyonGeometry = build_stroke(std::move(builder), drawEvent.penWidth); 0374 } else { 0375 lyonGeometry = build_fill(std::move(builder)); 0376 } 0377 QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0378 lyonGeometry.vertices.size(), lyonGeometry.indices.size()); 0379 0380 geometry->setIndexDataPattern(QSGGeometry::StaticPattern); 0381 geometry->setDrawingMode(GL_TRIANGLES); 0382 node->setGeometry(geometry); 0383 node->setFlag(QSGNode::OwnsGeometry); 0384 0385 QSGFlatColorMaterial *material = new QSGFlatColorMaterial; 0386 material->setColor(drawEvent.penColor); 0387 node->setMaterial(material); 0388 node->setFlag(QSGNode::OwnsMaterial); 0389 root->appendChildNode(node); 0390 0391 geometryFromLyon(geometry, std::move(lyonGeometry)); 0392 0393 node->markDirty(QSGNode::DirtyGeometry); 0394 } 0395 0396 return root; 0397 } 0398 0399 0400 #include "moc_drawingarea.cpp"