Support custom emoji in IsolatedEmoji.

This commit is contained in:
John Preston 2022-07-25 17:54:37 +03:00
parent 9b941bae97
commit edfb7bb65a
10 changed files with 183 additions and 62 deletions

View file

@ -150,22 +150,25 @@ auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker {
return { i->second.get(), nullptr }; return { i->second.get(), nullptr };
} }
if (!emoji->colored()) { if (!emoji->colored()) {
return Sticker(); return {};
} }
const auto j = _map.find(emoji->original()); const auto j = _map.find(emoji->original());
if (j != end(_map)) { if (j != end(_map)) {
const auto index = emoji->variantIndex(emoji); const auto index = emoji->variantIndex(emoji);
return { j->second.get(), ColorReplacements(index) }; return { j->second.get(), ColorReplacements(index) };
} }
return Sticker(); return {};
} }
auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
Expects(!emoji.empty()); Expects(!emoji.empty());
return (emoji.items[1] != nullptr) if (!v::is_null(emoji.items[1])) {
? Sticker() return {};
: stickerForEmoji(emoji.items[0]); } else if (const auto regular = std::get_if<EmojiPtr>(&emoji.items[0])) {
return stickerForEmoji(*regular);
}
return {};
} }
std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) { std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_block.h" #include "ui/text/text_block.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "styles/style_chat.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
@ -36,7 +37,8 @@ using SizeTag = CustomEmojiManager::SizeTag;
using LottieSize = ChatHelpers::StickerLottieSize; using LottieSize = ChatHelpers::StickerLottieSize;
switch (tag) { switch (tag) {
case SizeTag::Normal: return LottieSize::MessageHistory; case SizeTag::Normal: return LottieSize::MessageHistory;
case SizeTag::Large: return LottieSize::EmojiInteraction; case SizeTag::Large: return LottieSize::StickersPanel;
case SizeTag::Isolated: return LottieSize::EmojiInteraction;
} }
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag."); Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
} }
@ -45,6 +47,9 @@ using SizeTag = CustomEmojiManager::SizeTag;
switch (tag) { switch (tag) {
case SizeTag::Normal: return Ui::Emoji::GetSizeNormal(); case SizeTag::Normal: return Ui::Emoji::GetSizeNormal();
case SizeTag::Large: return Ui::Emoji::GetSizeLarge(); case SizeTag::Large: return Ui::Emoji::GetSizeLarge();
case SizeTag::Isolated:
return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
* style::DevicePixelRatio();
} }
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag."); Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
} }
@ -396,20 +401,25 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview( Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
DocumentId documentId, DocumentId documentId,
SizeTag tag) const { SizeTag tag) const {
const auto &other = _instances[1 - SizeIndex(tag)]; for (auto i = _instances.size(); i != 0;) {
const auto j = other.find(documentId); if (SizeIndex(tag) == --i) {
if (j == end(other)) { continue;
return {}; }
} else if (const auto nonExact = j->second->imagePreview()) { const auto &other = _instances[i];
const auto size = SizeFromTag(tag); const auto j = other.find(documentId);
return { if (j == end(other)) {
nonExact.image().scaled( continue;
size, } else if (const auto nonExact = j->second->imagePreview()) {
size, const auto size = SizeFromTag(tag);
Qt::IgnoreAspectRatio, return {
Qt::SmoothTransformation), nonExact.image().scaled(
false, size,
}; size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
false,
};
}
} }
return {}; return {};
} }
@ -535,7 +545,7 @@ void CustomEmojiManager::requestSetFor(not_null<DocumentData*> document) {
int CustomEmojiManager::SizeIndex(SizeTag tag) { int CustomEmojiManager::SizeIndex(SizeTag tag) {
const auto result = static_cast<int>(tag); const auto result = static_cast<int>(tag);
Ensures(result >= 0 && result < 2); Ensures(result >= 0 && result < kSizeCount);
return result; return result;
} }

View file

@ -33,6 +33,9 @@ public:
enum class SizeTag { enum class SizeTag {
Normal, Normal,
Large, Large,
Isolated,
kCount,
}; };
CustomEmojiManager(not_null<Session*> owner); CustomEmojiManager(not_null<Session*> owner);
@ -64,6 +67,8 @@ public:
[[nodiscard]] Session &owner() const; [[nodiscard]] Session &owner() const;
private: private:
static constexpr auto kSizeCount = int(SizeTag::kCount);
struct RepaintBunch { struct RepaintBunch {
crl::time when = 0; crl::time when = 0;
std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>> instances; std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>> instances;
@ -91,12 +96,16 @@ private:
const not_null<Session*> _owner; const not_null<Session*> _owner;
base::flat_map< std::array<
uint64, base::flat_map<
std::unique_ptr<Ui::CustomEmoji::Instance>> _instances[2]; uint64,
base::flat_map< std::unique_ptr<Ui::CustomEmoji::Instance>>,
uint64, kSizeCount> _instances;
std::vector<base::weak_ptr<CustomEmojiLoader>>> _loaders[2]; std::array<
base::flat_map<
uint64,
std::vector<base::weak_ptr<CustomEmojiLoader>>>,
kSizeCount> _loaders;
base::flat_set<uint64> _pendingForRequest; base::flat_set<uint64> _pendingForRequest;
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;

View file

@ -1308,7 +1308,7 @@ TextWithEntities HistoryItem::inReplyText() const {
} }
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const { Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
return Ui::Text::IsolatedEmoji(); return {};
} }
HistoryItem::~HistoryItem() { HistoryItem::~HistoryItem() {

View file

@ -419,15 +419,18 @@ void Element::customEmojiRepaint() {
} }
} }
void Element::clearCustomEmojiRepaint() const {
_customEmojiRepaintScheduled = false;
data()->_customEmojiRepaintScheduled = false;
}
void Element::prepareCustomEmojiPaint( void Element::prepareCustomEmojiPaint(
Painter &p, Painter &p,
const Ui::Text::String &text) const { const Ui::Text::String &text) const {
const auto item = data();
if (!text.hasCustomEmoji()) { if (!text.hasCustomEmoji()) {
return; return;
} }
_customEmojiRepaintScheduled = false; clearCustomEmojiRepaint();
item->_customEmojiRepaintScheduled = false;
p.setInactive(delegate()->elementIsGifPaused()); p.setInactive(delegate()->elementIsGifPaused());
if (!_heavyCustomEmoji) { if (!_heavyCustomEmoji) {
_heavyCustomEmoji = true; _heavyCustomEmoji = true;

View file

@ -431,6 +431,7 @@ public:
void prepareCustomEmojiPaint( void prepareCustomEmojiPaint(
Painter &p, Painter &p,
const Ui::Text::String &text) const; const Ui::Text::String &text) const;
void clearCustomEmojiRepaint() const;
[[nodiscard]] ClickHandlerPtr fromPhotoLink() const { [[nodiscard]] ClickHandlerPtr fromPhotoLink() const {
return fromLink(); return fromLink();

View file

@ -14,20 +14,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "data/data_session.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/stickers/data_custom_emoji.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace HistoryView { namespace HistoryView {
namespace { namespace {
using EmojiImage = Stickers::LargeEmojiImage; using Stickers::LargeEmojiImage;
using ImagePtr = std::shared_ptr<Stickers::LargeEmojiImage>;
using CustomPtr = std::unique_ptr<Ui::Text::CustomEmoji>;
auto ResolveImages( auto ResolveImages(
not_null<Main::Session*> session, not_null<Main::Session*> session,
Fn<void()> customEmojiRepaint,
const Ui::Text::IsolatedEmoji &emoji) const Ui::Text::IsolatedEmoji &emoji)
-> std::array<std::shared_ptr<EmojiImage>, Ui::Text::kIsolatedEmojiLimit> { -> std::array<LargeEmojiMedia, Ui::Text::kIsolatedEmojiLimit> {
const auto single = [&](EmojiPtr emoji) { const auto single = [&](Ui::Text::IsolatedEmoji::Item item)
return emoji ? session->emojiStickersPack().image(emoji) : nullptr; -> LargeEmojiMedia {
if (const auto regular = std::get_if<EmojiPtr>(&item)) {
return session->emojiStickersPack().image(*regular);
} else if (const auto custom = std::get_if<QString>(&item)) {
return session->data().customEmojiManager().create(
*custom,
customEmojiRepaint,
Data::CustomEmojiManager::SizeTag::Isolated);
}
return v::null;
}; };
return { { return { {
single(emoji.items[0]), single(emoji.items[0]),
@ -35,28 +49,25 @@ auto ResolveImages(
single(emoji.items[2]) } }; single(emoji.items[2]) } };
} }
auto NonEmpty(const std::array<std::shared_ptr<EmojiImage>, Ui::Text::kIsolatedEmojiLimit> &images) {
using namespace rpl::mappers;
return images | ranges::views::filter(_1 != nullptr);
}
} // namespace } // namespace
LargeEmoji::LargeEmoji( LargeEmoji::LargeEmoji(
not_null<Element*> parent, not_null<Element*> parent,
const Ui::Text::IsolatedEmoji &emoji) const Ui::Text::IsolatedEmoji &emoji)
: _parent(parent) : _parent(parent)
, _images(ResolveImages(&parent->data()->history()->session(), emoji)) { , _images(ResolveImages(
&parent->data()->history()->session(),
[=] { parent->customEmojiRepaint(); },
emoji)) {
} }
QSize LargeEmoji::size() { QSize LargeEmoji::size() {
using namespace rpl::mappers; using namespace rpl::mappers;
const auto count = ranges::distance(NonEmpty(_images)); const auto count = _images.size()
Assert(count > 0); - ranges::count(_images, LargeEmojiMedia());
const auto single = EmojiImage::Size() / cIntRetinaFactor(); const auto single = LargeEmojiImage::Size() / cIntRetinaFactor();
const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline; const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
const auto inner = count * single.width() + (count - 1) * skip; const auto inner = count * single.width() + (count - 1) * skip;
const auto &padding = st::largeEmojiPadding; const auto &padding = st::largeEmojiPadding;
@ -70,23 +81,91 @@ void LargeEmoji::draw(
Painter &p, Painter &p,
const PaintContext &context, const PaintContext &context,
const QRect &r) { const QRect &r) {
auto &&images = NonEmpty(_images); _parent->clearCustomEmojiRepaint();
const auto &padding = st::largeEmojiPadding; const auto &padding = st::largeEmojiPadding;
auto x = r.x() + (r.width() - _size.width()) / 2 + padding.left(); auto x = r.x() + (r.width() - _size.width()) / 2 + padding.left();
const auto y = r.y() + (r.height() - _size.height()) / 2 + padding.top(); const auto y = r.y() + (r.height() - _size.height()) / 2 + padding.top();
const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline; const auto skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
const auto size = EmojiImage::Size() / cIntRetinaFactor(); const auto size = LargeEmojiImage::Size() / cIntRetinaFactor();
for (const auto &image : images) { const auto paused = _parent->delegate()->elementIsGifPaused();
if (const auto &prepared = image->image) { const auto selected = context.selected();
const auto colored = context.selected() if (!selected) {
? &context.st->msgStickerOverlay() _selectedFrame = QImage();
: nullptr; }
p.drawPixmap(x, y, prepared->pix(size, { .colored = colored })); for (const auto &media : _images) {
} else if (image->load) { if (const auto image = std::get_if<ImagePtr>(&media)) {
image->load(); if (const auto &prepared = (*image)->image) {
const auto colored = selected
? &context.st->msgStickerOverlay()
: nullptr;
p.drawPixmap(
x,
y,
prepared->pix(size, { .colored = colored }));
} else if ((*image)->load) {
(*image)->load();
}
} else if (const auto custom = std::get_if<CustomPtr>(&media)) {
paintCustom(p, x, y, custom->get(), context, paused);
} else {
continue;
} }
x += size.width() + skip; x += size.width() + skip;
} }
} }
void LargeEmoji::paintCustom(
QPainter &p,
int x,
int y,
not_null<Ui::Text::CustomEmoji*> emoji,
const PaintContext &context,
bool paused) {
if (!_hasHeavyPart) {
_hasHeavyPart = true;
_parent->history()->owner().registerHeavyViewPart(_parent);
}
const auto inner = st::largeEmojiSize + 2 * st::largeEmojiOutline;
const auto outer = Ui::Text::AdjustCustomEmojiSize(inner);
const auto skip = (inner - outer) / 2;
const auto preview = context.imageStyle()->msgServiceBg->c;
if (context.selected()) {
const auto factor = style::DevicePixelRatio();
const auto size = QSize(outer, outer) * factor;
if (_selectedFrame.size() != size) {
_selectedFrame = QImage(
size,
QImage::Format_ARGB32_Premultiplied);
_selectedFrame.setDevicePixelRatio(factor);
}
_selectedFrame.fill(Qt::transparent);
auto q = QPainter(&_selectedFrame);
emoji->paint(q, 0, 0, context.now, preview, paused);
q.end();
_selectedFrame = Images::Colored(
std::move(_selectedFrame),
context.st->msgStickerOverlay()->c);
p.drawImage(x + skip, y + skip, _selectedFrame);
} else {
emoji->paint(p, x + skip, y + skip, context.now, preview, paused);
}
}
bool LargeEmoji::hasHeavyPart() const {
return _hasHeavyPart;
}
void LargeEmoji::unloadHeavyPart() {
if (_hasHeavyPart) {
_hasHeavyPart = false;
for (auto &media : _images) {
if (const auto custom = std::get_if<CustomPtr>(&media)) {
(*custom)->unload();
}
}
}
}
} // namespace HistoryView } // namespace HistoryView

View file

@ -20,6 +20,11 @@ struct LargeEmojiImage;
namespace HistoryView { namespace HistoryView {
using LargeEmojiMedia = std::variant<
v::null_t,
std::shared_ptr<Stickers::LargeEmojiImage>,
std::unique_ptr<Ui::Text::CustomEmoji>>;
class LargeEmoji final : public UnwrappedMedia::Content { class LargeEmoji final : public UnwrappedMedia::Content {
public: public:
LargeEmoji( LargeEmoji(
@ -39,12 +44,23 @@ public:
return true; return true;
} }
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
private: private:
void paintCustom(
QPainter &p,
int x,
int y,
not_null<Ui::Text::CustomEmoji*> emoji,
const PaintContext &context,
bool paused);
const not_null<Element*> _parent; const not_null<Element*> _parent;
const std::array< const std::array<LargeEmojiMedia, Ui::Text::kIsolatedEmojiLimit> _images;
std::shared_ptr<Stickers::LargeEmojiImage>, QImage _selectedFrame;
Ui::Text::kIsolatedEmojiLimit> _images;
QSize _size; QSize _size;
bool _hasHeavyPart = false;
}; };

View file

@ -19,7 +19,7 @@ namespace Ui::CustomEmoji {
namespace { namespace {
constexpr auto kMaxSize = 128; constexpr auto kMaxSize = 128;
constexpr auto kMaxFrames = 512; constexpr auto kMaxFrames = 180;
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000); constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
constexpr auto kCacheVersion = 1; constexpr auto kCacheVersion = 1;
constexpr auto kPreloadFrames = 3; constexpr auto kPreloadFrames = 3;
@ -415,7 +415,7 @@ void Renderer::frameReady(
} }
if (const auto count = generator->count()) { if (const auto count = generator->count()) {
if (!_cache.frames()) { if (!_cache.frames()) {
_cache.reserve(count); _cache.reserve(std::max(count, kMaxFrames));
} }
} }
const auto current = _cache.currentFrame(); const auto current = _cache.currentFrame();
@ -425,7 +425,7 @@ void Renderer::frameReady(
if (explicitRepaint && _repaint) { if (explicitRepaint && _repaint) {
_repaint(); _repaint();
} }
if (!duration) { if (!duration || total + 1 >= kMaxFrames) {
finish(); finish();
} else if (current + kPreloadFrames > total) { } else if (current + kPreloadFrames > total) {
renderNext(std::move(generator), std::move(frame)); renderNext(std::move(generator), std::move(frame));

@ -1 +1 @@
Subproject commit c5b32c53efbc1481f3942d28ef7e4f5eaca5df1f Subproject commit a5d7b23a638e6c2d9bffa97bf40d5d7559a926c8