| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- /*
- 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 "chat_helpers/emoji_sets_manager.h"
- #include "mtproto/dedicated_file_loader.h"
- #include "ui/wrap/vertical_layout.h"
- #include "ui/wrap/fade_wrap.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/labels.h"
- #include "ui/effects/animations.h"
- #include "ui/effects/radial_animation.h"
- #include "ui/emoji_config.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "core/application.h"
- #include "lang/lang_keys.h"
- #include "main/main_account.h"
- #include "storage/storage_cloud_blob.h"
- #include "styles/style_layers.h"
- #include "styles/style_boxes.h"
- #include "styles/style_chat_helpers.h"
- namespace Ui {
- namespace Emoji {
- namespace {
- using namespace Storage::CloudBlob;
- struct Set : public Blob {
- QString previewPath;
- };
- inline auto PreviewPath(int i) {
- return u":/gui/emoji/set%1_preview.webp"_q.arg(i);
- }
- const auto kSets = {
- Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
- Set{ { 1, 2290, 8'306'943, "Android" }, PreviewPath(1) },
- Set{ { 2, 2291, 5'694'303, "Twemoji" }, PreviewPath(2) },
- Set{ { 3, 2292, 7'261'223, "JoyPixels" }, PreviewPath(3) },
- };
- using Loading = MTP::DedicatedLoader::Progress;
- using SetState = BlobState;
- class Loader final : public BlobLoader {
- public:
- Loader(
- not_null<Main::Session*> session,
- int id,
- MTP::DedicatedLoader::Location location,
- const QString &folder,
- int size);
- void destroy() override;
- void unpack(const QString &path) override;
- private:
- void fail() override;
- };
- class Inner : public Ui::RpWidget {
- public:
- Inner(QWidget *parent, not_null<Main::Session*> session);
- private:
- void setupContent();
- const not_null<Main::Session*> _session;
- };
- class Row : public Ui::RippleButton {
- public:
- Row(QWidget *widget, not_null<Main::Session*> session, const Set &set);
- protected:
- void paintEvent(QPaintEvent *e) override;
- void onStateChanged(State was, StateChangeSource source) override;
- private:
- [[nodiscard]] bool showOver() const;
- [[nodiscard]] bool showOver(State state) const;
- void updateStatusColorOverride();
- void setupContent(const Set &set);
- void setupLabels(const Set &set);
- void setupPreview(const Set &set);
- void setupAnimation();
- void paintPreview(QPainter &p) const;
- void paintRadio(QPainter &p);
- void setupHandler();
- void load();
- void radialAnimationCallback(crl::time now);
- void updateLoadingToFinished();
- const not_null<Main::Session*> _session;
- int _id = 0;
- bool _switching = false;
- rpl::variable<SetState> _state;
- Ui::FlatLabel *_status = nullptr;
- std::array<QPixmap, 4> _preview;
- Ui::Animations::Simple _toggled;
- Ui::Animations::Simple _active;
- std::unique_ptr<Ui::RadialAnimation> _loading;
- };
- base::unique_qptr<Loader> GlobalLoader;
- rpl::event_stream<Loader*> GlobalLoaderValues;
- void SetGlobalLoader(base::unique_qptr<Loader> loader) {
- GlobalLoader = std::move(loader);
- GlobalLoaderValues.fire(GlobalLoader.get());
- }
- int64 GetDownloadSize(int id) {
- return ranges::find(kSets, id, &Set::id)->size;
- }
- [[nodiscard]] float64 CountProgress(not_null<const Loading*> loading) {
- return (loading->size > 0)
- ? (loading->already / float64(loading->size))
- : 0.;
- }
- MTP::DedicatedLoader::Location GetDownloadLocation(int id) {
- const auto username = kCloudLocationUsername.utf16();
- const auto i = ranges::find(kSets, id, &Set::id);
- return MTP::DedicatedLoader::Location{ username, i->postId };
- }
- SetState ComputeState(int id) {
- if (id == CurrentSetId()) {
- return Active();
- } else if (SetIsReady(id)) {
- return Ready();
- }
- return Available{ GetDownloadSize(id) };
- }
- QString StateDescription(const SetState &state) {
- return StateDescription(
- state,
- tr::lng_emoji_set_active);
- }
- bool GoodSetPartName(const QString &name) {
- return (name == u"config.json"_q)
- || (name.startsWith(u"emoji_"_q) && name.endsWith(u".webp"_q));
- }
- bool UnpackSet(const QString &path, const QString &folder) {
- return UnpackBlob(path, folder, GoodSetPartName);
- }
- Loader::Loader(
- not_null<Main::Session*> session,
- int id,
- MTP::DedicatedLoader::Location location,
- const QString &folder,
- int size)
- : BlobLoader(nullptr, session, id, location, folder, size) {
- }
- void Loader::unpack(const QString &path) {
- const auto folder = internal::SetDataPath(id());
- const auto weak = Ui::MakeWeak(this);
- crl::async([=] {
- if (UnpackSet(path, folder)) {
- QFile(path).remove();
- SwitchToSet(id(), crl::guard(weak, [=](bool success) {
- if (success) {
- destroy();
- } else {
- fail();
- }
- }));
- } else {
- crl::on_main(weak, [=] {
- fail();
- });
- }
- });
- }
- void Loader::destroy() {
- Expects(GlobalLoader == this);
- SetGlobalLoader(nullptr);
- }
- void Loader::fail() {
- ClearNeedSwitchToId();
- BlobLoader::fail();
- }
- Inner::Inner(QWidget *parent, not_null<Main::Session*> session)
- : RpWidget(parent)
- , _session(session) {
- setupContent();
- }
- void Inner::setupContent() {
- const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
- for (const auto &set : kSets) {
- content->add(object_ptr<Row>(content, _session, set));
- }
- content->resizeToWidth(st::boxWidth);
- Ui::ResizeFitChild(this, content);
- }
- Row::Row(QWidget *widget, not_null<Main::Session*> session, const Set &set)
- : RippleButton(widget, st::defaultRippleAnimation)
- , _session(session)
- , _id(set.id)
- , _state(Available{ set.size }) {
- setupContent(set);
- setupHandler();
- }
- void Row::paintEvent(QPaintEvent *e) {
- auto p = QPainter(this);
- const auto over = showOver();
- const auto bg = over ? st::windowBgOver : st::windowBg;
- p.fillRect(rect(), bg);
- paintRipple(p, 0, 0);
- paintPreview(p);
- paintRadio(p);
- }
- void Row::paintPreview(QPainter &p) const {
- const auto x = st::manageEmojiPreviewPadding.left();
- const auto y = st::manageEmojiPreviewPadding.top();
- const auto width = st::manageEmojiPreviewWidth;
- const auto height = st::manageEmojiPreviewWidth;
- auto &&preview = ranges::views::zip(_preview, ranges::views::ints(0, int(_preview.size())));
- for (const auto &[pixmap, index] : preview) {
- const auto row = (index / 2);
- const auto column = (index % 2);
- const auto left = x + (column ? width - st::manageEmojiPreview : 0);
- const auto top = y + (row ? height - st::manageEmojiPreview : 0);
- p.drawPixmap(left, top, pixmap);
- }
- }
- void Row::paintRadio(QPainter &p) {
- if (_loading && !_loading->animating()) {
- _loading = nullptr;
- }
- const auto loading = _loading
- ? _loading->computeState()
- : Ui::RadialState{ 0., 0, arc::kFullLength };
- const auto isToggledSet = v::is<Active>(_state.current());
- const auto isActiveSet = isToggledSet || v::is<Loading>(_state.current());
- const auto toggled = _toggled.value(isToggledSet ? 1. : 0.);
- const auto active = _active.value(isActiveSet ? 1. : 0.);
- const auto _st = &st::defaultRadio;
- PainterHighQualityEnabler hq(p);
- const auto left = width()
- - st::manageEmojiMarginRight
- - _st->diameter
- - _st->thickness;
- const auto top = (height() - _st->diameter - _st->thickness) / 2;
- const auto outerWidth = width();
- auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, active);
- pen.setWidth(_st->thickness);
- pen.setCapStyle(Qt::RoundCap);
- p.setPen(pen);
- p.setBrush(_st->bg);
- const auto rect = style::rtlrect(QRectF(
- left,
- top,
- _st->diameter,
- _st->diameter
- ).marginsRemoved(QMarginsF(
- _st->thickness / 2.,
- _st->thickness / 2.,
- _st->thickness / 2.,
- _st->thickness / 2.
- )), outerWidth);
- if (loading.shown > 0 && anim::Disabled()) {
- anim::DrawStaticLoading(
- p,
- rect,
- _st->thickness,
- pen.color(),
- _st->bg);
- } else if (loading.arcLength < arc::kFullLength) {
- p.drawArc(rect, loading.arcFrom, loading.arcLength);
- } else {
- p.drawEllipse(rect);
- }
- if (toggled > 0 && (!_loading || !anim::Disabled())) {
- p.setPen(Qt::NoPen);
- p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled));
- const auto skip0 = _st->diameter / 2.;
- const auto skip1 = _st->skip / 10.;
- const auto checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
- p.drawEllipse(style::rtlrect(QRectF(
- left,
- top,
- _st->diameter,
- _st->diameter
- ).marginsRemoved(QMarginsF(
- checkSkip,
- checkSkip,
- checkSkip,
- checkSkip
- )), outerWidth));
- }
- }
- bool Row::showOver(State state) const {
- return (!(state & StateFlag::Disabled))
- && (state & (StateFlag::Over | StateFlag::Down));
- }
- bool Row::showOver() const {
- return showOver(state());
- }
- void Row::onStateChanged(State was, StateChangeSource source) {
- RippleButton::onStateChanged(was, source);
- if (showOver() != showOver(was)) {
- updateStatusColorOverride();
- }
- }
- void Row::updateStatusColorOverride() {
- const auto isToggledSet = v::is<Active>(_state.current());
- const auto toggled = _toggled.value(isToggledSet ? 1. : 0.);
- const auto over = showOver();
- if (toggled == 0. && !over) {
- _status->setTextColorOverride(std::nullopt);
- } else {
- _status->setTextColorOverride(anim::color(
- over ? st::contactsStatusFgOver : st::contactsStatusFg,
- st::contactsStatusFgOnline,
- toggled));
- }
- }
- void Row::setupContent(const Set &set) {
- _state = GlobalLoaderValues.events_starting_with(
- GlobalLoader.get()
- ) | rpl::map([=](Loader *loader) {
- return (loader && loader->id() == _id)
- ? loader->state()
- : rpl::single(rpl::empty) | rpl::then(
- Updated()
- ) | rpl::map([=] {
- return ComputeState(_id);
- });
- }) | rpl::flatten_latest(
- ) | rpl::filter([=](const SetState &state) {
- return !v::is<Failed>(_state.current())
- || !v::is<Available>(state);
- });
- setupLabels(set);
- setupPreview(set);
- setupAnimation();
- const auto height = st::manageEmojiPreviewPadding.top()
- + st::manageEmojiPreviewHeight
- + st::manageEmojiPreviewPadding.bottom();
- resize(width(), height);
- }
- void Row::setupHandler() {
- clicks(
- ) | rpl::filter([=] {
- const auto &state = _state.current();
- return !_switching && (v::is<Ready>(state)
- || v::is<Available>(state));
- }) | rpl::start_with_next([=] {
- if (v::is<Available>(_state.current())) {
- load();
- return;
- }
- _switching = true;
- SwitchToSet(_id, crl::guard(this, [=](bool success) {
- _switching = false;
- if (!success) {
- load();
- } else if (GlobalLoader && GlobalLoader->id() == _id) {
- GlobalLoader->destroy();
- }
- }));
- }, lifetime());
- _state.value(
- ) | rpl::map([=](const SetState &state) {
- return v::is<Ready>(state) || v::is<Available>(state);
- }) | rpl::start_with_next([=](bool active) {
- setDisabled(!active);
- setPointerCursor(active);
- }, lifetime());
- }
- void Row::load() {
- LoadAndSwitchTo(_session, _id);
- }
- void Row::setupLabels(const Set &set) {
- using namespace rpl::mappers;
- const auto name = Ui::CreateChild<Ui::FlatLabel>(
- this,
- set.name,
- st::localStorageRowTitle);
- name->setAttribute(Qt::WA_TransparentForMouseEvents);
- _status = Ui::CreateChild<Ui::FlatLabel>(
- this,
- _state.value() | rpl::map(StateDescription),
- st::localStorageRowSize);
- _status->setAttribute(Qt::WA_TransparentForMouseEvents);
- sizeValue(
- ) | rpl::start_with_next([=](QSize size) {
- const auto left = st::manageEmojiPreviewPadding.left()
- + st::manageEmojiPreviewWidth
- + st::manageEmojiPreviewPadding.right();
- const auto namey = st::manageEmojiPreviewPadding.top()
- + st::manageEmojiNameTop;
- const auto statusy = st::manageEmojiPreviewPadding.top()
- + st::manageEmojiStatusTop;
- name->moveToLeft(left, namey);
- _status->moveToLeft(left, statusy);
- }, name->lifetime());
- }
- void Row::setupPreview(const Set &set) {
- const auto size = st::manageEmojiPreview * style::DevicePixelRatio();
- const auto original = QImage(set.previewPath);
- const auto full = original.height();
- auto &&preview = ranges::views::zip(_preview, ranges::views::ints(0, int(_preview.size())));
- for (auto &&[pixmap, index] : preview) {
- pixmap = Ui::PixmapFromImage(original.copy(
- { full * index, 0, full, full }
- ).scaledToWidth(size, Qt::SmoothTransformation));
- pixmap.setDevicePixelRatio(style::DevicePixelRatio());
- }
- }
- void Row::updateLoadingToFinished() {
- _loading->update(
- v::is<Failed>(_state.current()) ? 0. : 1.,
- true,
- crl::now());
- }
- void Row::radialAnimationCallback(crl::time now) {
- const auto updated = [&] {
- const auto state = _state.current();
- if (const auto loading = std::get_if<Loading>(&state)) {
- return _loading->update(CountProgress(loading), false, now);
- } else {
- updateLoadingToFinished();
- }
- return false;
- }();
- if (!anim::Disabled() || updated) {
- update();
- }
- }
- void Row::setupAnimation() {
- using namespace rpl::mappers;
- _state.value(
- ) | rpl::start_with_next([=](const SetState &state) {
- update();
- }, lifetime());
- _state.value(
- ) | rpl::map(
- _1 == SetState{ Active() }
- ) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](bool toggled) {
- _toggled.start(
- [=] { updateStatusColorOverride(); update(); },
- toggled ? 0. : 1.,
- toggled ? 1. : 0.,
- st::defaultRadio.duration);
- }, lifetime());
- _state.value(
- ) | rpl::map([](const SetState &state) {
- return v::is<Loading>(state) || v::is<Active>(state);
- }) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](bool active) {
- _active.start(
- [=] { update(); },
- active ? 0. : 1.,
- active ? 1. : 0.,
- st::defaultRadio.duration);
- }, lifetime());
- _state.value(
- ) | rpl::map([](const SetState &state) {
- return std::get_if<Loading>(&state);
- }) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](const Loading *loading) {
- if (loading && !_loading) {
- _loading = std::make_unique<Ui::RadialAnimation>(
- [=](crl::time now) { radialAnimationCallback(now); });
- _loading->start(CountProgress(loading));
- } else if (!loading && _loading) {
- updateLoadingToFinished();
- }
- }, lifetime());
- _toggled.stop();
- _active.stop();
- updateStatusColorOverride();
- }
- } // namespace
- ManageSetsBox::ManageSetsBox(QWidget*, not_null<Main::Session*> session)
- : _session(session) {
- }
- void ManageSetsBox::prepare() {
- const auto inner = setInnerWidget(object_ptr<Inner>(this, _session));
- setTitle(tr::lng_emoji_manage_sets());
- addButton(tr::lng_close(), [=] { closeBox(); });
- setDimensionsToContent(st::boxWidth, inner);
- }
- void LoadAndSwitchTo(not_null<Main::Session*> session, int id) {
- if (!ranges::contains(kSets, id, &Set::id)) {
- ClearNeedSwitchToId();
- return;
- }
- SetGlobalLoader(base::make_unique_q<Loader>(
- session,
- id,
- GetDownloadLocation(id),
- internal::SetDataPath(id),
- GetDownloadSize(id)));
- }
- } // namespace Emoji
- } // namespace Ui
|