tdesktop/Telegram/SourceFiles/history/view/media/history_view_custom_emoji.cpp

261 lines
7.1 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_custom_emoji.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/stickers/data_custom_emoji.h"
#include "chat_helpers/stickers_lottie.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_isolated_emoji.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
using SizeTag = Data::CustomEmojiManager::SizeTag;
using LottieSize = ChatHelpers::StickerLottieSize;
using CustomPtr = std::unique_ptr<Ui::Text::CustomEmoji>;
using StickerPtr = std::unique_ptr<Sticker>;
struct CustomEmojiSizeInfo {
LottieSize tag = LottieSize::MessageHistory;
float64 scale = 1.;
};
[[nodiscard]] const base::flat_map<int, CustomEmojiSizeInfo> &SizesInfo() {
// size = i->second.scale * st::maxAnimatedEmojiSize.
// CustomEmojiManager::SizeTag caching uses first ::EmojiInteraction-s.
using Info = CustomEmojiSizeInfo;
static auto result = base::flat_map<int, Info>{
{ 1, Info{ LottieSize::EmojiInteractionReserved7, 1. } },
{ 2, Info{ LottieSize::EmojiInteractionReserved6, 0.7 } },
{ 3, Info{ LottieSize::EmojiInteractionReserved5, 0.52 } },
};
return result;
}
[[nodiscard]] SizeTag EmojiSize(int dimension) {
return (dimension == 4 || dimension == 5)
? SizeTag::Isolated
: (dimension == 6 || dimension == 7)
? SizeTag::Large
: SizeTag::Normal;
}
[[nodiscard]] bool IsLargeSizeDimension(int dimension) {
return (dimension == 5) || (dimension == 6);
}
} //namespace
CustomEmoji::CustomEmoji(
not_null<Element*> parent,
const Ui::Text::OnlyCustomEmoji &emoji)
: _parent(parent) {
Expects(!emoji.lines.empty());
auto resolving = false;
const auto owner = &parent->data()->history()->owner();
const auto manager = &owner->customEmojiManager();
const auto max = ranges::max_element(
emoji.lines,
std::less<>(),
&std::vector<Ui::Text::OnlyCustomEmoji::Item>::size);
const auto dimension = int(std::max(emoji.lines.size(), max->size()));
const auto &sizes = SizesInfo();
const auto i = sizes.find(dimension);
const auto useCustomEmoji = (i == end(sizes));
const auto tag = EmojiSize(dimension);
_singleSize = !useCustomEmoji
? int(base::SafeRound(i->second.scale * st::maxAnimatedEmojiSize))
: Data::FrameSizeFromTag(tag);
for (const auto &line : emoji.lines) {
_lines.emplace_back();
for (const auto &element : line) {
if (useCustomEmoji) {
_lines.back().push_back(
manager->create(
element.entityData,
[=] { parent->customEmojiRepaint(); },
tag));
} else {
const auto &data = element.entityData;
const auto id = Data::ParseCustomEmojiData(data).id;
const auto document = owner->document(id);
if (document->sticker()) {
const auto skipPremiumEffect = false;
auto sticker = std::make_unique<Sticker>(
parent,
document,
skipPremiumEffect);
sticker->setCustomEmojiPart(_singleSize, i->second.tag);
_lines.back().push_back(std::move(sticker));
} else {
_lines.back().push_back(element.entityData);
resolving = true;
}
}
}
}
if (resolving) {
}
}
CustomEmoji::~CustomEmoji() {
if (_hasHeavyPart) {
unloadHeavyPart();
_parent->checkHeavyPart();
}
}
QSize CustomEmoji::countOptimalSize() {
Expects(!_lines.empty());
const auto max = ranges::max_element(
_lines,
std::less<>(),
&std::vector<LargeCustomEmoji>::size);
return {
_singleSize * int(max->size()),
_singleSize * int(_lines.size()),
};
}
QSize CustomEmoji::countCurrentSize(int newWidth) {
const auto perRow = std::max(newWidth / _singleSize, 1);
auto width = 0;
auto height = 0;
for (const auto &line : _lines) {
const auto count = int(line.size());
accumulate_max(width, std::min(perRow, count) * _singleSize);
height += std::max((count + perRow - 1) / perRow, 1) * _singleSize;
}
return { width, height };
}
void CustomEmoji::draw(
Painter &p,
const PaintContext &context,
const QRect &r) {
_parent->clearCustomEmojiRepaint();
auto x = r.x();
auto y = r.y();
const auto perRow = std::max(r.width() / _singleSize, 1);
const auto paused = _parent->delegate()->elementIsGifPaused();
for (auto &line : _lines) {
const auto count = int(line.size());
const auto rows = std::max((count + perRow - 1) / perRow, 1);
for (auto row = 0; row != rows; ++row) {
for (auto column = 0; column != perRow; ++column) {
const auto index = row * perRow + column;
if (index >= count) {
break;
}
paintElement(p, x, y, line[index], context, paused);
x += _singleSize;
}
x = r.x();
y += _singleSize;
}
}
}
void CustomEmoji::paintElement(
Painter &p,
int x,
int y,
LargeCustomEmoji &element,
const PaintContext &context,
bool paused) {
if (const auto sticker = std::get_if<StickerPtr>(&element)) {
paintSticker(p, x, y, sticker->get(), context, paused);
} else if (const auto custom = std::get_if<CustomPtr>(&element)) {
paintCustom(p, x, y, custom->get(), context, paused);
}
}
void CustomEmoji::paintSticker(
Painter &p,
int x,
int y,
not_null<Sticker*> sticker,
const PaintContext &context,
bool paused) {
sticker->draw(p, context, { QPoint(x, y), sticker->countOptimalSize() });
}
void CustomEmoji::paintCustom(
Painter &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 CustomEmoji::hasHeavyPart() const {
return _hasHeavyPart;
}
void CustomEmoji::unloadHeavyPart() {
if (!_hasHeavyPart) {
return;
}
const auto unload = [&](const LargeCustomEmoji &element) {
if (const auto sticker = std::get_if<StickerPtr>(&element)) {
(*sticker)->unloadHeavyPart();
} else if (const auto custom = std::get_if<CustomPtr>(&element)) {
(*custom)->unload();
}
};
_hasHeavyPart = false;
for (const auto &line : _lines) {
for (const auto &element : line) {
unload(element);
}
}
}
} // namespace HistoryView