From 0ed434cfaf4dfd1f9260451c1ea32b8859f535c7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 8 Jul 2022 19:00:30 +0400 Subject: [PATCH] Show all stickers as emoji after default categories. --- .../chat_helpers/emoji_list_widget.cpp | 361 ++++++++++++++++-- .../chat_helpers/emoji_list_widget.h | 70 +++- .../chat_helpers/message_field.cpp | 15 +- .../chat_helpers/tabbed_selector.cpp | 6 +- .../chat_helpers/tabbed_selector.h | 1 + Telegram/SourceFiles/data/data_document.cpp | 8 +- Telegram/SourceFiles/data/data_document.h | 1 + .../data/stickers/data_custom_emoji.cpp | 7 + .../data/stickers/data_custom_emoji.h | 4 + .../SourceFiles/history/history_widget.cpp | 8 + .../history_view_compose_controls.cpp | 7 + .../ui/text/custom_emoji_instance.cpp | 30 +- .../ui/text/custom_emoji_instance.h | 8 +- 13 files changed, 469 insertions(+), 57 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 165938446..290b11916 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -10,11 +10,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" +#include "ui/text/custom_emoji_instance.h" #include "ui/emoji_config.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" #include "lang/lang_keys.h" #include "layout/layout_position.h" +#include "data/data_session.h" +#include "data/data_document.h" +#include "data/stickers/data_stickers.h" +#include "data/stickers/data_custom_emoji.h" #include "emoji_suggestions_data.h" #include "emoji_suggestions_helper.h" #include "main/main_session.h" @@ -88,7 +93,6 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - void prepareSection(int &left, int top, int _width, Ui::IconButton *sectionIcon, Section section); void setActiveSection(Section section); not_null _pan; @@ -96,7 +100,32 @@ private: }; -EmojiListWidget::Footer::Footer(not_null parent) : InnerFooter(parent) +struct EmojiListWidget::CustomInstance { + CustomInstance( + std::unique_ptr loader, + Fn, + Ui::CustomEmoji::RepaintRequest)> repaintLater, + Fn repaint); + + Ui::CustomEmoji::Instance emoji; + Ui::CustomEmoji::Object object; +}; + +EmojiListWidget::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)) { +} + +EmojiListWidget::Footer::Footer(not_null parent) +: InnerFooter(parent) , _pan(parent) , _sections { { object_ptr(this, st::emojiCategoryRecent), @@ -389,7 +418,8 @@ EmojiListWidget::EmojiListWidget( not_null controller) : Inner(parent, controller) , _picker(this) -, _showPickerTimer([=] { showPicker(); }) { +, _showPickerTimer([=] { showPicker(); }) +, _repaintTimer([=] { invokeRepaints(); }) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); @@ -408,16 +438,112 @@ EmojiListWidget::EmojiListWidget( ) | rpl::start_with_next([=](EmojiPtr emoji) { colorChosen(emoji); }, lifetime()); + _picker->hidden( ) | rpl::start_with_next([=] { pickerHidden(); }, lifetime()); + + controller->session().data().stickers().updated( + ) | rpl::start_with_next([=] { + refreshCustom(); + resizeToWidth(width()); + }, lifetime()); +} + +EmojiListWidget::~EmojiListWidget() { + base::take(_instances); + base::take(_repaints); +} + +void EmojiListWidget::repaintLater( + uint64 setId, + Ui::CustomEmoji::RepaintRequest request) { + if (_instances.empty()) { + return; + } + auto &repaint = _repaints[request.duration]; + if (repaint.when < request.when) { + repaint.when = request.when; + } + repaint.ids.emplace(setId); + scheduleRepaintTimer(); +} + +void EmojiListWidget::scheduleRepaintTimer() { + if (_repaintTimerScheduled) { + return; + } + _repaintTimerScheduled = true; + Ui::PostponeCall(this, [=] { + _repaintTimerScheduled = false; + + auto next = crl::time(); + for (const auto &[duration, bunch] : _repaints) { + if (!next || next > bunch.when) { + next = bunch.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 EmojiListWidget::invokeRepaints() { + _repaintNext = 0; + auto ids = base::flat_set(); + const auto now = crl::now(); + for (auto i = begin(_repaints); i != end(_repaints);) { + if (i->second.when > now) { + ++i; + continue; + } + if (ids.empty()) { + ids = std::move(i->second.ids); + } else { + for (const auto id : i->second.ids) { + ids.emplace(id); + } + } + i = _repaints.erase(i); + } + repaintCustom([&](uint64 id) { return ids.contains(id); }); + scheduleRepaintTimer(); +} + +template +void EmojiListWidget::repaintCustom(CheckId checkId) { + enumerateSections([&](const SectionInfo &info) { + if (info.section >= kEmojiSectionCount + && checkId(_custom[info.section - kEmojiSectionCount].id)) { + update( + 0, + info.rowsTop, + width(), + info.rowsBottom - info.rowsTop); + } + return true; + }); } rpl::producer EmojiListWidget::chosen() const { return _chosen.events(); } +auto EmojiListWidget::customChosen() const +-> rpl::producer { + return _customChosen.events(); +} + void EmojiListWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { @@ -425,6 +551,28 @@ void EmojiListWidget::visibleTopBottomUpdated( if (_footer) { _footer->setCurrentSectionIcon(currentSection(visibleTop)); } + unloadNotSeenCustom(visibleTop, visibleBottom); +} + +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(); + } + return true; + }); } object_ptr EmojiListWidget::createFooter() { @@ -438,26 +586,41 @@ template bool EmojiListWidget::enumerateSections(Callback callback) const { Expects(_columnCount > 0); + auto i = 0; auto info = SectionInfo(); - for (auto i = 0; i != kEmojiSectionCount; ++i) { - info.section = i; - info.count = _counts[i]; - info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0); + const auto next = [&] { + info.rowsCount = (info.count + _columnCount - 1) / _columnCount; info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader); info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height(); if (!callback(info)) { return false; } info.top = info.rowsBottom; + return true; + }; + for (; i != kEmojiSectionCount; ++i) { + info.section = i; + info.count = _counts[i]; + if (!next()) { + return false; + } + } + for (auto §ion : _custom) { + info.section = i++; + info.count = int(section.list.size()); + if (!next()) { + return false; + } } return true; } EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const { - Expects(section >= 0 && section < kEmojiSectionCount); + Expects(section >= 0 && section < sectionsCount()); + auto result = SectionInfo(); - enumerateSections([searchForSection = section, &result](const SectionInfo &info) { - if (info.section == searchForSection) { + enumerateSections([&](const SectionInfo &info) { + if (info.section == section) { result = info; return false; } @@ -466,10 +629,12 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const { return result; } -EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) const { +EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset( + int yOffset) const { auto result = SectionInfo(); - enumerateSections([&result, yOffset](const SectionInfo &info) { - if (yOffset < info.rowsBottom || info.section == kEmojiSectionCount - 1) { + const auto count = sectionsCount(); + enumerateSections([&result, count, yOffset](const SectionInfo &info) { + if (yOffset < info.rowsBottom || info.section == count - 1) { result = info; return false; } @@ -478,8 +643,14 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) c return result; } +int EmojiListWidget::sectionsCount() const { + return kEmojiSectionCount + int(_custom.size()); +} + int EmojiListWidget::countDesiredHeight(int newWidth) { - auto fullWidth = (st::roundRadiusSmall + newWidth + st::emojiScroll.width); + const auto fullWidth = st::roundRadiusSmall + + newWidth + + st::emojiScroll.width; _columnCount = std::max( (fullWidth - st::emojiPadding * 2) / st::emojiPanDesiredSize, 1); @@ -491,13 +662,13 @@ int EmojiListWidget::countDesiredHeight(int newWidth) { _rowsLeft -= st::roundRadiusSmall; _singleSize = QSize(singleWidth, singleWidth - 4 * st::lineWidth); _picker->setSingleSize(_singleSize); - return sectionInfo(kEmojiSectionCount - 1).rowsBottom + st::emojiPanPadding; + return sectionInfo(sectionsCount() - 1).rowsBottom + st::emojiPanPadding; } void EmojiListWidget::ensureLoaded(int section) { - Expects(section >= 0 && section < kEmojiSectionCount); + Expects(section >= 0 && section < sectionsCount()); - if (!_emoji[section].isEmpty()) { + if (section >= kEmojiSectionCount || !_emoji[section].empty()) { return; } _emoji[section] = (static_cast
(section) == Section::Recent) @@ -534,7 +705,10 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { toColumn = _columnCount - toColumn; } - enumerateSections([this, &p, r, fromColumn, toColumn](const SectionInfo &info) { + const auto paused = controller()->isGifPausedAtLeastFor( + Window::GifPauseReason::SavedGifs); + const auto now = crl::now(); + enumerateSections([&](const SectionInfo &info) { if (r.top() >= info.rowsBottom) { return true; } else if (r.top() + r.height() <= info.top) { @@ -543,7 +717,14 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { if (info.section > 0 && r.top() < info.rowsTop) { p.setFont(st::emojiPanHeaderFont); p.setPen(st::emojiPanHeaderFg); - p.drawTextLeft(st::emojiPanHeaderLeft - st::roundRadiusSmall, info.top + st::emojiPanHeaderTop, width(), ChatHelpers::EmojiCategoryTitle(info.section)(tr::now)); + const auto text = (info.section < kEmojiSectionCount) + ? ChatHelpers::EmojiCategoryTitle(info.section)(tr::now) + : _custom[info.section - kEmojiSectionCount].title; + p.drawTextLeft( + st::emojiPanHeaderLeft - st::roundRadiusSmall, + info.top + st::emojiPanHeaderTop, + width(), + text); } if (r.top() + r.height() > info.rowsTop) { ensureLoaded(info.section); @@ -567,12 +748,12 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners); } - Ui::Emoji::Draw( - p, - _emoji[info.section][index], - _esize, - w.x() + (_singleSize.width() - (_esize / cIntRetinaFactor())) / 2, - w.y() + (_singleSize.height() - (_esize / cIntRetinaFactor())) / 2); + if (info.section < kEmojiSectionCount) { + drawEmoji(p, w, info.section, index); + } else { + const auto set = info.section - kEmojiSectionCount; + drawCustom(p, w, now, paused, set, index); + } } } } @@ -580,6 +761,38 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { }); } +void EmojiListWidget::drawEmoji( + QPainter &p, + QPoint position, + int section, + int index) { + const auto size = (_esize / cIntRetinaFactor()); + Ui::Emoji::Draw( + p, + _emoji[section][index], + _esize, + position.x() + (_singleSize.width() - size) / 2, + position.y() + (_singleSize.height() - size) / 2); +} + +void EmojiListWidget::drawCustom( + QPainter &p, + QPoint position, + crl::time now, + bool paused, + int set, + int index) { + const auto size = (_esize / cIntRetinaFactor()); + _custom[set].painted = true; + _custom[set].list[index].instance->object.paint( + p, + position.x() + (_singleSize.width() - size) / 2, + position.y() + (_singleSize.height() - size) / 2, + now, + st::windowBgRipple->c, + paused); +} + bool EmojiListWidget::checkPickerHide() { if (!_picker->isHidden() && _pickerSel >= 0) { _picker->hideAnimated(); @@ -644,18 +857,21 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { _picker->hide(); } - if (_selected < 0 || _selected != pressed) return; - - if (_selected >= Layout::PositionToIndex(kEmojiSectionCount, 0)) { + if (_selected < 0 || _selected != pressed) { return; } const auto &[section, sel] = Layout::IndexToPosition(_selected); - if (sel < _emoji[section].size()) { - auto emoji = _emoji[section][sel]; - if (emoji->hasVariants() && !_picker->isHidden()) return; - + if (section < kEmojiSectionCount && sel < _emoji[section].size()) { + const auto emoji = _emoji[section][sel]; + if (emoji->hasVariants() && !_picker->isHidden()) { + return; + } selectEmoji(emoji); + } else if (section >= kEmojiSectionCount + && sel < _custom[section - kEmojiSectionCount].list.size()) { + auto &set = _custom[section - kEmojiSectionCount]; + selectCustom(set.list[sel].document); } } @@ -664,6 +880,10 @@ void EmojiListWidget::selectEmoji(EmojiPtr emoji) { _chosen.fire_copy(emoji); } +void EmojiListWidget::selectCustom(not_null document) { + _customChosen.fire({ .document = document }); +} + void EmojiListWidget::showPicker() { if (_pickerSel < 0) return; @@ -694,7 +914,7 @@ void EmojiListWidget::pickerHidden() { updateSelected(); } -QRect EmojiListWidget::emojiRect(int section, int sel) { +QRect EmojiListWidget::emojiRect(int section, int sel) const { Expects(_columnCount > 0); auto info = sectionInfo(section); @@ -796,9 +1016,82 @@ void EmojiListWidget::refreshRecent() { clearSelection(); _emoji[0] = Core::App().settings().recentEmojiSection(); _counts[0] = _emoji[0].size(); + refreshCustom(); resizeToWidth(width()); } +void EmojiListWidget::refreshCustom() { + auto searchFromIndex = 0; + auto old = base::take(_custom); + const auto owner = &controller()->session().data(); + const auto &order = owner->stickers().setsOrder(); + const auto &sets = owner->stickers().sets(); + for (const auto setId : order) { + auto it = sets.find(setId); + if (it == sets.cend() || it->second->stickers.isEmpty()) { + continue; + } + const auto &list = it->second->stickers; + const auto i = ranges::find(old, setId, &CustomSet::id); + if (i != end(old)) { + const auto valid = [&] { + const auto count = int(list.size()); + if (i->list.size() != count) { + return false; + } + for (auto k = 0; k != count; ++k) { + if (i->list[k].document != list[k]) { + return false; + } + } + return true; + }(); + if (valid) { + _custom.push_back(base::take(*i)); + continue; + } + } + auto set = std::vector(); + set.reserve(list.size()); + for (const auto document : list) { + if (document->sticker()) { + auto i = _instances.find(document->id); + if (i == end(_instances)) { + using Loading = Ui::CustomEmoji::Loading; + auto loader = owner->customEmojiManager().createLoader( + document, + Data::CustomEmojiManager::SizeTag::Large); + const auto repaintDelayed = [=]( + not_null instance, + Ui::CustomEmoji::RepaintRequest request) { + repaintLater(setId, request); + }; + const auto repaintNow = [=] { + repaintCustom([&](uint64 id) { + return id == setId; + }); + }; + i = _instances.emplace( + document->id, + std::make_unique( + std::move(loader), + std::move(repaintDelayed), + std::move(repaintNow))).first; + } + set.push_back({ + .instance = i->second.get(), + .document = document, + }); + } + } + _custom.push_back({ + .id = setId, + .title = it->second->title, + .list = std::move(set), + }); + } +} + bool EmojiListWidget::eventHook(QEvent *e) { if (e->type() == QEvent::ParentChange) { if (_picker->parentWidget() != parentWidget()) { @@ -819,7 +1112,7 @@ void EmojiListWidget::updateSelected() { auto sx = (rtl() ? width() - p.x() : p.x()) - _rowsLeft; if (sx >= 0 && sx < _columnCount * _singleSize.width()) { newSelected = qFloor((p.y() - info.rowsTop) / _singleSize.height()) * _columnCount + qFloor(sx / _singleSize.width()); - if (newSelected >= _emoji[info.section].size()) { + if (newSelected >= info.count) { newSelected = -1; } else { newSelected += Layout::PositionToIndex(info.section, 0); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 68f3ee93a..96ed10e82 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -16,11 +16,14 @@ template struct phrase; } // namespace tr -namespace Ui { -namespace Emoji { +namespace Ui::Emoji { enum class Section; -} // namespace Emoji -} // namespace Ui +} // namespace Ui::Emoji + +namespace Ui::CustomEmoji { +class Instance; +struct RepaintRequest; +} // namespace Ui::CustomEmoji namespace Window { class SessionController; @@ -39,6 +42,7 @@ public: EmojiListWidget( QWidget *parent, not_null controller); + ~EmojiListWidget(); using Section = Ui::Emoji::Section; @@ -47,14 +51,16 @@ public: object_ptr createFooter() override; void showEmojiSection(Section section); - Section currentSection(int yOffset) const; + [[nodiscard]] Section currentSection(int yOffset) const; // Ui::AbstractTooltipShower interface. QString tooltipText() const override; QPoint tooltipPos() const override; bool tooltipWindowActive() const override; - rpl::producer chosen() const; + [[nodiscard]] rpl::producer chosen() const; + [[nodiscard]] auto customChosen() const + -> rpl::producer; protected: void visibleTopBottomUpdated( @@ -85,29 +91,69 @@ private: int rowsTop = 0; int rowsBottom = 0; }; + struct CustomInstance; + struct CustomOne { + not_null instance; + not_null document; + }; + struct CustomSet { + uint64 id = 0; + QString title; + std::vector list; + bool painted = false; + }; + struct RepaintSet { + base::flat_set ids; + crl::time when = 0; + }; template bool enumerateSections(Callback callback) const; - SectionInfo sectionInfo(int section) const; - SectionInfo sectionInfoByOffset(int yOffset) const; + [[nodiscard]] SectionInfo sectionInfo(int section) const; + [[nodiscard]] SectionInfo sectionInfoByOffset(int yOffset) const; + [[nodiscard]] int sectionsCount() const; void showPicker(); void pickerHidden(); void colorChosen(EmojiPtr emoji); bool checkPickerHide(); + void refreshCustom(); + void unloadNotSeenCustom(int visibleTop, int visibleBottom); void ensureLoaded(int section); void updateSelected(); void setSelected(int newSelected); void selectEmoji(EmojiPtr emoji); + void selectCustom(not_null document); + void drawEmoji( + QPainter &p, + QPoint position, + int section, + int index); + void drawCustom( + QPainter &p, + QPoint position, + crl::time now, + bool paused, + int set, + int index); + [[nodiscard]] QRect emojiRect(int section, int sel) const; - QRect emojiRect(int section, int sel); + void repaintLater( + uint64 setId, + Ui::CustomEmoji::RepaintRequest request); + template + void repaintCustom(CheckId checkId); + void scheduleRepaintTimer(); + void invokeRepaints(); Footer *_footer = nullptr; int _counts[kEmojiSectionCount]; QVector _emoji[kEmojiSectionCount]; + std::vector _custom; + base::flat_map> _instances; int _rowsLeft = 0; int _columnCount = 1; @@ -122,7 +168,13 @@ private: object_ptr _picker; base::Timer _showPickerTimer; + base::flat_map _repaints; + bool _repaintTimerScheduled = false; + base::Timer _repaintTimer; + crl::time _repaintNext = 0; + rpl::event_stream _chosen; + rpl::event_stream _customChosen; }; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index efe20aac4..2c5ef8d7a 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "data/data_session.h" #include "data/data_user.h" +#include "data/data_document.h" #include "data/stickers/data_custom_emoji.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "window/window_session_controller.h" @@ -77,14 +78,18 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) { i = all.erase(i); continue; } else if (Ui::InputField::IsCustomEmojiLink(tag)) { - if (!_session->premium()) { + const auto data = Ui::InputField::CustomEmojiEntityData(tag); + const auto emoji = Data::ParseCustomEmojiData(data); + if (emoji.selfId != id) { i = all.erase(i); continue; } - const auto data = Ui::InputField::CustomEmojiEntityData(tag); - if (Data::ParseCustomEmojiData(data).selfId != id) { - i = all.erase(i); - continue; + if (!_session->premium()) { + const auto document = _session->data().document(emoji.id); + if (document->isPremiumSticker()) { + i = all.erase(i); + continue; + } } } ++i; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 31c78a302..ea2909b7b 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -462,7 +462,11 @@ rpl::producer TabbedSelector::emojiChosen() const { return emoji()->chosen(); } -rpl::producer TabbedSelector::fileChosen() const { +auto TabbedSelector::customEmojiChosen() const -> rpl::producer { + return emoji()->customChosen(); +} + +auto TabbedSelector::fileChosen() const -> rpl::producer { auto never = rpl::never( ) | rpl::type_erased(); return rpl::merge( diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 280b74976..2723e33ee 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -83,6 +83,7 @@ public: Main::Session &session() const; rpl::producer emojiChosen() const; + rpl::producer customEmojiChosen() const; rpl::producer fileChosen() const; rpl::producer photoChosen() const; rpl::producer inlineResultChosen() const; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 5556f1029..d58dea06d 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -708,7 +708,7 @@ void DocumentData::automaticLoadSettingsChanged() { return; } _loader = nullptr; - _flags &= ~Flag::DownloadCancelled; + resetCancelled(); } void DocumentData::finishLoad() { @@ -867,7 +867,7 @@ void DocumentData::save( cancel(); } } - _flags &= ~Flag::DownloadCancelled; + resetCancelled(); if (_loader) { if (fromCloud == LoadFromCloudOrLocal) { @@ -992,6 +992,10 @@ bool DocumentData::cancelled() const { return (_flags & Flag::DownloadCancelled); } +void DocumentData::resetCancelled() { + _flags &= ~Flag::DownloadCancelled; +} + VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) { auto bitsCount = static_cast(encoded5bit.size() * 8); auto valuesCount = bitsCount / 5; diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index c660687da..dcfa84b7f 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -115,6 +115,7 @@ public: bool autoLoading = false); void cancel(); [[nodiscard]] bool cancelled() const; + void resetCancelled(); [[nodiscard]] float64 progress() const; [[nodiscard]] int64 loadOffset() const; [[nodiscard]] bool uploading() const; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index b0101a06f..2c7146005 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -163,6 +163,7 @@ void CustomEmojiLoader::load(Fn loaded) { .media = load->document->createMediaView(), .loaded = std::move(loaded), }); + load->process->media->owner()->resetCancelled(); load->process->media->checkStickerLarge(); if (load->process->media->loaded()) { check(); @@ -399,6 +400,12 @@ std::unique_ptr CustomEmojiManager::create( std::move(update)); } +std::unique_ptr CustomEmojiManager::createLoader( + not_null document, + SizeTag tag) { + return std::make_unique(document, tag); +} + void CustomEmojiManager::request() { auto ids = QVector(); ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size()))); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 1efd5a6ab..edbc89f39 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -42,6 +42,10 @@ public: QStringView data, Fn update); + [[nodiscard]] std::unique_ptr createLoader( + not_null document, + SizeTag tag); + [[nodiscard]] Main::Session &session() const; [[nodiscard]] Session &owner() const; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 6af46c30b..f34d6c11b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "data/data_group_call.h" #include "data/stickers/data_stickers.h" +#include "data/stickers/data_custom_emoji.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" @@ -1051,6 +1052,13 @@ void HistoryWidget::initTabbedSelector() { Ui::InsertEmojiAtCursor(_field->textCursor(), emoji); }, lifetime()); + selector->customEmojiChosen( + ) | rpl::filter([=] { + return !isHidden() && !_field->isHidden(); + }) | rpl::start_with_next([=](Selector::FileChosen data) { + Data::InsertCustomEmoji(_field.data(), data.document); + }, lifetime()); + selector->fileChosen( ) | filter | rpl::start_with_next([=](Selector::FileChosen data) { controller()->sendingAnimation().appendSending( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index da4d23433..54b6e87a1 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_channel.h" #include "data/stickers/data_stickers.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_web_page.h" #include "storage/storage_account.h" #include "apiwrap.h" @@ -1828,6 +1829,12 @@ void ComposeControls::initTabbedSelector() { Ui::InsertEmojiAtCursor(_field->textCursor(), emoji); }, wrap->lifetime()); + using FileChosen = ChatHelpers::TabbedSelector::FileChosen; + selector->customEmojiChosen( + ) | rpl::start_with_next([=](FileChosen data) { + Data::InsertCustomEmoji(_field, data.document); + }, wrap->lifetime()); + selector->fileChosen( ) | rpl::start_to_stream(_fileChosen, wrap->lifetime()); diff --git a/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp b/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp index 02d4555d6..4d70c178a 100644 --- a/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp +++ b/Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp @@ -21,6 +21,7 @@ constexpr auto kMaxSize = 128; constexpr auto kMaxFrames = 512; constexpr auto kMaxFrameDuration = 86400 * crl::time(1000); constexpr auto kCacheVersion = 1; +constexpr auto kPreloadFrames = 3; struct CacheHeader { int version = 0; @@ -377,6 +378,8 @@ Renderer::Renderer(RendererDescriptor &&descriptor) }); } +Renderer::~Renderer() = default; + void Renderer::frameReady( std::unique_ptr generator, crl::time duration, @@ -390,24 +393,35 @@ void Renderer::frameReady( _cache.reserve(count); } } - const auto explicitRepaint = (_cache.frames() == _cache.currentFrame()); + const auto current = _cache.currentFrame(); + const auto total = _cache.frames(); + const auto explicitRepaint = (current == total); _cache.add(duration, frame); if (explicitRepaint && _repaint) { _repaint(); } if (!duration) { finish(); - return; + } else if (current + kPreloadFrames > total) { + renderNext(std::move(generator), std::move(frame)); + } else { + _generator = std::move(generator); + _storage = std::move(frame); } +} + +void Renderer::renderNext( + std::unique_ptr generator, + QImage storage) { const auto size = _cache.size(); const auto guard = base::make_weak(this); crl::async([ =, - frame = std::move(frame), + storage = std::move(storage), generator = std::move(generator) ]() mutable { auto rendered = generator->renderNext( - std::move(frame), + std::move(storage), QSize(size, size) * style::DevicePixelRatio(), Qt::KeepAspectRatio); crl::on_main(guard, [ @@ -432,7 +446,13 @@ void Renderer::finish() { } PaintFrameResult Renderer::paint(QPainter &p, int x, int y, crl::time now) { - return _cache.paintCurrentFrame(p, x, y, now); + const auto result = _cache.paintCurrentFrame(p, x, y, now); + if (_generator + && (!result.painted + || _cache.currentFrame() + kPreloadFrames >= _cache.frames())) { + renderNext(std::move(_generator), std::move(_storage)); + } + return result; } std::optional Renderer::ready(const QString &entityData) { diff --git a/Telegram/SourceFiles/ui/text/custom_emoji_instance.h b/Telegram/SourceFiles/ui/text/custom_emoji_instance.h index 21f448431..a24e9ec77 100644 --- a/Telegram/SourceFiles/ui/text/custom_emoji_instance.h +++ b/Telegram/SourceFiles/ui/text/custom_emoji_instance.h @@ -136,7 +136,7 @@ struct RendererDescriptor { class Renderer final : public base::has_weak_ptr { public: explicit Renderer(RendererDescriptor &&descriptor); - virtual ~Renderer() = default; + virtual ~Renderer(); PaintFrameResult paint(QPainter &p, int x, int y, crl::time now); [[nodiscard]] std::optional ready(const QString &entityData); @@ -152,10 +152,14 @@ private: std::unique_ptr generator, crl::time duration, QImage frame); + void renderNext( + std::unique_ptr generator, + QImage storage); void finish(); Cache _cache; std::unique_ptr _generator; + QImage _storage; Fn _put; Fn _repaint; Fn()> _loader; @@ -208,6 +212,8 @@ public: Instance( Loading loading, Fn, RepaintRequest)> repaintLater); + Instance(const Instance&) = delete; + Instance &operator=(const Instance&) = delete; [[nodiscard]] QString entityData() const; void paint(