diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index dd88a9e161..899d586843 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -523,8 +523,14 @@ PRIVATE editor/photo_editor_controls.h editor/photo_editor_layer_widget.cpp editor/photo_editor_layer_widget.h + editor/scene.cpp + editor/scene.h editor/scene_item_base.cpp editor/scene_item_base.h + editor/scene_item_canvas.cpp + editor/scene_item_canvas.h + editor/scene_item_line.cpp + editor/scene_item_line.h editor/scene_item_sticker.cpp editor/scene_item_sticker.h editor/stickers_panel_controller.cpp diff --git a/Telegram/SourceFiles/editor/editor_paint.cpp b/Telegram/SourceFiles/editor/editor_paint.cpp index ffdd975b9a..cdb38be342 100644 --- a/Telegram/SourceFiles/editor/editor_paint.cpp +++ b/Telegram/SourceFiles/editor/editor_paint.cpp @@ -7,13 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "editor/editor_paint.h" +#include "editor/scene.h" #include "editor/scene_item_base.h" +#include "editor/scene_item_canvas.h" #include "editor/scene_item_sticker.h" #include "editor/controllers.h" #include "base/event_filter.h" -#include -#include #include namespace Editor { @@ -27,15 +27,17 @@ constexpr auto kViewStyle = "QGraphicsView {\ border: 0px\ }"_cs; -std::shared_ptr EnsureScene(PhotoModifications &mods) { +std::shared_ptr EnsureScene( + PhotoModifications &mods, + const QSize &size) { if (!mods.paint) { - mods.paint = std::make_shared(nullptr); + mods.paint = std::make_shared(QRectF(QPointF(), size)); } return mods.paint; } -auto GroupsFilter(QGraphicsItem *i) { - return i->type() == QGraphicsItemGroup::Type; +auto Filter(QGraphicsItem *i) { + return i->type() != ItemCanvas::Type; } } // namespace @@ -46,8 +48,8 @@ Paint::Paint( const QSize &imageSize, std::shared_ptr controllers) : RpWidget(parent) -, _lastZ(std::make_shared(0.)) -, _scene(EnsureScene(modifications)) +, _lastZ(std::make_shared(9000.)) +, _scene(EnsureScene(modifications, imageSize)) , _view(base::make_unique_q(_scene.get(), this)) , _imageSize(imageSize) { Expects(modifications.paint != nullptr); @@ -59,16 +61,18 @@ Paint::Paint( _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _view->setStyleSheet(kViewStyle.utf8()); - _scene->setSceneRect(0, 0, imageSize.width(), imageSize.height()); - - initDrawing(); + _scene->mousePresses( + ) | rpl::start_with_next([=] { + _hasUndo = true; + clearRedoList(); + }, lifetime()); // Undo / Redo. controllers->undoController->performRequestChanges( ) | rpl::start_with_next([=](const Undo &command) { const auto isUndo = (command == Undo::Undo); - const auto filtered = groups(isUndo + const auto filtered = filteredItems(isUndo ? Qt::DescendingOrder : Qt::AscendingOrder); @@ -136,56 +140,7 @@ void Paint::applyTransform(QRect geometry, int angle, bool flipped) { _view->setGeometry(QRect(QPoint(), size)); } -void Paint::initDrawing() { - using Result = base::EventFilterResult; - - auto callback = [=](not_null event) { - const auto type = event->type(); - const auto isPress = (type == QEvent::GraphicsSceneMousePress); - const auto isMove = (type == QEvent::GraphicsSceneMouseMove); - const auto isRelease = (type == QEvent::GraphicsSceneMouseRelease); - if (!isPress && !isMove && !isRelease) { - return Result::Continue; - } - const auto e = static_cast(event.get()); - - const auto &size = _brushData.size; - const auto &color = _brushData.color; - const auto mousePoint = e->scenePos(); - if (isPress) { - _hasUndo = true; - clearRedoList(); - - auto dot = _scene->addEllipse( - mousePoint.x() - size / 2, - mousePoint.y() - size / 2, - size, - size, - QPen(Qt::NoPen), - QBrush(color)); - _brushData.group = _scene->createItemGroup( - QList{ std::move(dot) }); - } - if (isMove && _brushData.group) { - _brushData.group->addToGroup(_scene->addLine( - _brushData.lastPoint.x(), - _brushData.lastPoint.y(), - mousePoint.x(), - mousePoint.y(), - QPen(color, size, Qt::SolidLine, Qt::RoundCap))); - } - if (isRelease) { - _brushData.group = nullptr; - } - _brushData.lastPoint = mousePoint; - - return Result::Cancel; - }; - - base::install_event_filter(this, _scene.get(), std::move(callback)); -} - -std::shared_ptr Paint::saveScene() const { +std::shared_ptr Paint::saveScene() const { _scene->clearSelection(); return _scene->items().empty() ? nullptr @@ -195,7 +150,7 @@ std::shared_ptr Paint::saveScene() const { } void Paint::cancel() { - const auto filtered = groups(Qt::AscendingOrder); + const auto filtered = filteredItems(Qt::AscendingOrder); if (filtered.empty()) { return; } @@ -229,17 +184,17 @@ void Paint::keepResult() { } bool Paint::hasUndo() const { - return ranges::any_of(groups(), &QGraphicsItem::isVisible); + return ranges::any_of(filteredItems(), &QGraphicsItem::isVisible); } bool Paint::hasRedo() const { return ranges::any_of( - groups(), + filteredItems(), [=](QGraphicsItem *i) { return isItemHidden(i); }); } void Paint::clearRedoList() { - const auto items = groups(Qt::AscendingOrder); + const auto items = filteredItems(Qt::AscendingOrder); auto &&filtered = ranges::views::all( items ) | ranges::views::filter( @@ -267,17 +222,17 @@ void Paint::updateUndoState() { _hasRedo = hasRedo(); } -std::vector Paint::groups(Qt::SortOrder order) const { +std::vector Paint::filteredItems(Qt::SortOrder order) const { const auto items = _scene->items(order); return ranges::views::all( items - ) | ranges::views::filter(GroupsFilter) | ranges::to_vector; + ) | ranges::views::filter(Filter) | ranges::to_vector; } void Paint::applyBrush(const Brush &brush) { - _brushData.color = brush.color; - _brushData.size = - (kMinBrush + float64(kMaxBrush - kMinBrush) * brush.sizeRatio); + _scene->applyBrush( + brush.color, + (kMinBrush + float64(kMaxBrush - kMinBrush) * brush.sizeRatio)); } } // namespace Editor diff --git a/Telegram/SourceFiles/editor/editor_paint.h b/Telegram/SourceFiles/editor/editor_paint.h index 0f1b8fbecb..eba8fc3d43 100644 --- a/Telegram/SourceFiles/editor/editor_paint.h +++ b/Telegram/SourceFiles/editor/editor_paint.h @@ -11,13 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/photo_editor_common.h" -class QGraphicsItemGroup; +class QGraphicsItem; class QGraphicsView; namespace Editor { struct Controllers; class ItemBase; +class Scene; // Paint control. class Paint final : public Ui::RpWidget { @@ -28,7 +29,7 @@ public: const QSize &imageSize, std::shared_ptr controllers); - [[nodiscard]] std::shared_ptr saveScene() const; + [[nodiscard]] std::shared_ptr saveScene() const; void applyTransform(QRect geometry, int angle, bool flipped); void applyBrush(const Brush &brush); @@ -42,7 +43,6 @@ private: bool undid = false; }; - void initDrawing(); bool hasUndo() const; bool hasRedo() const; void clearRedoList(); @@ -50,25 +50,17 @@ private: bool isItemToRemove(not_null item) const; bool isItemHidden(not_null item) const; - std::vector groups( + std::vector filteredItems( Qt::SortOrder order = Qt::DescendingOrder) const; const std::shared_ptr _lastZ; - const std::shared_ptr _scene; + const std::shared_ptr _scene; const base::unique_qptr _view; const QSize _imageSize; std::vector _previousItems; QList _itemsToRemove; - struct { - QPointF lastPoint; - - float size = 1.; - QColor color; - QGraphicsItemGroup *group; - } _brushData; - rpl::variable _hasUndo = true; rpl::variable _hasRedo = true; diff --git a/Telegram/SourceFiles/editor/photo_editor_common.cpp b/Telegram/SourceFiles/editor/photo_editor_common.cpp index b5c49c9339..33530cd68a 100644 --- a/Telegram/SourceFiles/editor/photo_editor_common.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_common.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "editor/photo_editor_common.h" +#include "editor/scene.h" + namespace Editor { QImage ImageModified(QImage image, const PhotoModifications &mods) { diff --git a/Telegram/SourceFiles/editor/photo_editor_common.h b/Telegram/SourceFiles/editor/photo_editor_common.h index 3c97080585..89cb41d228 100644 --- a/Telegram/SourceFiles/editor/photo_editor_common.h +++ b/Telegram/SourceFiles/editor/photo_editor_common.h @@ -7,10 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include - namespace Editor { +class Scene; + struct PhotoEditorMode { enum class Mode { Transform, @@ -28,7 +28,7 @@ struct PhotoModifications { int angle = 0; bool flipped = false; QRect crop; - std::shared_ptr paint = nullptr; + std::shared_ptr paint = nullptr; [[nodiscard]] bool empty() const; [[nodiscard]] explicit operator bool() const; diff --git a/Telegram/SourceFiles/editor/scene.cpp b/Telegram/SourceFiles/editor/scene.cpp new file mode 100644 index 0000000000..fd83e8b661 --- /dev/null +++ b/Telegram/SourceFiles/editor/scene.cpp @@ -0,0 +1,99 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "editor/scene.h" + +#include "editor/scene_item_canvas.h" +#include "editor/scene_item_line.h" +#include "ui/rp_widget.h" + +#include +#include +#include + +namespace Editor { + +Scene::Scene(const QRectF &rect) +: QGraphicsScene(rect) +, _canvas(new ItemCanvas) { + clearPath(); + + addItem(_canvas); + + _canvas->paintRequest( + ) | rpl::start_with_next([=](not_null p) { + p->fillRect(sceneRect(), Qt::transparent); + + p->setPen(QPen(_brushData.color, _brushData.size)); + p->drawPath(_path); + }, _lifetime); +} + +void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) { + QGraphicsScene::mousePressEvent(event); + if (event->isAccepted() || (event->button() == Qt::RightButton)) { + return; + } + _mousePresses.fire({}); + _path.moveTo(event->scenePos()); + _drawing = true; +} + +void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + QGraphicsScene::mouseReleaseEvent(event); + if (event->isAccepted() || (event->button() == Qt::RightButton)) { + return; + } + _path.lineTo(event->scenePos()); + addLineItem(); + _drawing = false; +} + +void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + QGraphicsScene::mouseMoveEvent(event); + if (event->isAccepted() + || (event->button() == Qt::RightButton) + || !_drawing) { + 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(); +} + +void Scene::applyBrush(const QColor &color, float size) { + _brushData.color = color; + _brushData.size = size; +} + +void Scene::clearPath() { + _path = QPainterPath(); + _path.setFillRule(Qt::WindingFill); +} + +rpl::producer<> Scene::mousePresses() const { + return _mousePresses.events(); +} + +Scene::~Scene() { +} + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene.h b/Telegram/SourceFiles/editor/scene.h new file mode 100644 index 0000000000..71a7f4ab8b --- /dev/null +++ b/Telegram/SourceFiles/editor/scene.h @@ -0,0 +1,59 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include + +#include + +class QGraphicsSceneMouseEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneMouseEvent; + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Editor { + +class ItemCanvas; + +class Scene final : public QGraphicsScene { +public: + Scene(const QRectF &rect); + ~Scene(); + void applyBrush(const QColor &color, float size); + + [[nodiscard]] rpl::producer<> mousePresses() const; + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + 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.; + + struct { + float size = 1.; + QColor color; + } _brushData; + + rpl::event_stream<> _mousePresses; + rpl::lifetime _lifetime; + +}; + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene_item_canvas.cpp b/Telegram/SourceFiles/editor/scene_item_canvas.cpp new file mode 100644 index 0000000000..5a5005e150 --- /dev/null +++ b/Telegram/SourceFiles/editor/scene_item_canvas.cpp @@ -0,0 +1,49 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "editor/scene_item_canvas.h" + +#include + +namespace Editor { + +ItemCanvas::ItemCanvas() { + setAcceptedMouseButtons(0); +} + +QRectF ItemCanvas::boundingRect() const { + return scene()->sceneRect(); +} + +void ItemCanvas::paint( + QPainter *p, + const QStyleOptionGraphicsItem *, + QWidget *) { + _paintRequest.fire_copy(p); +} + +int ItemCanvas::type() const { + return Type; +} + +rpl::producer> ItemCanvas::paintRequest() const { + return _paintRequest.events(); +} + +bool ItemCanvas::collidesWithItem( + const QGraphicsItem *, + Qt::ItemSelectionMode) const { + return false; +} + +bool ItemCanvas::collidesWithPath( + const QPainterPath &, + Qt::ItemSelectionMode) const { + return false; +} + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene_item_canvas.h b/Telegram/SourceFiles/editor/scene_item_canvas.h new file mode 100644 index 0000000000..12f15c0404 --- /dev/null +++ b/Telegram/SourceFiles/editor/scene_item_canvas.h @@ -0,0 +1,41 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include + +namespace Editor { + +class ItemCanvas : public QGraphicsItem { +public: + enum { Type = UserType + 6 }; + + ItemCanvas(); + + QRectF boundingRect() const override; + void paint( + QPainter *p, + const QStyleOptionGraphicsItem *option, + QWidget *widget) override; + int type() const override; + + [[nodiscard]] rpl::producer> paintRequest() const; +protected: + bool collidesWithItem( + const QGraphicsItem *, + Qt::ItemSelectionMode) const override; + + bool collidesWithPath( + const QPainterPath &, + Qt::ItemSelectionMode) const override; +private: + rpl::event_stream> _paintRequest; + +}; + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene_item_line.cpp b/Telegram/SourceFiles/editor/scene_item_line.cpp new file mode 100644 index 0000000000..fe53f5d5ca --- /dev/null +++ b/Telegram/SourceFiles/editor/scene_item_line.cpp @@ -0,0 +1,62 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "editor/scene_item_line.h" + +#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); +} + +QRectF ItemLine::boundingRect() const { + return scene()->sceneRect(); +} + +void ItemLine::paint( + QPainter *p, + const QStyleOptionGraphicsItem *, + QWidget *) { + p->drawPixmap(0, 0, _pixmap); +} + +bool ItemLine::collidesWithItem( + const QGraphicsItem *, + Qt::ItemSelectionMode) const { + return false; +} +bool ItemLine::collidesWithPath( + const QPainterPath &, + Qt::ItemSelectionMode) const { + return false; +} + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/scene_item_line.h b/Telegram/SourceFiles/editor/scene_item_line.h new file mode 100644 index 0000000000..1f24186d65 --- /dev/null +++ b/Telegram/SourceFiles/editor/scene_item_line.h @@ -0,0 +1,41 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include + +namespace Editor { + +class ItemLine : public QGraphicsItem { +public: + enum { Type = UserType + 5 }; + + ItemLine( + const QPainterPath &path, + const QSize &size, + const QColor &brushColor, + float brushSize); + QRectF boundingRect() const override; + void paint( + QPainter *p, + const QStyleOptionGraphicsItem *option, + QWidget *widget) override; +protected: + bool collidesWithItem( + const QGraphicsItem *, + Qt::ItemSelectionMode) const override; + bool collidesWithPath( + const QPainterPath &, + Qt::ItemSelectionMode) const override; +private: + const QRectF _rect; + const QPixmap _pixmap; + +}; + +} // namespace Editor