1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "editor/editor_paint.h"
9
10 #include "ui/boxes/confirm_box.h"
11 #include "editor/controllers/controllers.h"
12 #include "editor/scene/scene.h"
13 #include "editor/scene/scene_item_canvas.h"
14 #include "editor/scene/scene_item_image.h"
15 #include "editor/scene/scene_item_sticker.h"
16 #include "lottie/lottie_single_player.h"
17 #include "storage/storage_media_prepare.h"
18 #include "ui/chat/attach/attach_prepare.h"
19 #include "ui/ui_utility.h"
20
21 #include <QGraphicsView>
22 #include <QtCore/QMimeData>
23
24 namespace Editor {
25 namespace {
26
27 constexpr auto kMaxBrush = 25.;
28 constexpr auto kMinBrush = 1.;
29
30 constexpr auto kViewStyle = "QGraphicsView {\
31 background-color: transparent;\
32 border: 0px\
33 }"_cs;
34
EnsureScene(PhotoModifications & mods,const QSize & size)35 std::shared_ptr<Scene> EnsureScene(
36 PhotoModifications &mods,
37 const QSize &size) {
38 if (!mods.paint) {
39 mods.paint = std::make_shared<Scene>(QRectF(QPointF(), size));
40 }
41 return mods.paint;
42 }
43
44 } // namespace
45
46 using ItemPtr = std::shared_ptr<QGraphicsItem>;
47
Paint(not_null<Ui::RpWidget * > parent,PhotoModifications & modifications,const QSize & imageSize,std::shared_ptr<Controllers> controllers)48 Paint::Paint(
49 not_null<Ui::RpWidget*> parent,
50 PhotoModifications &modifications,
51 const QSize &imageSize,
52 std::shared_ptr<Controllers> controllers)
53 : RpWidget(parent)
54 , _controllers(controllers)
55 , _scene(EnsureScene(modifications, imageSize))
56 , _view(base::make_unique_q<QGraphicsView>(_scene.get(), this))
57 , _imageSize(imageSize) {
58 Expects(modifications.paint != nullptr);
59
60 keepResult();
61
62 _view->show();
63 _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
64 _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
65 _view->setStyleSheet(kViewStyle.utf8());
66
67 // Undo / Redo.
68 controllers->undoController->performRequestChanges(
69 ) | rpl::start_with_next([=](const Undo &command) {
70 if (command == Undo::Undo) {
71 _scene->performUndo();
72 } else {
73 _scene->performRedo();
74 }
75
76 _hasUndo = _scene->hasUndo();
77 _hasRedo = _scene->hasRedo();
78 }, lifetime());
79
80 controllers->undoController->setCanPerformChanges(rpl::merge(
81 _hasUndo.value() | rpl::map([](bool enable) {
82 return UndoController::EnableRequest{
83 .command = Undo::Undo,
84 .enable = enable,
85 };
86 }),
87 _hasRedo.value() | rpl::map([](bool enable) {
88 return UndoController::EnableRequest{
89 .command = Undo::Redo,
90 .enable = enable,
91 };
92 })));
93
94 if (controllers->stickersPanelController) {
95 using ShowRequest = StickersPanelController::ShowRequest;
96
97 controllers->stickersPanelController->setShowRequestChanges(
98 controllers->stickersPanelController->stickerChosen(
99 ) | rpl::map_to(ShowRequest::HideAnimated));
100
101 controllers->stickersPanelController->stickerChosen(
102 ) | rpl::start_with_next([=](not_null<DocumentData*> document) {
103 const auto item = std::make_shared<ItemSticker>(
104 document,
105 itemBaseData());
106 _scene->addItem(item);
107 _scene->clearSelection();
108 }, lifetime());
109 }
110
111 rpl::merge(
112 controllers->stickersPanelController
113 ? controllers->stickersPanelController->stickerChosen(
114 ) | rpl::to_empty
115 : rpl::never<>() | rpl::type_erased(),
116 _scene->addsItem()
117 ) | rpl::start_with_next([=] {
118 clearRedoList();
119 updateUndoState();
120 }, lifetime());
121
122 _scene->removesItem(
123 ) | rpl::start_with_next([=] {
124 updateUndoState();
125 }, lifetime());
126
127 }
128
applyTransform(QRect geometry,int angle,bool flipped)129 void Paint::applyTransform(QRect geometry, int angle, bool flipped) {
130 if (geometry.isEmpty()) {
131 return;
132 }
133 setGeometry(geometry);
134 const auto size = geometry.size();
135
136 const auto rotatedImageSize = QTransform()
137 .rotate(angle)
138 .mapRect(QRect(QPoint(), _imageSize));
139
140 const auto ratioW = size.width() / float64(rotatedImageSize.width())
141 * (flipped ? -1 : 1);
142 const auto ratioH = size.height() / float64(rotatedImageSize.height());
143
144 _view->setTransform(QTransform().scale(ratioW, ratioH).rotate(angle));
145 _view->setGeometry(QRect(QPoint(), size));
146
147 _transform = {
148 .angle = angle,
149 .flipped = flipped,
150 .zoom = size.width() / float64(_scene->sceneRect().width()),
151 };
152 _scene->updateZoom(_transform.zoom);
153 }
154
saveScene() const155 std::shared_ptr<Scene> Paint::saveScene() const {
156 _scene->save(SaveState::Save);
157 return _scene->items().empty()
158 ? nullptr
159 : _scene;
160 }
161
restoreScene()162 void Paint::restoreScene() {
163 _scene->restore(SaveState::Save);
164 }
165
cancel()166 void Paint::cancel() {
167 _scene->restore(SaveState::Keep);
168 }
169
keepResult()170 void Paint::keepResult() {
171 _scene->save(SaveState::Keep);
172 }
173
clearRedoList()174 void Paint::clearRedoList() {
175 _scene->clearRedoList();
176
177 _hasRedo = false;
178 }
179
updateUndoState()180 void Paint::updateUndoState() {
181 _hasUndo = _scene->hasUndo();
182 _hasRedo = _scene->hasRedo();
183 }
184
applyBrush(const Brush & brush)185 void Paint::applyBrush(const Brush &brush) {
186 _scene->applyBrush(
187 brush.color,
188 (kMinBrush + float64(kMaxBrush - kMinBrush) * brush.sizeRatio));
189 }
190
handleMimeData(const QMimeData * data)191 void Paint::handleMimeData(const QMimeData *data) {
192 const auto add = [&](QImage image) {
193 if (image.isNull()) {
194 return;
195 }
196 if (!Ui::ValidateThumbDimensions(image.width(), image.height())) {
197 _controllers->showBox(
198 Box<Ui::InformBox>(tr::lng_edit_media_invalid_file(tr::now)));
199 return;
200 }
201
202 const auto item = std::make_shared<ItemImage>(
203 Ui::PixmapFromImage(std::move(image)),
204 itemBaseData());
205 _scene->addItem(item);
206 _scene->clearSelection();
207 };
208
209 using Error = Ui::PreparedList::Error;
210 auto result = data->hasUrls()
211 ? Storage::PrepareMediaList(
212 data->urls().mid(0, 1),
213 _imageSize.width() / 2)
214 : Ui::PreparedList(Error::EmptyFile, QString());
215 if (result.error == Error::None) {
216 add(base::take(result.files.front().preview));
217 } else if (data->hasImage()) {
218 add(qvariant_cast<QImage>(data->imageData()));
219 }
220 }
221
itemBaseData() const222 ItemBase::Data Paint::itemBaseData() const {
223 const auto s = _scene->sceneRect().toRect().size();
224 const auto size = std::min(s.width(), s.height()) / 2;
225 const auto x = s.width() / 2;
226 const auto y = s.height() / 2;
227 return ItemBase::Data{
228 .initialZoom = _transform.zoom,
229 .zPtr = _scene->lastZ(),
230 .size = size,
231 .x = x,
232 .y = y,
233 .flipped = _transform.flipped,
234 .rotation = -_transform.angle,
235 .imageSize = _imageSize,
236 };
237 }
238
239 } // namespace Editor
240