diff --git a/Telegram/SourceFiles/editor/editor_paint.cpp b/Telegram/SourceFiles/editor/editor_paint.cpp index 4a12af1042..14b0540951 100644 --- a/Telegram/SourceFiles/editor/editor_paint.cpp +++ b/Telegram/SourceFiles/editor/editor_paint.cpp @@ -63,6 +63,11 @@ Paint::Paint( clearRedoList(); }, lifetime()); + _scene->addsItem( + ) | rpl::start_with_next([=] { + updateUndoState(); + }, lifetime()); + // Undo / Redo. controllers->undoController->performRequestChanges( ) | rpl::start_with_next([=](const Undo &command) { diff --git a/Telegram/SourceFiles/editor/scene.cpp b/Telegram/SourceFiles/editor/scene.cpp index 1912702ae6..a3f4b57bb5 100644 --- a/Telegram/SourceFiles/editor/scene.cpp +++ b/Telegram/SourceFiles/editor/scene.cpp @@ -11,93 +11,75 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/scene_item_line.h" #include "ui/rp_widget.h" -#include -#include #include namespace Editor { +namespace { + +bool SkipMouseEvent(not_null event) { + return event->isAccepted() || (event->button() == Qt::RightButton); +} + +} // namespace Scene::Scene(const QRectF &rect) : QGraphicsScene(rect) , _canvas(new ItemCanvas) { - clearPath(); - QGraphicsScene::addItem(_canvas); + _canvas->clearPixmap(); - _canvas->paintRequest( - ) | rpl::start_with_next([=](not_null p) { - p->fillRect(sceneRect(), Qt::transparent); - - p->setPen(QPen(_brushData.color, _brushData.size)); - p->drawPath(_path); + _canvas->grabContentRequests( + ) | rpl::start_with_next([=](ItemCanvas::Content &&content) { + const auto item = new ItemLine(std::move(content.pixmap)); + item->setPos(content.position); + addItem(item); + _canvas->setZValue(++_lastLineZ); }, _lifetime); } void Scene::addItem(not_null item) { item->setNumber(_itemNumber++); QGraphicsScene::addItem(item); + _addsItem.fire({}); } void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mousePressEvent(event); - if (event->isAccepted() || (event->button() == Qt::RightButton)) { + if (SkipMouseEvent(event)) { return; } + _canvas->handleMousePressEvent(event); _mousePresses.fire({}); - _path.moveTo(event->scenePos()); - _drawing = true; } void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseReleaseEvent(event); - if (event->isAccepted() || (event->button() == Qt::RightButton)) { + if (SkipMouseEvent(event)) { return; } - _path.lineTo(event->scenePos()); - addLineItem(); - _drawing = false; + _canvas->handleMouseReleaseEvent(event); } void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseMoveEvent(event); - if (event->isAccepted() - || (event->button() == Qt::RightButton) - || !_drawing) { + if (SkipMouseEvent(event)) { return; } - const auto scenePos = event->scenePos(); - _path.lineTo(scenePos); - _path.moveTo(scenePos); - _canvas->update(); -} - -void Scene::addLineItem() { - if (_path.capacity() < 3) { - return; - } - addItem(new ItemLine( - _path, - sceneRect().size().toSize() * cIntRetinaFactor(), - _brushData.color, - _brushData.size)); - _canvas->setZValue(++_lastLineZ); - clearPath(); + _canvas->handleMouseMoveEvent(event); } void Scene::applyBrush(const QColor &color, float size) { - _brushData.color = color; - _brushData.size = size; -} - -void Scene::clearPath() { - _path = QPainterPath(); - _path.setFillRule(Qt::WindingFill); + _canvas->applyBrush(color, size); } rpl::producer<> Scene::mousePresses() const { return _mousePresses.events(); } +rpl::producer<> Scene::addsItem() const { + return _addsItem.events(); +} + std::vector Scene::items(Qt::SortOrder order) const { using Item = QGraphicsItem; auto rawItems = QGraphicsScene::items(); diff --git a/Telegram/SourceFiles/editor/scene.h b/Telegram/SourceFiles/editor/scene.h index 1ae75f5c26..46b5cf111d 100644 --- a/Telegram/SourceFiles/editor/scene.h +++ b/Telegram/SourceFiles/editor/scene.h @@ -11,8 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include -class QGraphicsSceneMouseEvent; -class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent; namespace Ui { @@ -33,6 +31,7 @@ public: [[nodiscard]] std::vector items( Qt::SortOrder order = Qt::DescendingOrder) const; void addItem(not_null item); + [[nodiscard]] rpl::producer<> addsItem() const; [[nodiscard]] rpl::producer<> mousePresses() const; protected: @@ -40,24 +39,13 @@ protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; private: - void clearPath(); - void addLineItem(); - const not_null _canvas; - QPainterPath _path; - bool _drawing = false; - float64 _lastLineZ = 0.; - int _itemNumber = 0; - struct { - float size = 1.; - QColor color; - } _brushData; - rpl::event_stream<> _mousePresses; + rpl::event_stream<> _addsItem; rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/editor/scene_item_canvas.cpp b/Telegram/SourceFiles/editor/scene_item_canvas.cpp index 5a5005e150..5b85357cf8 100644 --- a/Telegram/SourceFiles/editor/scene_item_canvas.cpp +++ b/Telegram/SourceFiles/editor/scene_item_canvas.cpp @@ -8,32 +8,197 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/scene_item_canvas.h" #include +#include namespace Editor { +namespace { + +QRectF NormalizedRect(const QPointF& p1, const QPointF& p2) { + return QRectF( + std::min(p1.x(), p2.x()), + std::min(p1.y(), p2.y()), + std::abs(p2.x() - p1.x()) + 1, + std::abs(p2.y() - p1.y()) + 1); +} + +std::vector InterpolatedPoints( + const QPointF &startPoint, + const QPointF &endPoint) { + std::vector points; + + const auto x1 = startPoint.x(); + const auto y1 = startPoint.y(); + const auto x2 = endPoint.x(); + const auto y2 = endPoint.y(); + + // Difference of x and y values. + const auto dx = x2 - x1; + const auto dy = y2 - y1; + + // Absolute values of differences. + const auto ix = std::abs(dx); + const auto iy = std::abs(dy); + + // Larger of the x and y differences. + const auto inc = ix > iy ? ix : iy; + + // Plot location. + auto plotx = x1; + auto ploty = y1; + + auto x = 0; + auto y = 0; + + points.push_back(QPointF(plotx, ploty)); + + for (auto i = 0; i <= inc; i++) { + const auto xInc = x > inc; + const auto yInc = y > inc; + + x += ix; + y += iy; + + if (xInc) { + x -= inc; + plotx += 1 * ((dx < 0) ? -1 : 1); + } + + if (yInc) { + y -= inc; + ploty += 1 * ((dy < 0) ? -1 : 1); + } + + if (xInc || yInc) { + points.push_back(QPointF(plotx, ploty)); + } + } + return points; +} + +} // namespace ItemCanvas::ItemCanvas() { setAcceptedMouseButtons(0); } +void ItemCanvas::clearPixmap() { + _hq = nullptr; + _p = nullptr; + + _pixmap = QPixmap( + (scene()->sceneRect().size() * cIntRetinaFactor()).toSize()); + _pixmap.setDevicePixelRatio(cRetinaFactor()); + _pixmap.fill(Qt::transparent); + + _p = std::make_unique(&_pixmap); + _hq = std::make_unique(*_p); + _p->setPen(Qt::NoPen); + _p->setBrush(_brushData.color); +} + +void ItemCanvas::applyBrush(const QColor &color, float size) { + _brushData.color = color; + _brushData.size = size; + _p->setBrush(color); + _brushMargins = QMarginsF(size, size, size, size) / 2.; +} + QRectF ItemCanvas::boundingRect() const { return scene()->sceneRect(); } +void ItemCanvas::computeContentRect(const QPointF &p) { + if (!scene()) { + return; + } + const auto sceneSize = scene()->sceneRect().size(); + _contentRect = QRectF( + QPointF( + std::clamp(p.x() - _brushMargins.left(), 0., _contentRect.x()), + std::clamp(p.y() - _brushMargins.top(), 0., _contentRect.y()) + ), + QPointF( + std::clamp( + p.x() + _brushMargins.right(), + _contentRect.x() + _contentRect.width(), + sceneSize.width()), + std::clamp( + p.y() + _brushMargins.bottom(), + _contentRect.y() + _contentRect.height(), + sceneSize.height()) + )); +} + +void ItemCanvas::drawLine( + const QPointF ¤tPoint, + const QPointF &lastPoint) { + const auto halfBrushSize = _brushData.size / 2.; + const auto points = InterpolatedPoints(lastPoint, currentPoint); + + _rectToUpdate |= NormalizedRect(currentPoint, lastPoint) + _brushMargins; + + for (const auto &point : points) { + _p->drawEllipse(point, halfBrushSize, halfBrushSize); + } +} + +void ItemCanvas::handleMousePressEvent( + not_null e) { + _lastPoint = e->scenePos(); + _contentRect = QRectF(_lastPoint, _lastPoint); + _drawing = true; +} + +void ItemCanvas::handleMouseMoveEvent( + not_null e) { + if (!_drawing) { + return; + } + const auto scenePos = e->scenePos(); + drawLine(scenePos, _lastPoint); + update(_rectToUpdate); + computeContentRect(scenePos); + _lastPoint = scenePos; +} + +void ItemCanvas::handleMouseReleaseEvent( + not_null e) { + if (!_drawing) { + return; + } + _drawing = false; + + const auto scaledContentRect = QRectF( + _contentRect.x() * cRetinaFactor(), + _contentRect.y() * cRetinaFactor(), + _contentRect.width() * cRetinaFactor(), + _contentRect.height() * cRetinaFactor()); + + _grabContentRequests.fire({ + .pixmap = _pixmap.copy(scaledContentRect.toRect()), + .position = _contentRect.topLeft(), + }); + clearPixmap(); + update(); +} + void ItemCanvas::paint( QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) { - _paintRequest.fire_copy(p); + p->fillRect(_rectToUpdate, Qt::transparent); + p->drawPixmap(0, 0, _pixmap); + _rectToUpdate = QRectF(); +} + +rpl::producer ItemCanvas::grabContentRequests() const { + return _grabContentRequests.events(); } int ItemCanvas::type() const { return Type; } -rpl::producer> ItemCanvas::paintRequest() const { - return _paintRequest.events(); -} - bool ItemCanvas::collidesWithItem( const QGraphicsItem *, Qt::ItemSelectionMode) const { @@ -46,4 +211,9 @@ bool ItemCanvas::collidesWithPath( return false; } +ItemCanvas::~ItemCanvas() { + _hq = nullptr; + _p = nullptr; +} + } // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene_item_canvas.h b/Telegram/SourceFiles/editor/scene_item_canvas.h index 12f15c0404..4b810b2e2d 100644 --- a/Telegram/SourceFiles/editor/scene_item_canvas.h +++ b/Telegram/SourceFiles/editor/scene_item_canvas.h @@ -9,13 +9,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +class QGraphicsSceneMouseEvent; + namespace Editor { class ItemCanvas : public QGraphicsItem { public: enum { Type = UserType + 6 }; + struct Content { + QPixmap pixmap; + QPointF position; + }; + ItemCanvas(); + ~ItemCanvas(); + + void applyBrush(const QColor &color, float size); + void clearPixmap(); QRectF boundingRect() const override; void paint( @@ -24,7 +35,12 @@ public: QWidget *widget) override; int type() const override; - [[nodiscard]] rpl::producer> paintRequest() const; + void handleMousePressEvent(not_null event); + void handleMouseReleaseEvent(not_null event); + void handleMouseMoveEvent(not_null event); + + [[nodiscard]] rpl::producer grabContentRequests() const; + protected: bool collidesWithItem( const QGraphicsItem *, @@ -34,7 +50,28 @@ protected: const QPainterPath &, Qt::ItemSelectionMode) const override; private: - rpl::event_stream> _paintRequest; + void computeContentRect(const QPointF &p); + void drawLine(const QPointF ¤tPoint, const QPointF &lastPoint); + + bool _drawing = false; + + std::unique_ptr _hq; + std::unique_ptr _p; + + QRectF _rectToUpdate; + QRectF _contentRect; + QMarginsF _brushMargins; + + QPointF _lastPoint; + + QPixmap _pixmap; + + struct { + float size = 1.; + QColor color; + } _brushData; + + rpl::event_stream _grabContentRequests; }; diff --git a/Telegram/SourceFiles/editor/scene_item_line.cpp b/Telegram/SourceFiles/editor/scene_item_line.cpp index fe53f5d5ca..9a4c6349b6 100644 --- a/Telegram/SourceFiles/editor/scene_item_line.cpp +++ b/Telegram/SourceFiles/editor/scene_item_line.cpp @@ -10,35 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Editor { -namespace { -QPixmap PathToPixmap( - const QPainterPath &path, - const QSize &size, - const QColor &brushColor, - float brushSize) { - auto pixmap = QPixmap(size); - pixmap.setDevicePixelRatio(cRetinaFactor()); - pixmap.fill(Qt::transparent); - Painter p(&pixmap); - p.setPen(QPen(brushColor, brushSize)); - p.drawPath(path); - return pixmap; -} - -} // namespace - -ItemLine::ItemLine( - const QPainterPath &path, - const QSize &size, - const QColor &brushColor, - float brushSize) -: _pixmap(PathToPixmap(path, size, brushColor, brushSize)) { - Expects(path.capacity() > 0); +ItemLine::ItemLine(const QPixmap &&pixmap) +: _pixmap(std::move(pixmap)) +, _rect(QPointF(), _pixmap.size() / cRetinaFactor()) { } QRectF ItemLine::boundingRect() const { - return scene()->sceneRect(); + return _rect; } void ItemLine::paint( diff --git a/Telegram/SourceFiles/editor/scene_item_line.h b/Telegram/SourceFiles/editor/scene_item_line.h index ca9a9197e0..154b95f829 100644 --- a/Telegram/SourceFiles/editor/scene_item_line.h +++ b/Telegram/SourceFiles/editor/scene_item_line.h @@ -15,11 +15,7 @@ class ItemLine : public NumberedItem { public: enum { Type = UserType + 5 }; - ItemLine( - const QPainterPath &path, - const QSize &size, - const QColor &brushColor, - float brushSize); + ItemLine(const QPixmap &&pixmap); QRectF boundingRect() const override; void paint( QPainter *p, @@ -33,8 +29,8 @@ protected: const QPainterPath &, Qt::ItemSelectionMode) const override; private: - const QRectF _rect; const QPixmap _pixmap; + const QRectF _rect; };