"Fossies" - the Fresh Open Source Software Archive 
Member "tdesktop-2.6.1/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp" (24 Feb 2021, 14525 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "emoji_sets_manager.cpp" see the
Fossies "Dox" file reference documentation.
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 "chat_helpers/emoji_sets_manager.h"
9
10 #include "mtproto/dedicated_file_loader.h"
11 #include "ui/wrap/vertical_layout.h"
12 #include "ui/wrap/fade_wrap.h"
13 #include "ui/widgets/buttons.h"
14 #include "ui/widgets/labels.h"
15 #include "ui/effects/animations.h"
16 #include "ui/effects/radial_animation.h"
17 #include "ui/emoji_config.h"
18 #include "core/application.h"
19 #include "main/main_account.h"
20 #include "mainwidget.h"
21 #include "app.h"
22 #include "storage/storage_cloud_blob.h"
23 #include "styles/style_layers.h"
24 #include "styles/style_boxes.h"
25 #include "styles/style_chat_helpers.h"
26
27 namespace Ui {
28 namespace Emoji {
29 namespace {
30
31 using namespace Storage::CloudBlob;
32
33 struct Set : public Blob {
34 QString previewPath;
35 };
36
37 inline auto PreviewPath(int i) {
38 return qsl(":/gui/emoji/set%1_preview.webp").arg(i);
39 }
40
41 const auto kSets = {
42 Set{ {0, 0, 0, "Mac"}, PreviewPath(0) },
43 Set{ {1, 713, 7'313'166, "Android"}, PreviewPath(1) },
44 Set{ {2, 714, 4'690'333, "Twemoji"}, PreviewPath(2) },
45 Set{ {3, 716, 5'968'021, "JoyPixels"}, PreviewPath(3) },
46 };
47
48 using Loading = MTP::DedicatedLoader::Progress;
49 using SetState = BlobState;
50
51 class Loader final : public BlobLoader {
52 public:
53 Loader(
54 not_null<Main::Session*> session,
55 int id,
56 MTP::DedicatedLoader::Location location,
57 const QString &folder,
58 int size);
59
60 void destroy() override;
61 void unpack(const QString &path) override;
62
63 private:
64 void fail() override;
65
66 };
67
68 class Inner : public Ui::RpWidget {
69 public:
70 Inner(QWidget *parent, not_null<Main::Session*> session);
71
72 private:
73 void setupContent();
74
75 const not_null<Main::Session*> _session;
76
77 };
78
79 class Row : public Ui::RippleButton {
80 public:
81 Row(QWidget *widget, not_null<Main::Session*> session, const Set &set);
82
83 protected:
84 void paintEvent(QPaintEvent *e) override;
85
86 void onStateChanged(State was, StateChangeSource source) override;
87
88 private:
89 [[nodiscard]] bool showOver() const;
90 [[nodiscard]] bool showOver(State state) const;
91 void updateStatusColorOverride();
92 void setupContent(const Set &set);
93 void setupLabels(const Set &set);
94 void setupPreview(const Set &set);
95 void setupAnimation();
96 void paintPreview(Painter &p) const;
97 void paintRadio(Painter &p);
98 void setupHandler();
99 void load();
100 void radialAnimationCallback(crl::time now);
101 void updateLoadingToFinished();
102
103 const not_null<Main::Session*> _session;
104 int _id = 0;
105 bool _switching = false;
106 rpl::variable<SetState> _state;
107 Ui::FlatLabel *_status = nullptr;
108 std::array<QPixmap, 4> _preview;
109 Ui::Animations::Simple _toggled;
110 Ui::Animations::Simple _active;
111 std::unique_ptr<Ui::RadialAnimation> _loading;
112
113 };
114
115 base::unique_qptr<Loader> GlobalLoader;
116 rpl::event_stream<Loader*> GlobalLoaderValues;
117
118 void SetGlobalLoader(base::unique_qptr<Loader> loader) {
119 GlobalLoader = std::move(loader);
120 GlobalLoaderValues.fire(GlobalLoader.get());
121 }
122
123 int GetDownloadSize(int id) {
124 return ranges::find(kSets, id, &Set::id)->size;
125 }
126
127 [[nodiscard]] float64 CountProgress(not_null<const Loading*> loading) {
128 return (loading->size > 0)
129 ? (loading->already / float64(loading->size))
130 : 0.;
131 }
132
133 MTP::DedicatedLoader::Location GetDownloadLocation(int id) {
134 const auto username = kCloudLocationUsername.utf16();
135 const auto i = ranges::find(kSets, id, &Set::id);
136 return MTP::DedicatedLoader::Location{ username, i->postId };
137 }
138
139 SetState ComputeState(int id) {
140 if (id == CurrentSetId()) {
141 return Active();
142 } else if (SetIsReady(id)) {
143 return Ready();
144 }
145 return Available{ GetDownloadSize(id) };
146 }
147
148 QString StateDescription(const SetState &state) {
149 return StateDescription(
150 state,
151 tr::lng_emoji_set_active);
152 }
153
154 bool GoodSetPartName(const QString &name) {
155 return (name == qstr("config.json"))
156 || (name.startsWith(qstr("emoji_"))
157 && name.endsWith(qstr(".webp")));
158 }
159
160 bool UnpackSet(const QString &path, const QString &folder) {
161 return UnpackBlob(path, folder, GoodSetPartName);
162 }
163
164
165 Loader::Loader(
166 not_null<Main::Session*> session,
167 int id,
168 MTP::DedicatedLoader::Location location,
169 const QString &folder,
170 int size)
171 : BlobLoader(nullptr, session, id, location, folder, size) {
172 }
173
174 void Loader::unpack(const QString &path) {
175 const auto folder = internal::SetDataPath(id());
176 const auto weak = Ui::MakeWeak(this);
177 crl::async([=] {
178 if (UnpackSet(path, folder)) {
179 QFile(path).remove();
180 SwitchToSet(id(), crl::guard(weak, [=](bool success) {
181 if (success) {
182 destroy();
183 } else {
184 fail();
185 }
186 }));
187 } else {
188 crl::on_main(weak, [=] {
189 fail();
190 });
191 }
192 });
193 }
194
195 void Loader::destroy() {
196 Expects(GlobalLoader == this);
197
198 SetGlobalLoader(nullptr);
199 }
200
201 void Loader::fail() {
202 ClearNeedSwitchToId();
203 BlobLoader::fail();
204 }
205
206 Inner::Inner(QWidget *parent, not_null<Main::Session*> session)
207 : RpWidget(parent)
208 , _session(session) {
209 setupContent();
210 }
211
212 void Inner::setupContent() {
213 const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
214
215 for (const auto &set : kSets) {
216 content->add(object_ptr<Row>(content, _session, set));
217 }
218
219 content->resizeToWidth(st::boxWidth);
220 Ui::ResizeFitChild(this, content);
221 }
222
223 Row::Row(QWidget *widget, not_null<Main::Session*> session, const Set &set)
224 : RippleButton(widget, st::defaultRippleAnimation)
225 , _session(session)
226 , _id(set.id)
227 , _state(Available{ set.size }) {
228 setupContent(set);
229 setupHandler();
230 }
231
232 void Row::paintEvent(QPaintEvent *e) {
233 Painter p(this);
234
235 const auto over = showOver();
236 const auto bg = over ? st::windowBgOver : st::windowBg;
237 p.fillRect(rect(), bg);
238
239 paintRipple(p, 0, 0);
240 paintPreview(p);
241 paintRadio(p);
242 }
243
244 void Row::paintPreview(Painter &p) const {
245 const auto x = st::manageEmojiPreviewPadding.left();
246 const auto y = st::manageEmojiPreviewPadding.top();
247 const auto width = st::manageEmojiPreviewWidth;
248 const auto height = st::manageEmojiPreviewWidth;
249 auto &&preview = ranges::view::zip(_preview, ranges::view::ints(0, int(_preview.size())));
250 for (const auto &[pixmap, index] : preview) {
251 const auto row = (index / 2);
252 const auto column = (index % 2);
253 const auto left = x + (column ? width - st::manageEmojiPreview : 0);
254 const auto top = y + (row ? height - st::manageEmojiPreview : 0);
255 p.drawPixmap(left, top, pixmap);
256 }
257 }
258
259 void Row::paintRadio(Painter &p) {
260 if (_loading && !_loading->animating()) {
261 _loading = nullptr;
262 }
263 const auto loading = _loading
264 ? _loading->computeState()
265 : Ui::RadialState{ 0., 0, FullArcLength };
266 const auto isToggledSet = v::is<Active>(_state.current());
267 const auto isActiveSet = isToggledSet || v::is<Loading>(_state.current());
268 const auto toggled = _toggled.value(isToggledSet ? 1. : 0.);
269 const auto active = _active.value(isActiveSet ? 1. : 0.);
270 const auto _st = &st::defaultRadio;
271
272 PainterHighQualityEnabler hq(p);
273
274 const auto left = width()
275 - st::manageEmojiMarginRight
276 - _st->diameter
277 - _st->thickness;
278 const auto top = (height() - _st->diameter - _st->thickness) / 2;
279 const auto outerWidth = width();
280
281 auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, active);
282 pen.setWidth(_st->thickness);
283 pen.setCapStyle(Qt::RoundCap);
284 p.setPen(pen);
285 p.setBrush(_st->bg);
286 const auto rect = style::rtlrect(QRectF(
287 left,
288 top,
289 _st->diameter,
290 _st->diameter
291 ).marginsRemoved(QMarginsF(
292 _st->thickness / 2.,
293 _st->thickness / 2.,
294 _st->thickness / 2.,
295 _st->thickness / 2.
296 )), outerWidth);
297 if (loading.shown > 0 && anim::Disabled()) {
298 anim::DrawStaticLoading(
299 p,
300 rect,
301 _st->thickness,
302 pen.color(),
303 _st->bg);
304 } else if (loading.arcLength < FullArcLength) {
305 p.drawArc(rect, loading.arcFrom, loading.arcLength);
306 } else {
307 p.drawEllipse(rect);
308 }
309
310 if (toggled > 0 && (!_loading || !anim::Disabled())) {
311 p.setPen(Qt::NoPen);
312 p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled));
313
314 const auto skip0 = _st->diameter / 2.;
315 const auto skip1 = _st->skip / 10.;
316 const auto checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
317 p.drawEllipse(style::rtlrect(QRectF(
318 left,
319 top,
320 _st->diameter,
321 _st->diameter
322 ).marginsRemoved(QMarginsF(
323 checkSkip,
324 checkSkip,
325 checkSkip,
326 checkSkip
327 )), outerWidth));
328 }
329 }
330
331 bool Row::showOver(State state) const {
332 return (!(state & StateFlag::Disabled))
333 && (state & (StateFlag::Over | StateFlag::Down));
334 }
335
336 bool Row::showOver() const {
337 return showOver(state());
338 }
339
340 void Row::onStateChanged(State was, StateChangeSource source) {
341 RippleButton::onStateChanged(was, source);
342 if (showOver() != showOver(was)) {
343 updateStatusColorOverride();
344 }
345 }
346
347 void Row::updateStatusColorOverride() {
348 const auto isToggledSet = v::is<Active>(_state.current());
349 const auto toggled = _toggled.value(isToggledSet ? 1. : 0.);
350 const auto over = showOver();
351 if (toggled == 0. && !over) {
352 _status->setTextColorOverride(std::nullopt);
353 } else {
354 _status->setTextColorOverride(anim::color(
355 over ? st::contactsStatusFgOver : st::contactsStatusFg,
356 st::contactsStatusFgOnline,
357 toggled));
358 }
359 }
360
361 void Row::setupContent(const Set &set) {
362 _state = GlobalLoaderValues.events_starting_with(
363 GlobalLoader.get()
364 ) | rpl::map([=](Loader *loader) {
365 return (loader && loader->id() == _id)
366 ? loader->state()
367 : rpl::single(
368 rpl::empty_value()
369 ) | rpl::then(
370 Updated()
371 ) | rpl::map([=] {
372 return ComputeState(_id);
373 });
374 }) | rpl::flatten_latest(
375 ) | rpl::filter([=](const SetState &state) {
376 return !v::is<Failed>(_state.current())
377 || !v::is<Available>(state);
378 });
379
380 setupLabels(set);
381 setupPreview(set);
382 setupAnimation();
383
384 const auto height = st::manageEmojiPreviewPadding.top()
385 + st::manageEmojiPreviewHeight
386 + st::manageEmojiPreviewPadding.bottom();
387 resize(width(), height);
388 }
389
390 void Row::setupHandler() {
391 clicks(
392 ) | rpl::filter([=] {
393 const auto &state = _state.current();
394 return !_switching && (v::is<Ready>(state)
395 || v::is<Available>(state));
396 }) | rpl::start_with_next([=] {
397 if (v::is<Available>(_state.current())) {
398 load();
399 return;
400 }
401 _switching = true;
402 SwitchToSet(_id, crl::guard(this, [=](bool success) {
403 _switching = false;
404 if (!success) {
405 load();
406 } else if (GlobalLoader && GlobalLoader->id() == _id) {
407 GlobalLoader->destroy();
408 }
409 }));
410 }, lifetime());
411
412 _state.value(
413 ) | rpl::map([=](const SetState &state) {
414 return v::is<Ready>(state) || v::is<Available>(state);
415 }) | rpl::start_with_next([=](bool active) {
416 setDisabled(!active);
417 setPointerCursor(active);
418 }, lifetime());
419 }
420
421 void Row::load() {
422 LoadAndSwitchTo(_session, _id);
423 }
424
425 void Row::setupLabels(const Set &set) {
426 using namespace rpl::mappers;
427
428 const auto name = Ui::CreateChild<Ui::FlatLabel>(
429 this,
430 set.name,
431 st::localStorageRowTitle);
432 name->setAttribute(Qt::WA_TransparentForMouseEvents);
433 _status = Ui::CreateChild<Ui::FlatLabel>(
434 this,
435 _state.value() | rpl::map(StateDescription),
436 st::localStorageRowSize);
437 _status->setAttribute(Qt::WA_TransparentForMouseEvents);
438
439 sizeValue(
440 ) | rpl::start_with_next([=](QSize size) {
441 const auto left = st::manageEmojiPreviewPadding.left()
442 + st::manageEmojiPreviewWidth
443 + st::manageEmojiPreviewPadding.right();
444 const auto namey = st::manageEmojiPreviewPadding.top()
445 + st::manageEmojiNameTop;
446 const auto statusy = st::manageEmojiPreviewPadding.top()
447 + st::manageEmojiStatusTop;
448 name->moveToLeft(left, namey);
449 _status->moveToLeft(left, statusy);
450 }, name->lifetime());
451 }
452
453 void Row::setupPreview(const Set &set) {
454 const auto size = st::manageEmojiPreview * cIntRetinaFactor();
455 const auto original = QImage(set.previewPath);
456 const auto full = original.height();
457 auto &&preview = ranges::view::zip(_preview, ranges::view::ints(0, int(_preview.size())));
458 for (auto &&[pixmap, index] : preview) {
459 pixmap = App::pixmapFromImageInPlace(original.copy(
460 { full * index, 0, full, full }
461 ).scaledToWidth(size, Qt::SmoothTransformation));
462 pixmap.setDevicePixelRatio(cRetinaFactor());
463 }
464 }
465
466 void Row::updateLoadingToFinished() {
467 _loading->update(
468 v::is<Failed>(_state.current()) ? 0. : 1.,
469 true,
470 crl::now());
471 }
472
473 void Row::radialAnimationCallback(crl::time now) {
474 const auto updated = [&] {
475 const auto state = _state.current();
476 if (const auto loading = std::get_if<Loading>(&state)) {
477 return _loading->update(CountProgress(loading), false, now);
478 } else {
479 updateLoadingToFinished();
480 }
481 return false;
482 }();
483 if (!anim::Disabled() || updated) {
484 update();
485 }
486 }
487
488 void Row::setupAnimation() {
489 using namespace rpl::mappers;
490
491 _state.value(
492 ) | rpl::start_with_next([=](const SetState &state) {
493 update();
494 }, lifetime());
495
496 _state.value(
497 ) | rpl::map(
498 _1 == SetState{ Active() }
499 ) | rpl::distinct_until_changed(
500 ) | rpl::start_with_next([=](bool toggled) {
501 _toggled.start(
502 [=] { updateStatusColorOverride(); update(); },
503 toggled ? 0. : 1.,
504 toggled ? 1. : 0.,
505 st::defaultRadio.duration);
506 }, lifetime());
507
508 _state.value(
509 ) | rpl::map([](const SetState &state) {
510 return v::is<Loading>(state) || v::is<Active>(state);
511 }) | rpl::distinct_until_changed(
512 ) | rpl::start_with_next([=](bool active) {
513 _active.start(
514 [=] { update(); },
515 active ? 0. : 1.,
516 active ? 1. : 0.,
517 st::defaultRadio.duration);
518 }, lifetime());
519
520 _state.value(
521 ) | rpl::map([](const SetState &state) {
522 return std::get_if<Loading>(&state);
523 }) | rpl::distinct_until_changed(
524 ) | rpl::start_with_next([=](const Loading *loading) {
525 if (loading && !_loading) {
526 _loading = std::make_unique<Ui::RadialAnimation>(
527 [=](crl::time now) { radialAnimationCallback(now); });
528 _loading->start(CountProgress(loading));
529 } else if (!loading && _loading) {
530 updateLoadingToFinished();
531 }
532 }, lifetime());
533
534 _toggled.stop();
535 _active.stop();
536 updateStatusColorOverride();
537 }
538
539 } // namespace
540
541 ManageSetsBox::ManageSetsBox(QWidget*, not_null<Main::Session*> session)
542 : _session(session) {
543 }
544
545 void ManageSetsBox::prepare() {
546 const auto inner = setInnerWidget(object_ptr<Inner>(this, _session));
547
548 setTitle(tr::lng_emoji_manage_sets());
549
550 addButton(tr::lng_close(), [=] { closeBox(); });
551
552 setDimensionsToContent(st::boxWidth, inner);
553 }
554
555 void LoadAndSwitchTo(not_null<Main::Session*> session, int id) {
556 if (!ranges::contains(kSets, id, &Set::id)) {
557 ClearNeedSwitchToId();
558 return;
559 }
560 SetGlobalLoader(base::make_unique_q<Loader>(
561 session,
562 id,
563 GetDownloadLocation(id),
564 internal::SetDataPath(id),
565 GetDownloadSize(id)));
566 }
567
568 } // namespace Emoji
569 } // namespace Ui