From b31a3ba5a371dcb1976ca160fab3b563df4dc09b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 22 Jul 2022 14:39:28 +0300 Subject: [PATCH] Improve emoji set box design. --- .../SourceFiles/api/api_attached_stickers.cpp | 23 ++-- .../SourceFiles/boxes/sticker_set_box.cpp | 129 ++++++++++++++---- Telegram/SourceFiles/boxes/sticker_set_box.h | 12 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 2 +- .../chat_helpers/chat_helpers.style | 6 + .../chat_helpers/emoji_list_widget.cpp | 83 +++++------ .../chat_helpers/emoji_list_widget.h | 3 + .../chat_helpers/emoji_suggestions_widget.cpp | 96 ++----------- .../chat_helpers/emoji_suggestions_widget.h | 24 ++-- .../chat_helpers/stickers_list_footer.cpp | 103 ++++++++------ .../chat_helpers/stickers_list_footer.h | 4 +- .../chat_helpers/stickers_list_widget.cpp | 2 +- .../SourceFiles/core/local_url_handlers.cpp | 5 +- .../data/stickers/data_stickers_set.cpp | 9 ++ .../data/stickers/data_stickers_set.h | 3 + .../admin_log/history_admin_log_item.cpp | 3 +- .../view/history_view_context_menu.cpp | 5 +- .../view/history_view_sticker_toast.cpp | 2 +- .../ui/text/custom_emoji_instance.cpp | 81 +++++++++++ .../ui/text/custom_emoji_instance.h | 31 +++++ 20 files changed, 407 insertions(+), 219 deletions(-) diff --git a/Telegram/SourceFiles/api/api_attached_stickers.cpp b/Telegram/SourceFiles/api/api_attached_stickers.cpp index e80e03e6e..8e789d078 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.cpp +++ b/Telegram/SourceFiles/api/api_attached_stickers.cpp @@ -45,19 +45,24 @@ void AttachedStickers::request( return; } // Single attached sticker pack. - const auto setData = result.v.front().match([&](const auto &data) { - return data.vset().match([&](const MTPDstickerSet &data) { - return &data; - }); + const auto data = result.v.front().match([&](const auto &data) { + return &data.vset().data(); }); - const auto setId = (setData->vid().v && setData->vaccess_hash().v) + const auto setId = (data->vid().v && data->vaccess_hash().v) ? StickerSetIdentifier{ - .id = setData->vid().v, - .accessHash = setData->vaccess_hash().v } - : StickerSetIdentifier{ .shortName = qs(setData->vshort_name()) }; + .id = data->vid().v, + .accessHash = data->vaccess_hash().v } + : StickerSetIdentifier{ .shortName = qs(data->vshort_name()) }; strongController->show( - Box(strongController, setId), + Box( + strongController, + setId, + (data->is_emojis() + ? Data::StickersType::Emoji + : data->is_masks() + ? Data::StickersType::Masks + : Data::StickersType::Stickers)), Ui::LayerOption::KeepOther); }).fail([=] { _requestId = 0; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 1e8ffbb9d..e2148ac39 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_document_media.h" #include "data/stickers/data_stickers.h" +#include "data/stickers/data_custom_emoji.h" #include "menu/menu_send.h" #include "lang/lang_keys.h" #include "ui/boxes/confirm_box.h" @@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/image/image_location_factory.h" #include "ui/text/text_utilities.h" +#include "ui/text/custom_emoji_instance.h" #include "ui/effects/path_shift_gradient.h" #include "ui/emoji_config.h" #include "ui/toast/toast.h" @@ -115,7 +117,8 @@ public: Inner( QWidget *parent, not_null controller, - const StickerSetIdentifier &set); + const StickerSetIdentifier &set, + Data::StickersType type); [[nodiscard]] bool loaded() const; [[nodiscard]] bool notInstalled() const; @@ -153,12 +156,15 @@ protected: void leaveEventHook(QEvent *e) override; private: + using CustomInstance = Ui::CustomEmoji::SeparateInstance; struct Element { not_null document; std::shared_ptr documentMedia; Lottie::Animation *lottie = nullptr; Media::Clip::ReaderPointer webm; + CustomInstance *emoji = nullptr; Ui::Animations::Simple overAnimation; + mutable QImage premiumLock; }; @@ -178,6 +184,10 @@ private: Media::Clip::Notification notification, not_null document, int index); + void setupEmoji(int index); + [[nodiscard]] not_null resolveCustomInstance( + not_null document); + void customEmojiRepaint(); void updateSelected(); void setSelected(int selected); @@ -196,10 +206,17 @@ private: void updateItems(); void repaintItems(crl::time now = 0); - not_null _controller; + const not_null _controller; MTP::Sender _api; std::vector _elements; std::unique_ptr _lottiePlayer; + + base::flat_map< + not_null, + std::unique_ptr> _instances; + std::unique_ptr _manager; + bool _repaintScheduled = false; + StickersPack _pack; StickersByEmojiMap _emoji; bool _loaded = false; @@ -226,6 +243,7 @@ private: base::Timer _updateItemsTimer; StickerSetIdentifier _input; + QMargins _padding; mtpRequestId _installRequest = 0; @@ -246,9 +264,18 @@ private: StickerSetBox::StickerSetBox( QWidget*, not_null controller, - const StickerSetIdentifier &set) + const StickerSetIdentifier &set, + Data::StickersType type) : _controller(controller) -, _set(set) { +, _set(set) +, _type(type) { +} + +StickerSetBox::StickerSetBox( + QWidget *parent, + not_null controller, + not_null set) +: StickerSetBox(parent, controller, set->identifier(), set->type()) { } QPointer StickerSetBox::Show( @@ -257,7 +284,10 @@ QPointer StickerSetBox::Show( if (const auto sticker = document->sticker()) { if (sticker->set) { return controller->show( - Box(controller, sticker->set), + Box( + controller, + sticker->set, + sticker->setType), Ui::LayerOption::KeepOther).data(); } } @@ -268,14 +298,18 @@ void StickerSetBox::prepare() { setTitle(tr::lng_contacts_loading()); _inner = setInnerWidget( - object_ptr(this, _controller, _set), + object_ptr(this, _controller, _set, _type), st::stickersScroll); _controller->session().data().stickers().updated( ) | rpl::start_with_next([=] { updateButtons(); }, lifetime()); - setDimensions(st::boxWideWidth, st::stickersMaxHeight); + setDimensions( + st::boxWideWidth, + (_type == Data::StickersType::Emoji + ? st::emojiSetMaxHeight + : st::stickersMaxHeight)); updateTitleAndButtons(); @@ -476,10 +510,12 @@ void StickerSetBox::resizeEvent(QResizeEvent *e) { StickerSetBox::Inner::Inner( QWidget *parent, not_null controller, - const StickerSetIdentifier &set) + const StickerSetIdentifier &set, + Data::StickersType type) : RpWidget(parent) , _controller(controller) , _api(&_controller->session().mtp()) +, _manager(std::make_unique()) , _setId(set.id) , _setAccessHash(set.accessHash) , _setShortName(set.shortName) @@ -489,6 +525,9 @@ StickerSetBox::Inner::Inner( [=] { repaintItems(); })) , _updateItemsTimer([=] { updateItems(); }) , _input(set) +, _padding((type == Data::StickersType::Emoji) + ? st::emojiSetPadding + : st::stickersPadding) , _previewTimer([=] { showPreview(); }) { setAttribute(Qt::WA_OpaquePaintEvent); @@ -613,16 +652,11 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { } _perRow = isEmojiSet() ? kEmojiPerRow : kStickersPerRow; _rowsCount = (_pack.size() + _perRow - 1) / _perRow; - const auto fullWidth = st::boxWideWidth; - const auto rowsLeft = st::stickersPadding.left(); - const auto rowsRight = rowsLeft; - const auto singleWidth = (fullWidth - rowsLeft - rowsRight) - / _perRow; - _singleSize = isEmojiSet() - ? QSize(singleWidth, singleWidth) - : st::stickersSize; + _singleSize = isEmojiSet() ? st::emojiSetSize : st::stickersSize; - resize(st::stickersPadding.left() + _perRow * _singleSize.width(), st::stickersPadding.top() + _rowsCount * _singleSize.height() + st::stickersPadding.bottom()); + resize( + _padding.left() + _perRow * _singleSize.width(), + _padding.top() + _rowsCount * _singleSize.height() + _padding.bottom()); _loaded = true; updateSelected(); @@ -862,8 +896,8 @@ void StickerSetBox::Inner::startOverAnimation(int index, float64 from, float64 t _elements[index].overAnimation.start([=] { const auto row = index / _perRow; const auto column = index % _perRow; - const auto left = st::stickersPadding.left() + column * _singleSize.width(); - const auto top = st::stickersPadding.top() + row * _singleSize.height(); + const auto left = _padding.left() + column * _singleSize.width(); + const auto top = _padding.top() + row * _singleSize.height(); rtlupdate(left, top, _singleSize.width(), _singleSize.height()); }, from, to, st::emojiPanDuration); } @@ -903,8 +937,8 @@ not_null StickerSetBox::Inner::getLottiePlayer() { int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const { QPoint l(mapFromGlobal(p)); if (rtl()) l.setX(width() - l.x()); - int32 row = (l.y() >= st::stickersPadding.top()) ? qFloor((l.y() - st::stickersPadding.top()) / _singleSize.height()) : -1; - int32 col = (l.x() >= st::stickersPadding.left()) ? qFloor((l.x() - st::stickersPadding.left()) / _singleSize.width()) : -1; + int32 row = (l.y() >= _padding.top()) ? qFloor((l.y() - _padding.top()) / _singleSize.height()) : -1; + int32 col = (l.x() >= _padding.left()) ? qFloor((l.x() - _padding.left()) / _singleSize.width()) : -1; if (row >= 0 && col >= 0 && col < _perRow) { int32 result = row * _perRow + col; return (result < _pack.size()) ? result : -1; @@ -915,6 +949,8 @@ int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const { void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { Painter p(this); + _repaintScheduled = false; + p.fillRect(e->rect(), st::boxBg); if (_elements.empty()) { return; @@ -933,7 +969,9 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { if (index >= _elements.size()) { break; } - const auto pos = QPoint(st::stickersPadding.left() + j * _singleSize.width(), st::stickersPadding.top() + i * _singleSize.height()); + const auto pos = QPoint( + _padding.left() + j * _singleSize.width(), + _padding.top() + i * _singleSize.height()); paintSticker(p, index, pos, paused, now); } } @@ -984,7 +1022,7 @@ void StickerSetBox::Inner::visibleTopBottomUpdated( } } }; - const auto rowsTop = st::stickersPadding.top(); + const auto rowsTop = _padding.top(); const auto singleHeight = _singleSize.height(); const auto rowsBottom = rowsTop + _rowsCount * singleHeight; if (visibleTop >= rowsTop + singleHeight && visibleTop < rowsBottom) { @@ -1058,6 +1096,37 @@ void StickerSetBox::Inner::clipCallback( updateItems(); } +void StickerSetBox::Inner::setupEmoji(int index) { + auto &element = _elements[index]; + element.emoji = resolveCustomInstance(element.document); +} + +auto StickerSetBox::Inner::resolveCustomInstance( + not_null document) +-> not_null { + const auto i = _instances.find(document); + if (i != end(_instances)) { + return i->second.get(); + } + auto instance = _manager->make( + _controller->session().data().customEmojiManager().createLoader( + document, + Data::CustomEmojiManager::SizeTag::Large), + [=] { customEmojiRepaint(); }); + return _instances.emplace( + document, + std::move(instance) + ).first->second.get(); +} + +void StickerSetBox::Inner::customEmojiRepaint() { + if (_repaintScheduled) { + return; + } + _repaintScheduled = true; + update(); +} + void StickerSetBox::Inner::paintSticker( Painter &p, int index, @@ -1080,7 +1149,9 @@ void StickerSetBox::Inner::paintSticker( && !_controller->session().premium(); media->checkStickerSmall(); - if (media->loaded()) { + if (sticker->setType == Data::StickersType::Emoji) { + const_cast(this)->setupEmoji(index); + } else if (media->loaded()) { if (sticker->isLottie() && !element.lottie) { const_cast(this)->setupLottie(index); } else if (sticker->isWebm() && !element.webm) { @@ -1095,7 +1166,15 @@ void StickerSetBox::Inner::paintSticker( (_singleSize.width() - size.width()) / 2, (_singleSize.height() - size.height()) / 2); auto lottieFrame = QImage(); - if (element.lottie && element.lottie->ready()) { + if (element.emoji) { + element.emoji->object.paint( + p, + ppos.x(), + ppos.y(), + now, + st::windowBgOver->c, + paused); + } else if (element.lottie && element.lottie->ready()) { lottieFrame = element.lottie->frame(); p.drawImage( QRect(ppos, lottieFrame.size() / cIntRetinaFactor()), diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.h b/Telegram/SourceFiles/boxes/sticker_set_box.h index a53bcafdc..aef99228f 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.h +++ b/Telegram/SourceFiles/boxes/sticker_set_box.h @@ -19,12 +19,21 @@ namespace Ui { class PlainShadow; } // namespace Ui +namespace Data { +class StickersSet; +} // namespace Data + class StickerSetBox final : public Ui::BoxContent { public: StickerSetBox( QWidget*, not_null controller, - const StickerSetIdentifier &set); + const StickerSetIdentifier &set, + Data::StickersType type); + StickerSetBox( + QWidget*, + not_null controller, + not_null set); static QPointer Show( not_null controller, @@ -48,6 +57,7 @@ private: const not_null _controller; const StickerSetIdentifier _set; + const Data::StickersType _type; class Inner; QPointer _inner; diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 12f460ca3..56ee95707 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -1806,7 +1806,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { const auto showSetByRow = [&](const Row &row) { setSelected(SelectedRow()); _controller->show( - Box(_controller, row.set->identifier()), + Box(_controller, row.set), Ui::LayerOption::KeepOther); }; if (selectedIndex >= 0 && !_inDragArea) { diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index fd9317e39..5ad31af19 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -79,6 +79,9 @@ stickersFeaturedInstalled: icon {{ "chat/input_save", lightButtonFg }}; stickersMaxHeight: 320px; stickersPadding: margins(19px, 13px, 19px, 13px); stickersSize: size(64px, 64px); +emojiSetPadding: margins(12px, 0px, 12px, 0px); +emojiSetMaxHeight: 197px; +emojiSetSize: size(42px, 39px); stickersScroll: ScrollArea(boxScroll) { deltat: 19px; deltab: 9px; @@ -225,6 +228,9 @@ stickerIconMove: 400; stickerPreviewDuration: 150; stickerPreviewMin: 0.1; +emojiIconWidth: 35px; +emojiIconArea: 32px; + stickerGroupCategorySize: 28px; stickerGroupCategoryAbout: defaultTextStyle; stickerGroupCategoryAddMargin: margins(0px, 10px, 0px, 5px); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index e99e0b0e6..d0f733f05 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -96,17 +96,9 @@ private: }; -struct EmojiListWidget::CustomInstance { - CustomInstance( - std::unique_ptr loader, - Fn, - Ui::CustomEmoji::RepaintRequest)> repaintLater, - Fn repaint, - bool recentOnly = false); +struct EmojiListWidget::CustomInstance : Ui::CustomEmoji::SeparateInstance { + using SeparateInstance::SeparateInstance; - Ui::CustomEmoji::Instance emoji; - Ui::CustomEmoji::Object object; bool recentOnly = false; }; @@ -115,20 +107,6 @@ struct EmojiListWidget::RecentOne { RecentEmojiId id; }; -EmojiListWidget::CustomInstance::CustomInstance( - std::unique_ptr loader, - Fn, - Ui::CustomEmoji::RepaintRequest)> repaintLater, - Fn repaint, - bool recentOnly) -: emoji( - Ui::CustomEmoji::Loading(std::move(loader), Ui::CustomEmoji::Preview()), - std::move(repaintLater)) -, object(&emoji, std::move(repaint)) -, recentOnly(recentOnly) { -} - EmojiColorPicker::EmojiColorPicker(QWidget *parent) : RpWidget(parent) { setMouseTracking(true); @@ -540,23 +518,42 @@ void EmojiListWidget::unloadNotSeenCustom( int visibleTop, int visibleBottom) { enumerateSections([&](const SectionInfo &info) { - if (info.section < kEmojiSectionCount - || (info.rowsBottom > visibleTop - && info.rowsTop < visibleBottom)) { - return true; - } - auto &custom = _custom[info.section - kEmojiSectionCount]; - if (!custom.painted) { - return true; - } - custom.painted = false; - for (const auto &single : custom.list) { - single.instance->object.unload(); + if (info.rowsBottom <= visibleTop || info.rowsTop >= visibleBottom) { + unloadCustomIn(info); } return true; }); } +void EmojiListWidget::unloadAllCustom() { + enumerateSections([&](const SectionInfo &info) { + unloadCustomIn(info); + return true; + }); +} + +void EmojiListWidget::unloadCustomIn(const SectionInfo &info) { + if (!info.section && _recentPainted) { + _recentPainted = false; + for (const auto &single : _recent) { + if (const auto instance = single.instance) { + instance->object.unload(); + } + } + return; + } else if (info.section < kEmojiSectionCount) { + return; + } + auto &custom = _custom[info.section - kEmojiSectionCount]; + if (!custom.painted) { + return; + } + custom.painted = false; + for (const auto &single : custom.list) { + single.instance->object.unload(); + } +} + object_ptr EmojiListWidget::createFooter() { Expects(_footer == nullptr); @@ -1020,7 +1017,7 @@ void EmojiListWidget::displaySet(uint64 setId) { auto it = sets.find(setId); if (it != sets.cend()) { checkHideWithBox(controller()->show( - Box(controller(), it->second->identifier()), + Box(controller(), it->second.get()), Ui::LayerOption::KeepOther).data()); } } @@ -1206,9 +1203,14 @@ void EmojiListWidget::processHideFinished() { _picker->hideFast(); _pickerSelected = v::null; } + unloadAllCustom(); clearSelection(); } +void EmojiListWidget::processPanelHideFinished() { + unloadAllCustom(); +} + void EmojiListWidget::refreshRecent() { clearSelection(); fillRecent(); @@ -1302,11 +1304,12 @@ auto EmojiListWidget::customInstanceWithLoader( }); } }; - return std::make_unique( + auto result = std::make_unique( std::move(loader), std::move(repaintDelayed), - std::move(repaintNow), - recentOnly); + std::move(repaintNow)); + result->recentOnly = recentOnly; + return result; } auto EmojiListWidget::resolveCustomInstance( diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 7d5edb74e..2b013f96d 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -96,6 +96,7 @@ protected: TabbedSelector::InnerFooter *getFooter() const override; void processHideFinished() override; + void processPanelHideFinished() override; int countDesiredHeight(int newWidth) override; private: @@ -179,6 +180,8 @@ private: bool checkPickerHide(); void refreshCustom(); void unloadNotSeenCustom(int visibleTop, int visibleBottom); + void unloadAllCustom(); + void unloadCustomIn(const SectionInfo &info); void ensureLoaded(int section); void updateSelected(); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index 1007ca950..44c1bd34b 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -41,36 +41,12 @@ constexpr auto kAnimationDuration = crl::time(120); } // namespace -struct SuggestionsWidget::CustomInstance { - CustomInstance( - std::unique_ptr loader, - Fn, - Ui::CustomEmoji::RepaintRequest)> repaintLater, - Fn repaint); - - Ui::CustomEmoji::Instance emoji; - Ui::CustomEmoji::Object object; -}; - -SuggestionsWidget::CustomInstance::CustomInstance( - std::unique_ptr loader, - Fn, - Ui::CustomEmoji::RepaintRequest)> repaintLater, - Fn repaint) -: emoji( - Ui::CustomEmoji::Loading(std::move(loader), Ui::CustomEmoji::Preview()), - std::move(repaintLater)) -, object(&emoji, std::move(repaint)) { -} - SuggestionsWidget::SuggestionsWidget( QWidget *parent, not_null session) : RpWidget(parent) , _session(session) -, _repaintTimer([=] { invokeRepaints(); }) +, _manager(std::make_unique()) , _oneWidth(st::emojiSuggestionSize) , _padding(st::emojiSuggestionsPadding) { resize( @@ -180,77 +156,23 @@ auto SuggestionsWidget::resolveCustomInstance( if (i != end(_instances)) { return i->second.get(); } - const auto repaintDelayed = [=]( - not_null instance, - Ui::CustomEmoji::RepaintRequest request) { - if (_instances.empty() || !request.when) { - return; - } - auto &when = _repaints[request.duration]; - if (when < request.when) { - when = request.when; - } - if (_repaintTimerScheduled) { - return; - } - scheduleRepaintTimer(); - }; - const auto repaintNow = [=] { - update(); - }; - auto instance = std::make_unique( + auto instance = _manager->make( _session->data().customEmojiManager().createLoader( document, Data::CustomEmojiManager::SizeTag::Large), - std::move(repaintDelayed), - std::move(repaintNow)); + [=] { customEmojiRepaint(); }); return _instances.emplace( document, std::move(instance) ).first->second.get(); } -void SuggestionsWidget::scheduleRepaintTimer() { - _repaintTimerScheduled = true; - Ui::PostponeCall(this, [=] { - _repaintTimerScheduled = false; - - auto next = crl::time(); - for (const auto &[duration, when] : _repaints) { - if (!next || next > when) { - next = when; - } - } - if (next && (!_repaintNext || _repaintNext > next)) { - const auto now = crl::now(); - if (now >= next) { - _repaintNext = 0; - _repaintTimer.cancel(); - invokeRepaints(); - } else { - _repaintNext = next; - _repaintTimer.callOnce(next - now); - } - } - }); -} - -void SuggestionsWidget::invokeRepaints() { - _repaintNext = 0; - auto invoke = false; - const auto now = crl::now(); - for (auto i = begin(_repaints); i != end(_repaints);) { - if (i->second > now) { - ++i; - continue; - } - invoke = true; - i = _repaints.erase(i); +void SuggestionsWidget::customEmojiRepaint() { + if (_repaintScheduled) { + return; } - if (invoke) { - update(); - } - scheduleRepaintTimer(); + _repaintScheduled = true; + update(); } SuggestionsWidget::Row::Row( @@ -378,6 +300,8 @@ void SuggestionsWidget::scrollByWheelEvent(not_null e) { void SuggestionsWidget::paintEvent(QPaintEvent *e) { Painter p(this); + _repaintScheduled = false; + const auto clip = e->rect(); p.fillRect(clip, st::boxBg); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h index bc29bfc74..1ca1adde6 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h @@ -19,11 +19,16 @@ class Session; } // namespace Main namespace Ui { - class InnerDropdown; class InputField; +} // namespace Ui -namespace Emoji { +namespace Ui::CustomEmoji { +struct SeparateInstance; +class SimpleManager; +} // namespace Ui::CustomEmoji + +namespace Ui::Emoji { class SuggestionsWidget final : public Ui::RpWidget { public: @@ -43,7 +48,7 @@ public: [[nodiscard]] rpl::producer triggered() const; private: - struct CustomInstance; + using CustomInstance = Ui::CustomEmoji::SeparateInstance; struct Row { Row(not_null emoji, const QString &replacement); @@ -89,19 +94,17 @@ private: [[nodiscard]] not_null resolveCustomInstance( not_null document); - void scheduleRepaintTimer(); - void invokeRepaints(); + void customEmojiRepaint(); const not_null _session; QString _query; std::vector _rows; + base::flat_map< not_null, std::unique_ptr> _instances; - base::flat_map _repaints; - bool _repaintTimerScheduled = false; - base::Timer _repaintTimer; - crl::time _repaintNext = 0; + std::unique_ptr _manager; + bool _repaintScheduled = false; std::optional _lastMousePosition; bool _mouseSelection = false; @@ -190,5 +193,4 @@ private: }; -} // namespace Emoji -} // namespace Ui +} // namespace Ui::Emoji diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 207fadac3..c42da30dd 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -139,8 +139,8 @@ bool StickersListFooter::ScrollState::animationCallback(crl::time now) { return false; } x.update(dt, anim::linear); - selectionX.update(dt, anim::linear); - selectionWidth.update(dt, anim::linear); + selectionX.update(dt, anim::easeOutCubic); + selectionWidth.update(dt, anim::easeOutCubic); return true; } @@ -151,7 +151,8 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor) , _settingsButtonVisible(descriptor.settingsButtonVisible) , _iconState([=] { update(); }) , _subiconState([=] { update(); }) -, _selectionBg(st::roundRadiusSmall, st::windowBgRipple) +, _selectionBg(st::roundRadiusLarge, st::windowBgRipple) +, _subselectionBg(st::emojiIconArea / 2, st::windowBgRipple) , _barSelection(descriptor.barSelection) { setMouseTracking(true); @@ -299,14 +300,13 @@ void StickersListFooter::enumerateIcons( const auto shift = _iconsLeft - iconsX; const auto emojiId = AllEmojiSectionSetId(); const auto right = width(); - const auto single = st::stickerIconWidth; for (auto i = 0, count = int(_icons.size()); i != count; ++i) { auto &icon = _icons[i]; const auto width = (icon.setId == emojiId) ? _subiconsWidthAnimation.value(_subiconsExpanded ? _subiconsWidth - : single) - : single; + : _singleWidth) + : _singleWidth; const auto shifted = shift + left; const auto visible = (shifted + width > 0 && shifted < right); const auto result = callback({ @@ -331,20 +331,19 @@ void StickersListFooter::enumerateSubicons( const auto right = _subiconsWidth; using Section = Ui::Emoji::Section; for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) { - const auto width = st::stickerIconWidth; const auto shifted = shift + left; - const auto visible = (shifted + width > 0 && shifted < right); + const auto visible = (shifted + _singleWidth > 0 && shifted < right); const auto result = callback({ .index = i - int(Section::People), .left = left, .adjustedLeft = shifted, - .width = int(base::SafeRound(width)), + .width = _singleWidth, .visible = visible, }); if (!result) { break; } - left += width; + left += _singleWidth; } } @@ -432,8 +431,8 @@ void StickersListFooter::updateEmojiSectionWidth() { _subiconsExpanded = expanded; _subiconsWidthAnimation.start( [=] { updateEmojiWidthCallback(); }, - expanded ? st::stickerIconWidth : _subiconsWidth, - expanded ? _subiconsWidth : st::stickerIconWidth, + expanded ? _singleWidth : _subiconsWidth, + expanded ? _subiconsWidth : _singleWidth, st::slideDuration); } @@ -580,12 +579,27 @@ void StickersListFooter::paintSelectionBg(Painter &p) const { selx = width() - selx - selw; } const auto skip = st::emojiIconSelectSkip; - const auto sely = _iconsTop - + (st::emojiFooterHeight - st::stickerIconWidth) / 2; - const auto selh = st::stickerIconWidth; - const auto rect = QRect(selx, sely, selw, selh); - const auto fill = rect.marginsRemoved({ skip, skip, skip, skip }); - _selectionBg.paint(p, fill); + const auto sely = _iconsTop; + const auto area = st::emojiIconArea; + const auto rect = QRect( + QPoint(selx, sely) + _areaPosition, + QSize(selw - 2 * _areaPosition.x(), area)); + if (rect.width() == rect.height() || _subiconsWidth <= _singleWidth) { + _selectionBg.paint(p, rect); + } else if (selw == _subiconsWidth) { + _subselectionBg.paint(p, rect); + } else { + PainterHighQualityEnabler hq(p); + const auto progress = (selw - _singleWidth) + / float64(_subiconsWidth - _singleWidth); + const auto radius = anim::interpolate( + st::roundRadiusLarge, + area / 2, + progress); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgRipple); + p.drawRoundedRect(rect, radius, radius); + } } void StickersListFooter::paintSelectionBar(Painter &p) const { @@ -864,17 +878,17 @@ void StickersListFooter::updateSelected() { auto x = p.x(), y = p.y(); if (rtl()) x = width() - x; const auto settingsLeft = width() - _iconsRight; - const auto searchLeft = _iconsLeft - st::stickerIconWidth; + const auto searchLeft = _iconsLeft - _singleWidth; auto newOver = OverState(SpecialOver::None); if (_searchButtonVisible && x >= searchLeft - && x < searchLeft + st::stickerIconWidth + && x < searchLeft + _singleWidth && y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight) { newOver = SpecialOver::Search; } else if (_settingsButtonVisible && x >= settingsLeft - && x < settingsLeft + st::stickerIconWidth + && x < settingsLeft + _singleWidth && y >= _iconsTop && y < _iconsTop + st::emojiFooterHeight) { if (!_icons.empty() && !hasOnlyFeaturedSets()) { @@ -975,6 +989,17 @@ void StickersListFooter::refreshIconsGeometry( _iconState.selectionWidth.finish(); _iconState.animationStart = 0; _iconState.animation.stop(); + if (_barSelection) { + _singleWidth = st::stickerIconWidth; + } else if (_icons.size() > 1 + && _icons[1].setId == EmojiSectionSetId(EmojiSection::People)) { + _singleWidth = (width() - _iconsLeft - _iconsRight) / _icons.size(); + } else { + _singleWidth = st::emojiIconWidth; + } + _areaPosition = QPoint( + (_singleWidth - st::emojiIconArea) / 2, + (st::emojiFooterHeight - st::emojiIconArea) / 2); refreshScrollableDimensions(); updateSelected(); validateSelectedIcon(_activeByScrollId, animations); @@ -988,18 +1013,18 @@ void StickersListFooter::refreshSubiconsGeometry() { _subiconState.selectionWidth.finish(); _subiconState.animationStart = 0; _subiconState.animation.stop(); - const auto single = st::stickerIconWidth; - const auto half = single / 2; + const auto half = _singleWidth / 2; const auto count = int(Section::Symbols) - int(Section::Recent); - const auto widthMax = count * single; - const auto widthMin = 4 * single + half; - const auto collapsedWidth = int(_icons.size()) * single; + const auto widthMax = count * _singleWidth; + const auto widthMin = 4 * _singleWidth + half; + const auto collapsedWidth = int(_icons.size()) * _singleWidth; _subiconsWidth = std::clamp( - width() + single - collapsedWidth, + width() + _singleWidth - collapsedWidth, widthMin, widthMax); if (_subiconsWidth < widthMax) { - _subiconsWidth = ((_subiconsWidth - half) / single) * single + half; + _subiconsWidth = half + + (((_subiconsWidth - half) / _singleWidth) * _singleWidth); } _subiconState.max = std::max( widthMax - _subiconsWidth, @@ -1020,16 +1045,16 @@ void StickersListFooter::paintStickerSettingsIcon(Painter &p) const { st::stickersSettings.paint( p, settingsLeft - + (st::stickerIconWidth - st::stickersSettings.width()) / 2, + + (_singleWidth - st::stickersSettings.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); } void StickersListFooter::paintSearchIcon(Painter &p) const { - const auto searchLeft = _iconsLeft - st::stickerIconWidth; + const auto searchLeft = _iconsLeft - _singleWidth; st::stickersSearch.paint( p, - searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2, + searchLeft + (_singleWidth - st::stickersSearch.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width()); } @@ -1100,7 +1125,7 @@ void StickersListFooter::updateSetIcon(uint64 setId) { } void StickersListFooter::updateSetIconAt(int left) { - update(left, _iconsTop, st::stickerIconWidth, st::emojiFooterHeight); + update(left, _iconsTop, _singleWidth, st::emojiFooterHeight); } void StickersListFooter::paintSetIcon( @@ -1118,8 +1143,7 @@ void StickersListFooter::paintSetIcon( : icon.stickerMedia ? icon.stickerMedia->thumbnail() : nullptr; - const auto x = info.adjustedLeft - + (st::stickerIconWidth - icon.pixw) / 2; + const auto x = info.adjustedLeft + (_singleWidth - icon.pixw) / 2; const auto y = _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2; if (icon.lottie && icon.lottie->ready()) { const auto frame = icon.lottie->frame(); @@ -1130,8 +1154,7 @@ void StickersListFooter::paintSetIcon( } p.drawImage( QRect( - (info.adjustedLeft - + (st::stickerIconWidth - size.width()) / 2), + (info.adjustedLeft + (_singleWidth - size.width()) / 2), _iconsTop + (st::emojiFooterHeight - size.height()) / 2, size.width(), size.height()), @@ -1166,7 +1189,7 @@ void StickersListFooter::paintSetIcon( icon.megagroup->paintUserpicLeft( p, icon.megagroupUserpic, - info.adjustedLeft + (st::stickerIconWidth - size) / 2, + info.adjustedLeft + (_singleWidth - size) / 2, _iconsTop + (st::emojiFooterHeight - size) / 2, width(), st::stickerGroupCategorySize); @@ -1174,7 +1197,7 @@ void StickersListFooter::paintSetIcon( validatePremiumIcon(); const auto size = st::stickersPremium.size(); p.drawImage( - info.adjustedLeft + (st::stickerIconWidth - size.width()) / 2, + info.adjustedLeft + (_singleWidth - size.width()) / 2, _iconsTop + (st::emojiFooterHeight - size.height()) / 2, _premiumIcon); } else { @@ -1207,12 +1230,12 @@ void StickersListFooter::paintSetIcon( const auto paintOne = [&](int left, const style::icon *icon) { icon->paint( p, - left + (st::stickerIconWidth - icon->width()) / 2, + left + (_singleWidth - icon->width()) / 2, _iconsTop + (st::emojiFooterHeight - icon->height()) / 2, width()); }; if (_icons[info.index].setId == AllEmojiSectionSetId() - && info.width > st::stickerIconWidth) { + && info.width > _singleWidth) { const auto skip = st::emojiIconSelectSkip; p.save(); p.setClipRect( diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index 0fb786b22..716ed504a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -226,11 +226,13 @@ private: int _iconsLeft = 0; int _iconsRight = 0; int _iconsTop = 0; + int _singleWidth = 0; + QPoint _areaPosition; ScrollState _iconState; ScrollState _subiconState; - Ui::RoundRect _selectionBg; + Ui::RoundRect _selectionBg, _subselectionBg; Ui::Animations::Simple _subiconsWidthAnimation; int _subiconsWidth = 0; bool _subiconsExpanded = false; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 8234d75c8..5d74b09ae 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -2611,7 +2611,7 @@ void StickersListWidget::displaySet(uint64 setId) { auto it = sets.find(setId); if (it != sets.cend()) { checkHideWithBox(controller()->show( - Box(controller(), it->second->identifier()), + Box(controller(), it->second.get()), Ui::LayerOption::KeepOther).data()); } } diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 1e5d3a3b2..36b717834 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -82,7 +82,10 @@ bool ShowStickerSet( Core::App().hideMediaView(); controller->show(Box( controller, - StickerSetIdentifier{ .shortName = match->captured(2) })); + StickerSetIdentifier{ .shortName = match->captured(2) }, + (match->captured(1) == "addemoji" + ? Data::StickersType::Emoji + : Data::StickersType::Stickers))); controller->window().activate(); return true; } diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp index 43081472d..daaaf89ac 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_file_origin.h" #include "data/data_document.h" +#include "data/stickers/data_stickers.h" #include "storage/file_download.h" #include "ui/image/image.h" @@ -97,6 +98,14 @@ StickerSetIdentifier StickersSet::identifier() const { }; } +StickersType StickersSet::type() const { + return (flags & StickersSetFlag::Emoji) + ? StickersType::Emoji + : (flags & StickersSetFlag::Masks) + ? StickersType::Masks + : StickersType::Stickers; +} + void StickersSet::setThumbnail(const ImageWithLocation &data) { Data::UpdateCloudFile( _thumbnail, diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h index 311521d1c..2a3063c3e 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h @@ -24,6 +24,8 @@ using SavedGifs = QVector; using StickersPack = QVector; using StickersByEmojiMap = QMap; +enum class StickersType : uchar; + class StickersSet; using StickersSets = base::flat_map>; @@ -81,6 +83,7 @@ public: [[nodiscard]] MTPInputStickerSet mtpInput() const; [[nodiscard]] StickerSetIdentifier identifier() const; + [[nodiscard]] StickersType type() const; void setThumbnail(const ImageWithLocation &data); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index efe1ab41c..bf16173dd 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -1043,7 +1043,8 @@ void GenerateItems( controller->show( Box( controller, - Data::FromInputSet(set)), + Data::FromInputSet(set), + Data::StickersType::Stickers), Ui::LayerOption::CloseOther); } }); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index cefe4b233..2f3843fcb 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1324,7 +1324,10 @@ void AddEmojiPacksAction( } // Single used emoji pack. strong->show( - Box(strong, packIds.front()), + Box( + strong, + packIds.front(), + Data::StickersType::Emoji), Ui::LayerOption::KeepOther); }); diff --git a/Telegram/SourceFiles/history/view/history_view_sticker_toast.cpp b/Telegram/SourceFiles/history/view/history_view_sticker_toast.cpp index efd3bae87..d8a0783e7 100644 --- a/Telegram/SourceFiles/history/view/history_view_sticker_toast.cpp +++ b/Telegram/SourceFiles/history/view/history_view_sticker_toast.cpp @@ -195,7 +195,7 @@ void StickerToast::showWithTitle(const QString &title) { } button->setClickedCallback([=] { _controller->show( - Box(_controller, _for->sticker()->set), + Box(_controller, _for->sticker()->set, setType), Ui::LayerOption::KeepOther); hideToast(); }); diff --git a/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp b/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp index 56b811a9a..f82c289ac 100644 --- a/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp +++ b/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/custom_emoji_instance.h" #include "ui/effects/frame_generator.h" +#include "ui/ui_utility.h" #include #include @@ -647,4 +648,84 @@ void Object::repaint() { _repaint(); } +SeparateInstance::SeparateInstance( + std::unique_ptr loader, + Fn, RepaintRequest)> repaintLater, + Fn repaint) +: emoji(Loading(std::move(loader), Preview()), std::move(repaintLater)) +, object(&emoji, std::move(repaint)) { +} + +SimpleManager::SimpleManager() +: _repaintTimer([=] { invokeRepaints(); }) { +} + +std::unique_ptr SimpleManager::make( + std::unique_ptr loader, + Fn repaint) { + auto repaintLater = [=]( + not_null instance, + RepaintRequest request) { + if (!request.when) { + return; + } + auto &when = _repaints[request.duration]; + if (when < request.when) { + when = request.when; + } + if (_repaintTimerScheduled) { + return; + } + scheduleRepaintTimer(); + }; + _simpleRepaint = repaint; + return std::make_unique( + std::move(loader), + std::move(repaintLater), + std::move(repaint)); +} + +void SimpleManager::scheduleRepaintTimer() { + _repaintTimerScheduled = true; + Ui::PostponeCall(this, [=] { + _repaintTimerScheduled = false; + + auto next = crl::time(); + for (const auto &[duration, when] : _repaints) { + if (!next || next > when) { + next = when; + } + } + if (next && (!_repaintNext || _repaintNext > next)) { + const auto now = crl::now(); + if (now >= next) { + _repaintNext = 0; + _repaintTimer.cancel(); + invokeRepaints(); + } else { + _repaintNext = next; + _repaintTimer.callOnce(next - now); + } + } + }); +} + +void SimpleManager::invokeRepaints() { + _repaintNext = 0; + auto invoke = false; + const auto now = crl::now(); + for (auto i = begin(_repaints); i != end(_repaints);) { + if (i->second > now) { + ++i; + continue; + } + invoke = true; + i = _repaints.erase(i); + } + if (invoke && _simpleRepaint) { + _simpleRepaint(); + } + scheduleRepaintTimer(); +} + } // namespace Ui::CustomEmoji diff --git a/Telegram/SourceFiles/ui/text/custom_emoji_instance.h b/Telegram/SourceFiles/ui/text/custom_emoji_instance.h index a24e9ec77..3072b0f63 100644 --- a/Telegram/SourceFiles/ui/text/custom_emoji_instance.h +++ b/Telegram/SourceFiles/ui/text/custom_emoji_instance.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_block.h" #include "base/weak_ptr.h" #include "base/bytes.h" +#include "base/timer.h" class QColor; class QPainter; @@ -266,4 +267,34 @@ private: }; +struct SeparateInstance { + SeparateInstance( + std::unique_ptr loader, + Fn, RepaintRequest)> repaintLater, + Fn repaint); + + Instance emoji; + Object object; +}; + +class SimpleManager final : public base::has_weak_ptr { +public: + SimpleManager(); + + [[nodiscard]] std::unique_ptr make( + std::unique_ptr loader, + Fn repaint); + +private: + void scheduleRepaintTimer(); + void invokeRepaints(); + + base::flat_map _repaints; + bool _repaintTimerScheduled = false; + crl::time _repaintNext = 0; + base::Timer _repaintTimer; + Fn _simpleRepaint; + +}; + } // namespace Ui::CustomEmoji