2021-02-13 04:29:31 +00:00
|
|
|
/*
|
|
|
|
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/editor_paint.h"
|
|
|
|
|
2021-05-06 21:47:49 +00:00
|
|
|
#include "boxes/confirm_box.h"
|
2021-03-21 12:38:33 +00:00
|
|
|
#include "editor/controllers/controllers.h"
|
|
|
|
#include "editor/scene/scene.h"
|
|
|
|
#include "editor/scene/scene_item_canvas.h"
|
2021-05-06 21:47:49 +00:00
|
|
|
#include "editor/scene/scene_item_image.h"
|
2021-03-21 12:38:33 +00:00
|
|
|
#include "editor/scene/scene_item_sticker.h"
|
2021-03-10 15:51:23 +00:00
|
|
|
#include "lottie/lottie_single_player.h"
|
2021-05-06 21:47:49 +00:00
|
|
|
#include "storage/storage_media_prepare.h"
|
|
|
|
#include "ui/chat/attach/attach_prepare.h"
|
2021-05-07 14:33:53 +00:00
|
|
|
#include "ui/ui_utility.h"
|
2021-02-13 04:29:31 +00:00
|
|
|
|
|
|
|
#include <QGraphicsView>
|
2021-05-06 21:47:49 +00:00
|
|
|
#include <QtCore/QMimeData>
|
2021-02-13 04:29:31 +00:00
|
|
|
|
|
|
|
namespace Editor {
|
|
|
|
namespace {
|
|
|
|
|
2021-02-16 03:45:05 +00:00
|
|
|
constexpr auto kMaxBrush = 25.;
|
|
|
|
constexpr auto kMinBrush = 1.;
|
|
|
|
|
2021-02-13 04:29:31 +00:00
|
|
|
constexpr auto kViewStyle = "QGraphicsView {\
|
|
|
|
background-color: transparent;\
|
|
|
|
border: 0px\
|
|
|
|
}"_cs;
|
|
|
|
|
2021-03-03 09:24:35 +00:00
|
|
|
std::shared_ptr<Scene> EnsureScene(
|
|
|
|
PhotoModifications &mods,
|
|
|
|
const QSize &size) {
|
2021-02-13 04:29:31 +00:00
|
|
|
if (!mods.paint) {
|
2021-03-03 09:24:35 +00:00
|
|
|
mods.paint = std::make_shared<Scene>(QRectF(QPointF(), size));
|
2021-02-13 04:29:31 +00:00
|
|
|
}
|
|
|
|
return mods.paint;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2021-03-10 15:51:23 +00:00
|
|
|
using ItemPtr = Scene::ItemPtr;
|
|
|
|
|
2021-02-13 04:29:31 +00:00
|
|
|
Paint::Paint(
|
|
|
|
not_null<Ui::RpWidget*> parent,
|
|
|
|
PhotoModifications &modifications,
|
2021-03-14 09:42:18 +00:00
|
|
|
const QSize &imageSize,
|
2021-02-23 10:34:12 +00:00
|
|
|
std::shared_ptr<Controllers> controllers)
|
2021-02-13 04:29:31 +00:00
|
|
|
: RpWidget(parent)
|
2021-05-06 21:47:49 +00:00
|
|
|
, _controllers(controllers)
|
2021-03-03 09:24:35 +00:00
|
|
|
, _lastZ(std::make_shared<float64>(9000.))
|
|
|
|
, _scene(EnsureScene(modifications, imageSize))
|
2021-02-13 04:29:31 +00:00
|
|
|
, _view(base::make_unique_q<QGraphicsView>(_scene.get(), this))
|
2021-03-14 09:42:18 +00:00
|
|
|
, _imageSize(imageSize) {
|
2021-02-13 04:29:31 +00:00
|
|
|
Expects(modifications.paint != nullptr);
|
|
|
|
|
2021-03-14 09:42:18 +00:00
|
|
|
keepResult();
|
|
|
|
|
2021-02-13 04:29:31 +00:00
|
|
|
_view->show();
|
|
|
|
_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
_view->setStyleSheet(kViewStyle.utf8());
|
|
|
|
|
2021-03-14 09:42:18 +00:00
|
|
|
// Undo / Redo.
|
2021-02-23 10:34:12 +00:00
|
|
|
controllers->undoController->performRequestChanges(
|
2021-03-14 09:42:18 +00:00
|
|
|
) | rpl::start_with_next([=](const Undo &command) {
|
|
|
|
const auto isUndo = (command == Undo::Undo);
|
|
|
|
|
2021-03-14 09:44:49 +00:00
|
|
|
const auto filtered = _scene->items(isUndo
|
2021-03-14 09:42:18 +00:00
|
|
|
? Qt::DescendingOrder
|
|
|
|
: Qt::AscendingOrder);
|
|
|
|
|
2021-03-10 15:51:23 +00:00
|
|
|
auto proj = [&](const ItemPtr &i) {
|
2021-03-14 09:42:18 +00:00
|
|
|
return isUndo ? i->isVisible() : isItemHidden(i);
|
|
|
|
};
|
|
|
|
const auto it = ranges::find_if(filtered, std::move(proj));
|
|
|
|
if (it != filtered.end()) {
|
|
|
|
(*it)->setVisible(!isUndo);
|
|
|
|
}
|
|
|
|
|
|
|
|
_hasUndo = hasUndo();
|
|
|
|
_hasRedo = hasRedo();
|
|
|
|
}, lifetime());
|
|
|
|
|
2021-02-23 10:34:12 +00:00
|
|
|
controllers->undoController->setCanPerformChanges(rpl::merge(
|
2021-03-14 09:42:18 +00:00
|
|
|
_hasUndo.value() | rpl::map([](bool enable) {
|
|
|
|
return UndoController::EnableRequest{
|
|
|
|
.command = Undo::Undo,
|
|
|
|
.enable = enable,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
_hasRedo.value() | rpl::map([](bool enable) {
|
|
|
|
return UndoController::EnableRequest{
|
|
|
|
.command = Undo::Redo,
|
|
|
|
.enable = enable,
|
|
|
|
};
|
|
|
|
})));
|
2021-02-23 10:35:23 +00:00
|
|
|
|
|
|
|
if (controllers->stickersPanelController) {
|
2021-05-04 17:57:52 +00:00
|
|
|
using ShowRequest = StickersPanelController::ShowRequest;
|
|
|
|
|
2021-02-23 10:35:23 +00:00
|
|
|
controllers->stickersPanelController->setShowRequestChanges(
|
|
|
|
controllers->stickersPanelController->stickerChosen(
|
2021-05-04 17:57:52 +00:00
|
|
|
) | rpl::map_to(ShowRequest::HideAnimated));
|
2021-02-23 15:35:40 +00:00
|
|
|
|
|
|
|
controllers->stickersPanelController->stickerChosen(
|
|
|
|
) | rpl::start_with_next([=](not_null<DocumentData*> document) {
|
2021-03-10 15:51:23 +00:00
|
|
|
const auto item = std::make_shared<ItemSticker>(
|
2021-03-10 07:45:42 +00:00
|
|
|
document,
|
2021-07-04 12:57:07 +00:00
|
|
|
itemBaseData());
|
2021-02-23 15:35:40 +00:00
|
|
|
_scene->addItem(item);
|
|
|
|
_scene->clearSelection();
|
|
|
|
}, lifetime());
|
2021-02-23 10:35:23 +00:00
|
|
|
}
|
2021-03-10 11:47:59 +00:00
|
|
|
|
|
|
|
rpl::merge(
|
|
|
|
controllers->stickersPanelController
|
|
|
|
? controllers->stickersPanelController->stickerChosen(
|
|
|
|
) | rpl::to_empty
|
2021-07-06 17:25:46 +00:00
|
|
|
: rpl::never<>() | rpl::type_erased(),
|
2021-05-08 14:17:30 +00:00
|
|
|
_scene->addsItem()
|
2021-03-10 11:47:59 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
clearRedoList();
|
2021-05-08 14:17:30 +00:00
|
|
|
updateUndoState();
|
2021-03-10 11:47:59 +00:00
|
|
|
}, lifetime());
|
2021-07-04 14:22:57 +00:00
|
|
|
|
|
|
|
_scene->removesItem(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
updateUndoState();
|
|
|
|
}, lifetime());
|
|
|
|
|
2021-02-13 04:29:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Paint::applyTransform(QRect geometry, int angle, bool flipped) {
|
|
|
|
if (geometry.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setGeometry(geometry);
|
|
|
|
const auto size = geometry.size();
|
|
|
|
|
|
|
|
const auto rotatedImageSize = QMatrix()
|
|
|
|
.rotate(angle)
|
|
|
|
.mapRect(QRect(QPoint(), _imageSize));
|
|
|
|
|
|
|
|
const auto ratioW = size.width() / float64(rotatedImageSize.width())
|
|
|
|
* (flipped ? -1 : 1);
|
|
|
|
const auto ratioH = size.height() / float64(rotatedImageSize.height());
|
|
|
|
|
|
|
|
_view->setTransform(QTransform().scale(ratioW, ratioH).rotate(angle));
|
|
|
|
_view->setGeometry(QRect(QPoint(), size));
|
2021-03-10 07:45:42 +00:00
|
|
|
|
2021-03-21 17:00:56 +00:00
|
|
|
_transform = {
|
|
|
|
.angle = angle,
|
|
|
|
.flipped = flipped,
|
|
|
|
.zoom = size.width() / float64(_scene->sceneRect().width()),
|
|
|
|
};
|
2021-02-13 04:29:31 +00:00
|
|
|
}
|
|
|
|
|
2021-03-03 09:24:35 +00:00
|
|
|
std::shared_ptr<Scene> Paint::saveScene() const {
|
2021-02-23 15:35:40 +00:00
|
|
|
_scene->clearSelection();
|
2021-02-20 07:52:48 +00:00
|
|
|
return _scene->items().empty()
|
|
|
|
? nullptr
|
|
|
|
: ranges::none_of(_scene->items(), &QGraphicsItem::isVisible)
|
|
|
|
? nullptr
|
|
|
|
: _scene;
|
2021-02-13 04:29:31 +00:00
|
|
|
}
|
|
|
|
|
2021-03-14 09:39:55 +00:00
|
|
|
void Paint::cancel() {
|
2021-03-21 17:07:52 +00:00
|
|
|
_scene->clearSelection();
|
2021-05-08 15:02:41 +00:00
|
|
|
_scene->cancelDrawing();
|
2021-03-21 17:07:52 +00:00
|
|
|
|
2021-03-14 09:44:49 +00:00
|
|
|
const auto filtered = _scene->items(Qt::AscendingOrder);
|
2021-03-14 09:39:55 +00:00
|
|
|
if (filtered.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:51:23 +00:00
|
|
|
for (const auto &item : filtered) {
|
2021-03-14 09:42:18 +00:00
|
|
|
const auto it = ranges::find(
|
|
|
|
_previousItems,
|
2021-03-10 15:51:23 +00:00
|
|
|
item,
|
2021-03-14 09:42:18 +00:00
|
|
|
&SavedItem::item);
|
|
|
|
if (it == end(_previousItems)) {
|
2021-03-10 15:51:23 +00:00
|
|
|
_scene->removeItem(item);
|
2021-03-14 09:39:55 +00:00
|
|
|
} else {
|
2021-03-14 09:42:18 +00:00
|
|
|
it->item->setVisible(!it->undid);
|
2021-03-14 09:39:55 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-14 09:42:18 +00:00
|
|
|
|
|
|
|
_itemsToRemove.clear();
|
2021-03-14 09:39:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Paint::keepResult() {
|
2021-03-21 17:07:52 +00:00
|
|
|
_scene->clearSelection();
|
2021-05-08 15:02:41 +00:00
|
|
|
_scene->cancelDrawing();
|
2021-03-21 17:07:52 +00:00
|
|
|
|
2021-03-14 09:42:18 +00:00
|
|
|
for (const auto &item : _itemsToRemove) {
|
|
|
|
_scene->removeItem(item);
|
|
|
|
}
|
2021-03-10 15:51:23 +00:00
|
|
|
_itemsToRemove.clear();
|
2021-03-14 09:42:18 +00:00
|
|
|
|
|
|
|
const auto items = _scene->items();
|
|
|
|
_previousItems = ranges::views::all(
|
|
|
|
items
|
2021-03-10 15:51:23 +00:00
|
|
|
) | ranges::views::transform([=](ItemPtr i) -> SavedItem {
|
2021-03-14 09:42:18 +00:00
|
|
|
return { i, !i->isVisible() };
|
|
|
|
}) | ranges::to_vector;
|
2021-03-14 09:39:55 +00:00
|
|
|
}
|
|
|
|
|
2021-03-14 09:42:18 +00:00
|
|
|
bool Paint::hasUndo() const {
|
2021-03-14 09:44:49 +00:00
|
|
|
return ranges::any_of(_scene->items(), &QGraphicsItem::isVisible);
|
2021-03-14 09:42:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Paint::hasRedo() const {
|
|
|
|
return ranges::any_of(
|
2021-03-14 09:44:49 +00:00
|
|
|
_scene->items(),
|
2021-03-10 15:51:23 +00:00
|
|
|
[=](const ItemPtr &i) { return isItemHidden(i); });
|
2021-03-14 09:42:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Paint::clearRedoList() {
|
2021-03-14 09:44:49 +00:00
|
|
|
const auto items = _scene->items(Qt::AscendingOrder);
|
2021-03-14 09:42:18 +00:00
|
|
|
auto &&filtered = ranges::views::all(
|
|
|
|
items
|
|
|
|
) | ranges::views::filter(
|
2021-03-10 15:51:23 +00:00
|
|
|
[=](const ItemPtr &i) { return isItemHidden(i); }
|
2021-03-14 09:42:18 +00:00
|
|
|
);
|
|
|
|
|
2021-03-10 15:51:23 +00:00
|
|
|
ranges::for_each(std::move(filtered), [&](ItemPtr item) {
|
2021-03-14 09:42:18 +00:00
|
|
|
item->hide();
|
|
|
|
_itemsToRemove.push_back(item);
|
|
|
|
});
|
|
|
|
|
|
|
|
_hasRedo = false;
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:51:23 +00:00
|
|
|
bool Paint::isItemHidden(const ItemPtr &item) const {
|
2021-03-14 09:42:18 +00:00
|
|
|
return !item->isVisible() && !isItemToRemove(item);
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:51:23 +00:00
|
|
|
bool Paint::isItemToRemove(const ItemPtr &item) const {
|
|
|
|
return ranges::contains(_itemsToRemove, item);
|
2021-03-14 09:42:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Paint::updateUndoState() {
|
|
|
|
_hasUndo = hasUndo();
|
|
|
|
_hasRedo = hasRedo();
|
|
|
|
}
|
|
|
|
|
2021-02-16 03:45:05 +00:00
|
|
|
void Paint::applyBrush(const Brush &brush) {
|
2021-03-03 09:24:35 +00:00
|
|
|
_scene->applyBrush(
|
|
|
|
brush.color,
|
|
|
|
(kMinBrush + float64(kMaxBrush - kMinBrush) * brush.sizeRatio));
|
2021-02-16 03:45:05 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 21:47:49 +00:00
|
|
|
void Paint::handleMimeData(const QMimeData *data) {
|
|
|
|
const auto add = [&](QImage image) {
|
|
|
|
if (image.isNull()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!Ui::ValidateThumbDimensions(image.width(), image.height())) {
|
|
|
|
_controllers->showBox(
|
|
|
|
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto item = std::make_shared<ItemImage>(
|
2021-05-07 14:33:53 +00:00
|
|
|
Ui::PixmapFromImage(std::move(image)),
|
2021-07-04 12:57:07 +00:00
|
|
|
itemBaseData());
|
2021-05-06 21:47:49 +00:00
|
|
|
_scene->addItem(item);
|
|
|
|
_scene->clearSelection();
|
|
|
|
};
|
|
|
|
|
|
|
|
using Error = Ui::PreparedList::Error;
|
|
|
|
auto result = data->hasUrls()
|
|
|
|
? Storage::PrepareMediaList(
|
|
|
|
data->urls().mid(0, 1),
|
|
|
|
_imageSize.width() / 2)
|
|
|
|
: Ui::PreparedList(Error::EmptyFile, QString());
|
|
|
|
if (result.error == Error::None) {
|
|
|
|
add(base::take(result.files.front().preview));
|
|
|
|
} else if (data->hasImage()) {
|
|
|
|
add(qvariant_cast<QImage>(data->imageData()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-04 12:57:07 +00:00
|
|
|
ItemBase::Data Paint::itemBaseData() const {
|
|
|
|
const auto s = _scene->sceneRect().toRect().size();
|
|
|
|
const auto size = std::min(s.width(), s.height()) / 2;
|
|
|
|
const auto x = s.width() / 2;
|
|
|
|
const auto y = s.height() / 2;
|
|
|
|
return ItemBase::Data{
|
|
|
|
.zoomValue = _transform.zoom.value(),
|
|
|
|
.zPtr = _lastZ,
|
|
|
|
.size = size,
|
|
|
|
.x = x,
|
|
|
|
.y = y,
|
2021-07-04 13:13:54 +00:00
|
|
|
.flipped = _transform.flipped,
|
|
|
|
.rotation = -_transform.angle,
|
2021-07-06 21:13:40 +00:00
|
|
|
.imageSize = _imageSize,
|
2021-07-04 12:57:07 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-02-13 04:29:31 +00:00
|
|
|
} // namespace Editor
|