diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 94741dda35..f27aeb137e 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -508,6 +508,8 @@ PRIVATE history/view/media/history_view_photo.cpp history/view/media/history_view_poll.h history/view/media/history_view_poll.cpp + history/view/media/history_view_slot_machine.h + history/view/media/history_view_slot_machine.cpp history/view/media/history_view_sticker.h history/view/media/history_view_sticker.cpp history/view/media/history_view_theme_document.h diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index f334294336..becdfceb80 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_message.h" // NewMessageFlags. #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities. +#include "chat_helpers/stickers_dice_pack.h" // DicePacks::kDiceString. #include "ui/text/text_entity.h" // TextWithEntities. #include "ui/item_text_options.h" // Ui::ItemTextOptions. #include "main/main_session.h" @@ -221,8 +222,9 @@ bool SendDice(Api::MessageToSend &message) { auto &account = message.action.history->session().account(); auto &config = account.appConfig(); static const auto hardcoded = std::vector{ - QString::fromUtf8("\xF0\x9F\x8E\xB2"), - QString::fromUtf8("\xF0\x9F\x8E\xAF") + Stickers::DicePacks::kDiceString, + Stickers::DicePacks::kDartString, + Stickers::DicePacks::kSlotString, }; const auto list = config.get>( "emojies_send_dice", diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 083778c9e3..59a5c8ba4b 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -20,6 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Stickers { +const QString DicePacks::kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); +const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF"); +const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0"); + DicePack::DicePack(not_null session, const QString &emoji) : _session(session) , _emoji(emoji) { @@ -54,13 +58,21 @@ void DicePack::load() { void DicePack::applySet(const MTPDmessages_stickerSet &data) { _map.clear(); auto documents = base::flat_map>(); + const auto isSlotMachine = DicePacks::IsSlot(_emoji); for (const auto &sticker : data.vdocuments().v) { const auto document = _session->data().processDocument( sticker); if (document->sticker()) { - documents.emplace(document->id, document); + if (isSlotMachine) { + _map.emplace(_map.size(), document); + } else { + documents.emplace(document->id, document); + } } } + if (isSlotMachine) { + return; + } for (const auto pack : data.vpacks().v) { pack.match([&](const MTPDstickerPack &data) { const auto emoji = qs(data.vemoticon()); @@ -86,11 +98,9 @@ void DicePack::tryGenerateLocalZero() { return; } - static const auto kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); - static const auto kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF"); - const auto path = (_emoji == kDiceString) + const auto path = (_emoji == DicePacks::kDiceString) ? qsl(":/gui/art/dice_idle.tgs") - : (_emoji == kDartString) + : (_emoji == DicePacks::kDartString) ? qsl(":/gui/art/dart_idle.tgs") : QString(); if (path.isEmpty()) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h index 744a6d65ed..a1556ee254 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -21,7 +21,7 @@ public: DicePack(not_null session, const QString &emoji); ~DicePack(); - DocumentData *lookup(int value); + [[nodiscard]] DocumentData *lookup(int value); private: void load(); @@ -39,7 +39,15 @@ class DicePacks final { public: explicit DicePacks(not_null session); - DocumentData *lookup(const QString &emoji, int value); + static const QString kDiceString; + static const QString kDartString; + static const QString kSlotString; + + [[nodiscard]] static bool IsSlot(const QString &emoji) { + return (emoji == kSlotString); + } + + [[nodiscard]] DocumentData *lookup(const QString &emoji, int value); private: const not_null _session; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index c87b7b733c..7a89c6252e 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -23,13 +23,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_poll.h" #include "history/view/media/history_view_theme_document.h" +#include "history/view/media/history_view_slot_machine.h" #include "history/view/media/history_view_dice.h" #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/emoji_config.h" +#include "api/api_sending.h" #include "storage/storage_shared_media.h" #include "storage/localstorage.h" +#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot. #include "data/data_session.h" #include "data/data_photo.h" #include "data/data_document.h" @@ -42,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "storage/file_upload.h" #include "app.h" +#include "styles/style_chat.h" namespace Data { namespace { @@ -1339,9 +1345,60 @@ std::unique_ptr MediaDice::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique( - message, - std::make_unique(message, this)); + return ::Stickers::DicePacks::IsSlot(_emoji) + ? std::make_unique( + message, + std::make_unique(message, this)) + : std::make_unique( + message, + std::make_unique(message, this)); +} + +ClickHandlerPtr MediaDice::makeHandler() const { + return MakeHandler(parent()->history(), _emoji); +} + +ClickHandlerPtr MediaDice::MakeHandler( + not_null history, + const QString &emoji) { + static auto ShownToast = base::weak_ptr(); + static const auto HideExisting = [] { + if (const auto toast = ShownToast.get()) { + toast->hideAnimated(); + ShownToast = nullptr; + } + }; + return std::make_shared([=] { + auto config = Ui::Toast::Config{ + .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, + .st = &st::historyDiceToast, + .durationMs = Ui::Toast::kDefaultDuration * 2, + .multiline = true, + }; + if (history->peer->canWrite()) { + auto link = Ui::Text::Link( + tr::lng_about_random_send(tr::now).toUpper()); + link.entities.push_back( + EntityInText(EntityType::Semibold, 0, link.text.size())); + config.text.append(' ').append(std::move(link)); + config.filter = crl::guard(&history->session(), [=]( + const ClickHandlerPtr &handler, + Qt::MouseButton button) { + if (button == Qt::LeftButton && !ShownToast.empty()) { + auto message = Api::MessageToSend(history); + message.action.clearDraft = false; + message.textWithTags.text = emoji; + + Api::SendDice(message); + HideExisting(); + } + return false; + }); + } + + HideExisting(); + ShownToast = Ui::Toast::Show(config); + }); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 5037a25921..ed139bbcf2 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_location.h" class Image; +class History; class HistoryItem; namespace base { @@ -447,6 +448,11 @@ public: not_null realParent, HistoryView::Element *replacing = nullptr) override; + [[nodiscard]] ClickHandlerPtr makeHandler() const; + [[nodiscard]] static ClickHandlerPtr MakeHandler( + not_null history, + const QString &emoji); + private: QString _emoji; int _value = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp index 3a79c6924f..9c823b0b92 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_dice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_dice.cpp @@ -9,15 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "chat_helpers/stickers_dice_pack.h" -#include "api/api_sending.h" -#include "api/api_common.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "history/view/history_view_element.h" -#include "ui/toast/toast.h" -#include "ui/text/text_utilities.h" -#include "lang/lang_keys.h" #include "main/main_session.h" #include "styles/style_chat.h" @@ -32,49 +27,6 @@ namespace { return session.diceStickersPacks().lookup(emoji, value); } -[[nodiscard]] ClickHandlerPtr MakeDiceHandler( - not_null history, - const QString &emoji) { - static auto ShownToast = base::weak_ptr(); - static const auto HideExisting = [] { - if (const auto toast = ShownToast.get()) { - toast->hideAnimated(); - ShownToast = nullptr; - } - }; - return std::make_shared([=] { - auto config = Ui::Toast::Config{ - .text = { tr::lng_about_random(tr::now, lt_emoji, emoji) }, - .st = &st::historyDiceToast, - .durationMs = Ui::Toast::kDefaultDuration * 2, - .multiline = true, - }; - if (history->peer->canWrite()) { - auto link = Ui::Text::Link( - tr::lng_about_random_send(tr::now).toUpper()); - link.entities.push_back( - EntityInText(EntityType::Semibold, 0, link.text.size())); - config.text.append(' ').append(std::move(link)); - config.filter = crl::guard(&history->session(), [=]( - const ClickHandlerPtr &handler, - Qt::MouseButton button) { - if (button == Qt::LeftButton && !ShownToast.empty()) { - auto message = Api::MessageToSend(history); - message.action.clearDraft = false; - message.textWithTags.text = emoji; - - Api::SendDice(message); - HideExisting(); - } - return false; - }); - } - - HideExisting(); - ShownToast = Ui::Toast::Show(config); - }); -} - } // namespace Dice::Dice(not_null parent, not_null dice) @@ -82,7 +34,7 @@ Dice::Dice(not_null parent, not_null dice) , _dice(dice) , _link(_parent->data()->Has() ? nullptr - : MakeDiceHandler(_parent->history(), dice->emoji())) { + : dice->makeHandler()) { if (const auto document = Lookup(parent, dice->emoji(), 0)) { _start.emplace(parent, document); _start->setDiceIndex(_dice->emoji(), 0); diff --git a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp new file mode 100644 index 0000000000..67a2f27439 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.cpp @@ -0,0 +1,200 @@ +/* +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 "history/view/media/history_view_slot_machine.h" + +#include "data/data_session.h" +#include "chat_helpers/stickers_dice_pack.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "main/main_session.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +constexpr auto kStartBackIndex = 0; +constexpr auto kWinBackIndex = 1; +constexpr auto kPullIndex = 2; +constexpr auto kShifts = std::array{ 3, 9, 15 }; +constexpr auto kSevenWinIndex = 0; +constexpr auto kSevenIndex = 1; +constexpr auto kBarIndex = 2; +constexpr auto kBerriesIndex = 3; +constexpr auto kLemonIndex = 4; +constexpr auto kStartIndex = 5; + +constexpr auto kWinValue = 64; + +const auto &kEmoji = ::Stickers::DicePacks::kSlotString; + +[[nodiscard]] DocumentData *Lookup( + not_null view, + int value) { + const auto &session = view->data()->history()->session(); + return session.diceStickersPacks().lookup(kEmoji, value); +} + +[[nodiscard]] int ComplexIndex(int partIndex, int inPartIndex) { + Expects(partIndex >= 0 && partIndex < 3); + + return kShifts[partIndex] + inPartIndex; +} + +[[nodiscard]] int ComputeComplexIndex(int value, int partIndex) { + Expects(value > 0 && value <= 64); + + if (value == kWinValue) { + return ComplexIndex(partIndex, kSevenWinIndex); + } + const auto bits = ((value - 1) >> (partIndex * 2)) & 0x03; // 0..3 + return ComplexIndex(partIndex, [&] { + switch (bits) { + case 0: return kBarIndex; + case 1: return kBerriesIndex; + case 2: return kLemonIndex; + case 3: return kSevenIndex; + } + Unexpected("Bits value in ComputeComplexIndex."); + }()); +} + +} // namespace + +SlotMachine::SlotMachine( + not_null parent, + not_null dice) +: _parent(parent) +, _dice(dice) +, _link(_parent->data()->Has() + ? nullptr + : dice->makeHandler()) { + resolveStarts(); + _showLastFrame = _parent->data()->Has(); + if (_showLastFrame) { + for (auto &drawingEnd : _drawingEnd) { + drawingEnd = true; + } + } +} + +SlotMachine::~SlotMachine() = default; + +void SlotMachine::resolve( + std::optional &sticker, + int singleTimeIndex, + int index, + bool initSize) const { + if (sticker) { + return; + } + const auto document = Lookup(_parent, index); + if (!document) { + return; + } + sticker.emplace(_parent, document); + sticker->setDiceIndex(kEmoji, singleTimeIndex); + if (initSize) { + sticker->initSize(); + } +} + +void SlotMachine::resolveStarts(bool initSize) { + resolve(_pull, kPullIndex, kPullIndex, initSize); + resolve(_start[0], 0, kStartBackIndex, initSize); + for (auto i = 0; i != 3; ++i) { + resolve(_start[i + 1], 0, ComplexIndex(i, kStartIndex), initSize); + } +} + +void SlotMachine::resolveEnds(int value) { + if (value <= 0 || value > 64) { + return; + } + if (value == kWinValue) { + resolve(_end[0], kWinBackIndex, kWinBackIndex, true); + } + for (auto i = 0; i != 3; ++i) { + const auto index = ComputeComplexIndex(value, i); + resolve(_end[i + 1], index, index, true); + } +} + +bool SlotMachine::isEndResolved() const { + for (auto i = 0; i != 3; ++i) { + if (!_end[i + 1]) { + return false; + } + } + return _end[0].has_value() || (_dice->value() != kWinValue); +} + +QSize SlotMachine::size() { + return _pull + ? _pull->size() + : Sticker::GetAnimatedEmojiSize(&_parent->history()->session()); +} + +ClickHandlerPtr SlotMachine::link() { + return _link; +} + +void SlotMachine::draw(Painter &p, const QRect &r, bool selected) { + resolveStarts(true); + resolveEnds(_dice->value()); + + //const auto endResolved = isEndResolved(); + //if (!endResolved) { + // for (auto &drawingEnd : _drawingEnd) { + // drawingEnd = false; + // } + //} + auto switchedToEnd = _drawingEnd; + const auto pullReady = _pull && _pull->readyToDrawLottie(); + const auto paintReady = [&] { + auto result = pullReady; + for (auto i = 1; i != 4; ++i) { + if (!_end[i] || !_end[i]->readyToDrawLottie()) { + switchedToEnd[i] = false; + } + if (!switchedToEnd[i] + && (!_start[i] || !_start[i]->readyToDrawLottie())) { + result = false; + } + } + if (!_end[0] || !_end[0]->readyToDrawLottie()) { + switchedToEnd[0] = false; + } + if (ranges::contains(switchedToEnd, false) + && (!_start[0] || !_start[0]->readyToDrawLottie())) { + result = false; + } + return result; + }(); + + if (!paintReady) { + return; + } + + for (auto i = 0; i != 4; ++i) { + if (switchedToEnd[i]) { + _end[i]->draw(p, r, selected); + } else { + _start[i]->draw(p, r, selected); + if (_end[i] + && _end[i]->readyToDrawLottie() + && _start[i]->atTheEnd()) { + _drawingEnd[i] = true; + } + } + } + _pull->draw(p, r, selected); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_slot_machine.h b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.h new file mode 100644 index 0000000000..fad54f1c31 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_slot_machine.h @@ -0,0 +1,79 @@ +/* +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 +*/ +#pragma once + +#include "history/view/media/history_view_media_unwrapped.h" +#include "history/view/media/history_view_sticker.h" + +namespace Data { +class MediaDice; +} // namespace Data + +namespace HistoryView { + +class SlotMachine final : public UnwrappedMedia::Content { +public: + SlotMachine(not_null parent, not_null dice); + ~SlotMachine(); + + QSize size() override; + void draw(Painter &p, const QRect &r, bool selected) override; + + ClickHandlerPtr link() override; + + bool hasHeavyPart() const override { + if (_pull && _pull->hasHeavyPart()) { + return true; + } + for (auto i = 0; i != 4; ++i) { + if ((_start[i] && _start[i]->hasHeavyPart()) + || (_end[i] && _end[i]->hasHeavyPart())) { + return true; + } + } + return false; + } + void unloadHeavyPart() override { + if (_pull) { + _pull->unloadHeavyPart(); + } + for (auto i = 0; i != 4; ++i) { + if (_start[i]) { + _start[i]->unloadHeavyPart(); + } + if (_end[i]) { + _end[i]->unloadHeavyPart(); + } + } + } + bool hidesForwardedInfo() override { + return false; + } + +private: + void resolveStarts(bool initSize = false); + void resolveEnds(int value); + [[nodiscard]] bool isEndResolved() const; + void resolve( + std::optional &sticker, + int singleTimeIndex, + int index, + bool initSize) const; + + const not_null _parent; + const not_null _dice; + ClickHandlerPtr _link; + std::optional _pull; + std::array, 4> _start; + std::array, 4> _end; + mutable bool _showLastFrame = false; + mutable std::array _drawingEnd = { { false } }; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index 15ee3cb285..6cc2e8cc29 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -109,7 +109,9 @@ void Sticker::initSize() { } QSize Sticker::size() { - initSize(); + if (_size.isEmpty()) { + initSize(); + } return _size; } @@ -301,7 +303,7 @@ void Sticker::setupLottie() { _dataMedia.get(), _replacements, ChatHelpers::StickerLottieSize::MessageHistory, - _size * cIntRetinaFactor(), + size() * cIntRetinaFactor(), Lottie::Quality::High); lottieCreated(); }