/* 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; using StickerPtr = std::unique_ptr; struct CustomEmojiSizeInfo { LottieSize tag = LottieSize::MessageHistory; float64 scale = 1.; }; [[nodiscard]] const base::flat_map &SizesInfo() { // size = i->second.scale * st::maxAnimatedEmojiSize. // CustomEmojiManager::SizeTag caching uses first ::EmojiInteraction-s. using Info = CustomEmojiSizeInfo; static auto result = base::flat_map{ { 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 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::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( 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::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(&element)) { paintSticker(p, x, y, sticker->get(), context, paused); } else if (const auto custom = std::get_if(&element)) { paintCustom(p, x, y, custom->get(), context, paused); } } void CustomEmoji::paintSticker( Painter &p, int x, int y, not_null 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 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(&element)) { (*sticker)->unloadHeavyPart(); } else if (const auto custom = std::get_if(&element)) { (*custom)->unload(); } }; _hasHeavyPart = false; for (const auto &line : _lines) { for (const auto &element : line) { unload(element); } } } } // namespace HistoryView