mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Support custom emoji in IsolatedEmoji.
This commit is contained in:
parent
9b941bae97
commit
edfb7bb65a
10 changed files with 183 additions and 62 deletions
|
@ -150,22 +150,25 @@ auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker {
|
|||
return { i->second.get(), nullptr };
|
||||
}
|
||||
if (!emoji->colored()) {
|
||||
return Sticker();
|
||||
return {};
|
||||
}
|
||||
const auto j = _map.find(emoji->original());
|
||||
if (j != end(_map)) {
|
||||
const auto index = emoji->variantIndex(emoji);
|
||||
return { j->second.get(), ColorReplacements(index) };
|
||||
}
|
||||
return Sticker();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
|
||||
Expects(!emoji.empty());
|
||||
|
||||
return (emoji.items[1] != nullptr)
|
||||
? Sticker()
|
||||
: stickerForEmoji(emoji.items[0]);
|
||||
if (!v::is_null(emoji.items[1])) {
|
||||
return {};
|
||||
} else if (const auto regular = std::get_if<EmojiPtr>(&emoji.items[0])) {
|
||||
return stickerForEmoji(*regular);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
|
||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/text/text_block.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
|
@ -36,7 +37,8 @@ using SizeTag = CustomEmojiManager::SizeTag;
|
|||
using LottieSize = ChatHelpers::StickerLottieSize;
|
||||
switch (tag) {
|
||||
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.");
|
||||
}
|
||||
|
@ -45,6 +47,9 @@ using SizeTag = CustomEmojiManager::SizeTag;
|
|||
switch (tag) {
|
||||
case SizeTag::Normal: return Ui::Emoji::GetSizeNormal();
|
||||
case SizeTag::Large: return Ui::Emoji::GetSizeLarge();
|
||||
case SizeTag::Isolated:
|
||||
return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
|
||||
* style::DevicePixelRatio();
|
||||
}
|
||||
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
|
||||
}
|
||||
|
@ -396,20 +401,25 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
|||
Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
|
||||
DocumentId documentId,
|
||||
SizeTag tag) const {
|
||||
const auto &other = _instances[1 - SizeIndex(tag)];
|
||||
const auto j = other.find(documentId);
|
||||
if (j == end(other)) {
|
||||
return {};
|
||||
} else if (const auto nonExact = j->second->imagePreview()) {
|
||||
const auto size = SizeFromTag(tag);
|
||||
return {
|
||||
nonExact.image().scaled(
|
||||
size,
|
||||
size,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation),
|
||||
false,
|
||||
};
|
||||
for (auto i = _instances.size(); i != 0;) {
|
||||
if (SizeIndex(tag) == --i) {
|
||||
continue;
|
||||
}
|
||||
const auto &other = _instances[i];
|
||||
const auto j = other.find(documentId);
|
||||
if (j == end(other)) {
|
||||
continue;
|
||||
} else if (const auto nonExact = j->second->imagePreview()) {
|
||||
const auto size = SizeFromTag(tag);
|
||||
return {
|
||||
nonExact.image().scaled(
|
||||
size,
|
||||
size,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation),
|
||||
false,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
@ -535,7 +545,7 @@ void CustomEmojiManager::requestSetFor(not_null<DocumentData*> document) {
|
|||
int CustomEmojiManager::SizeIndex(SizeTag tag) {
|
||||
const auto result = static_cast<int>(tag);
|
||||
|
||||
Ensures(result >= 0 && result < 2);
|
||||
Ensures(result >= 0 && result < kSizeCount);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ public:
|
|||
enum class SizeTag {
|
||||
Normal,
|
||||
Large,
|
||||
Isolated,
|
||||
|
||||
kCount,
|
||||
};
|
||||
|
||||
CustomEmojiManager(not_null<Session*> owner);
|
||||
|
@ -64,6 +67,8 @@ public:
|
|||
[[nodiscard]] Session &owner() const;
|
||||
|
||||
private:
|
||||
static constexpr auto kSizeCount = int(SizeTag::kCount);
|
||||
|
||||
struct RepaintBunch {
|
||||
crl::time when = 0;
|
||||
std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>> instances;
|
||||
|
@ -91,12 +96,16 @@ private:
|
|||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
base::flat_map<
|
||||
uint64,
|
||||
std::unique_ptr<Ui::CustomEmoji::Instance>> _instances[2];
|
||||
base::flat_map<
|
||||
uint64,
|
||||
std::vector<base::weak_ptr<CustomEmojiLoader>>> _loaders[2];
|
||||
std::array<
|
||||
base::flat_map<
|
||||
uint64,
|
||||
std::unique_ptr<Ui::CustomEmoji::Instance>>,
|
||||
kSizeCount> _instances;
|
||||
std::array<
|
||||
base::flat_map<
|
||||
uint64,
|
||||
std::vector<base::weak_ptr<CustomEmojiLoader>>>,
|
||||
kSizeCount> _loaders;
|
||||
base::flat_set<uint64> _pendingForRequest;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
|
|
|
@ -1308,7 +1308,7 @@ TextWithEntities HistoryItem::inReplyText() const {
|
|||
}
|
||||
|
||||
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
|
||||
return Ui::Text::IsolatedEmoji();
|
||||
return {};
|
||||
}
|
||||
|
||||
HistoryItem::~HistoryItem() {
|
||||
|
|
|
@ -419,15 +419,18 @@ void Element::customEmojiRepaint() {
|
|||
}
|
||||
}
|
||||
|
||||
void Element::clearCustomEmojiRepaint() const {
|
||||
_customEmojiRepaintScheduled = false;
|
||||
data()->_customEmojiRepaintScheduled = false;
|
||||
}
|
||||
|
||||
void Element::prepareCustomEmojiPaint(
|
||||
Painter &p,
|
||||
const Ui::Text::String &text) const {
|
||||
const auto item = data();
|
||||
if (!text.hasCustomEmoji()) {
|
||||
return;
|
||||
}
|
||||
_customEmojiRepaintScheduled = false;
|
||||
item->_customEmojiRepaintScheduled = false;
|
||||
clearCustomEmojiRepaint();
|
||||
p.setInactive(delegate()->elementIsGifPaused());
|
||||
if (!_heavyCustomEmoji) {
|
||||
_heavyCustomEmoji = true;
|
||||
|
|
|
@ -431,6 +431,7 @@ public:
|
|||
void prepareCustomEmojiPaint(
|
||||
Painter &p,
|
||||
const Ui::Text::String &text) const;
|
||||
void clearCustomEmojiRepaint() const;
|
||||
|
||||
[[nodiscard]] ClickHandlerPtr fromPhotoLink() const {
|
||||
return fromLink();
|
||||
|
|
|
@ -14,20 +14,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
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(
|
||||
not_null<Main::Session*> session,
|
||||
Fn<void()> customEmojiRepaint,
|
||||
const Ui::Text::IsolatedEmoji &emoji)
|
||||
-> std::array<std::shared_ptr<EmojiImage>, Ui::Text::kIsolatedEmojiLimit> {
|
||||
const auto single = [&](EmojiPtr emoji) {
|
||||
return emoji ? session->emojiStickersPack().image(emoji) : nullptr;
|
||||
-> std::array<LargeEmojiMedia, Ui::Text::kIsolatedEmojiLimit> {
|
||||
const auto single = [&](Ui::Text::IsolatedEmoji::Item item)
|
||||
-> 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 { {
|
||||
single(emoji.items[0]),
|
||||
|
@ -35,28 +49,25 @@ auto ResolveImages(
|
|||
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
|
||||
|
||||
LargeEmoji::LargeEmoji(
|
||||
not_null<Element*> parent,
|
||||
const Ui::Text::IsolatedEmoji &emoji)
|
||||
: _parent(parent)
|
||||
, _images(ResolveImages(&parent->data()->history()->session(), emoji)) {
|
||||
, _images(ResolveImages(
|
||||
&parent->data()->history()->session(),
|
||||
[=] { parent->customEmojiRepaint(); },
|
||||
emoji)) {
|
||||
}
|
||||
|
||||
QSize LargeEmoji::size() {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto count = ranges::distance(NonEmpty(_images));
|
||||
Assert(count > 0);
|
||||
const auto count = _images.size()
|
||||
- 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 inner = count * single.width() + (count - 1) * skip;
|
||||
const auto &padding = st::largeEmojiPadding;
|
||||
|
@ -70,23 +81,91 @@ void LargeEmoji::draw(
|
|||
Painter &p,
|
||||
const PaintContext &context,
|
||||
const QRect &r) {
|
||||
auto &&images = NonEmpty(_images);
|
||||
_parent->clearCustomEmojiRepaint();
|
||||
|
||||
const auto &padding = st::largeEmojiPadding;
|
||||
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 skip = st::largeEmojiSkip - 2 * st::largeEmojiOutline;
|
||||
const auto size = EmojiImage::Size() / cIntRetinaFactor();
|
||||
for (const auto &image : images) {
|
||||
if (const auto &prepared = image->image) {
|
||||
const auto colored = context.selected()
|
||||
? &context.st->msgStickerOverlay()
|
||||
: nullptr;
|
||||
p.drawPixmap(x, y, prepared->pix(size, { .colored = colored }));
|
||||
} else if (image->load) {
|
||||
image->load();
|
||||
const auto size = LargeEmojiImage::Size() / cIntRetinaFactor();
|
||||
const auto paused = _parent->delegate()->elementIsGifPaused();
|
||||
const auto selected = context.selected();
|
||||
if (!selected) {
|
||||
_selectedFrame = QImage();
|
||||
}
|
||||
for (const auto &media : _images) {
|
||||
if (const auto image = std::get_if<ImagePtr>(&media)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -20,6 +20,11 @@ struct LargeEmojiImage;
|
|||
|
||||
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 {
|
||||
public:
|
||||
LargeEmoji(
|
||||
|
@ -39,12 +44,23 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool hasHeavyPart() const override;
|
||||
void unloadHeavyPart() override;
|
||||
|
||||
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 std::array<
|
||||
std::shared_ptr<Stickers::LargeEmojiImage>,
|
||||
Ui::Text::kIsolatedEmojiLimit> _images;
|
||||
const std::array<LargeEmojiMedia, Ui::Text::kIsolatedEmojiLimit> _images;
|
||||
QImage _selectedFrame;
|
||||
QSize _size;
|
||||
bool _hasHeavyPart = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Ui::CustomEmoji {
|
|||
namespace {
|
||||
|
||||
constexpr auto kMaxSize = 128;
|
||||
constexpr auto kMaxFrames = 512;
|
||||
constexpr auto kMaxFrames = 180;
|
||||
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
|
||||
constexpr auto kCacheVersion = 1;
|
||||
constexpr auto kPreloadFrames = 3;
|
||||
|
@ -415,7 +415,7 @@ void Renderer::frameReady(
|
|||
}
|
||||
if (const auto count = generator->count()) {
|
||||
if (!_cache.frames()) {
|
||||
_cache.reserve(count);
|
||||
_cache.reserve(std::max(count, kMaxFrames));
|
||||
}
|
||||
}
|
||||
const auto current = _cache.currentFrame();
|
||||
|
@ -425,7 +425,7 @@ void Renderer::frameReady(
|
|||
if (explicitRepaint && _repaint) {
|
||||
_repaint();
|
||||
}
|
||||
if (!duration) {
|
||||
if (!duration || total + 1 >= kMaxFrames) {
|
||||
finish();
|
||||
} else if (current + kPreloadFrames > total) {
|
||||
renderNext(std::move(generator), std::move(frame));
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c5b32c53efbc1481f3942d28ef7e4f5eaca5df1f
|
||||
Subproject commit a5d7b23a638e6c2d9bffa97bf40d5d7559a926c8
|
Loading…
Add table
Reference in a new issue