File indexing completed on 2024-04-28 15:52:04

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"