"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