Support OnlyCustomEmoji unwrapped messages.

This commit is contained in:
John Preston 2022-08-03 20:26:39 +03:00
parent 8a91c949c2
commit 7a88f9434e
25 changed files with 610 additions and 143 deletions

View File

@ -618,6 +618,8 @@ PRIVATE
history/view/media/history_view_call.cpp
history/view/media/history_view_contact.h
history/view/media/history_view_contact.cpp
history/view/media/history_view_custom_emoji.h
history/view/media/history_view_custom_emoji.cpp
history/view/media/history_view_dice.h
history/view/media/history_view_dice.cpp
history/view/media/history_view_document.h

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_emoji_image_loader.h"
#include "history/history_item.h"
#include "history/history.h"
#include "lottie/lottie_common.h"
#include "ui/emoji_config.h"
#include "ui/text/text_isolated_emoji.h"
@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/stickers/data_custom_emoji.h"
#include "core/core_settings.h"
#include "core/application.h"
#include "base/call_delayed.h"
@ -121,7 +123,22 @@ EmojiPack::EmojiPack(not_null<Main::Session*> session)
EmojiPack::~EmojiPack() = default;
bool EmojiPack::add(not_null<HistoryItem*> item) {
if (const auto emoji = item->isolatedEmoji()) {
if (const auto custom = item->onlyCustomEmoji()) {
auto &ids = _onlyCustomItems[item];
Assert(ids.empty());
auto &manager = item->history()->owner().customEmojiManager();
for (const auto &line : custom.lines) {
for (const auto &element : line) {
const auto &data = element.entityData;
const auto id = Data::ParseCustomEmojiData(data).id;
if (!manager.resolved(id, [] {})) {
ids.emplace(id);
_onlyCustomWaiting[id].emplace(item);
}
}
}
return true;
} else if (const auto emoji = item->isolatedEmoji()) {
_items[emoji].emplace(item);
return true;
}
@ -129,16 +146,28 @@ bool EmojiPack::add(not_null<HistoryItem*> item) {
}
void EmojiPack::remove(not_null<const HistoryItem*> item) {
Expects(item->isIsolatedEmoji());
Expects(item->isIsolatedEmoji() || item->isOnlyCustomEmoji());
const auto emoji = item->isolatedEmoji();
const auto i = _items.find(emoji);
Assert(i != end(_items));
const auto j = i->second.find(item);
Assert(j != end(i->second));
i->second.erase(j);
if (i->second.empty()) {
_items.erase(i);
if (item->isOnlyCustomEmoji()) {
if (const auto list = _onlyCustomItems.take(item)) {
for (const auto id : *list) {
const auto i = _onlyCustomWaiting.find(id);
Assert(i != end(_onlyCustomWaiting));
i->second.remove(item);
if (i->second.empty()) {
_onlyCustomWaiting.erase(i);
}
}
}
} else if (const auto emoji = item->isolatedEmoji()) {
const auto i = _items.find(emoji);
Assert(i != end(_items));
const auto j = i->second.find(item);
Assert(j != end(i->second));
i->second.erase(j);
if (i->second.empty()) {
_items.erase(i);
}
}
}

View File

@ -107,6 +107,13 @@ private:
base::flat_map<EmojiPtr, std::weak_ptr<LargeEmojiImage>> _images;
mtpRequestId _requestId = 0;
base::flat_map<
not_null<HistoryItem*>,
base::flat_set<DocumentId>> _onlyCustomItems;
base::flat_map<
DocumentId,
base::flat_set<not_null<HistoryItem*>>> _onlyCustomWaiting;
base::flat_map<
EmojiPtr,
base::flat_map<int, not_null<DocumentData*>>> _animations;

View File

@ -268,8 +268,8 @@ enum class MessageFlag : uint32 {
// Outgoing message and failed to be sent.
SendingFailed = (1U << 26),
// No media and only a several emoji text.
IsolatedEmoji = (1U << 27),
// No media and only a several emoji or an only custom emoji text.
SpecialOnlyEmoji = (1U << 27),
// Message existing in the message history.
HistoryEntry = (1U << 28),

View File

@ -35,11 +35,12 @@ constexpr auto kMaxPerRequest = 100;
using SizeTag = CustomEmojiManager::SizeTag;
[[nodiscard]] ChatHelpers::StickerLottieSize LottieSizeFromTag(SizeTag tag) {
// NB! onlyCustomEmoji dimensions caching uses last ::EmojiInteraction-s.
using LottieSize = ChatHelpers::StickerLottieSize;
switch (tag) {
case SizeTag::Normal: return LottieSize::MessageHistory;
case SizeTag::Large: return LottieSize::StickersPanel;
case SizeTag::Isolated: return LottieSize::EmojiInteraction;
case SizeTag::Normal: return LottieSize::EmojiInteraction;
case SizeTag::Large: return LottieSize::EmojiInteractionReserved1;
case SizeTag::Isolated: return LottieSize::EmojiInteractionReserved2;
}
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
}
@ -55,12 +56,6 @@ using SizeTag = CustomEmojiManager::SizeTag;
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
}
[[nodiscard]] int SizeFromTag(SizeTag tag) {
const auto emoji = EmojiSizeFromTag(tag);
const auto factor = style::DevicePixelRatio();
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
}
} // namespace
class CustomEmojiLoader final
@ -233,7 +228,7 @@ void CustomEmojiLoader::startCacheLookup(
lookup->process = std::make_unique<Process>(Process{
.loaded = std::move(loaded),
});
const auto size = SizeFromTag(_tag);
const auto size = FrameSizeFromTag(_tag);
const auto weak = base::make_weak(&lookup->process->guard);
document->owner().cacheBigFile().get(key, [=](QByteArray value) {
auto cache = Ui::CustomEmoji::Cache::FromSerialized(value, size);
@ -286,7 +281,7 @@ void CustomEmojiLoader::check() {
load->process->lifetime.destroy();
const auto tag = _tag;
const auto size = SizeFromTag(_tag);
const auto size = FrameSizeFromTag(_tag);
auto bytes = Lottie::ReadContent(data, filepath);
auto loader = [=] {
return std::make_unique<CustomEmojiLoader>(document, tag);
@ -348,7 +343,7 @@ Ui::CustomEmoji::Preview CustomEmojiLoader::preview() {
|| !dimensions.width()) {
return {};
}
const auto scale = (SizeFromTag(_tag) * 1.)
const auto scale = (FrameSizeFromTag(_tag) * 1.)
/ (style::DevicePixelRatio() * dimensions.width());
return { document->createMediaView()->thumbnailPath(), scale };
};
@ -411,7 +406,7 @@ Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
if (j == end(other)) {
continue;
} else if (const auto nonExact = j->second->imagePreview()) {
const auto size = SizeFromTag(tag);
const auto size = FrameSizeFromTag(tag);
return {
nonExact.image().scaled(
size,
@ -451,6 +446,24 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
});
}
bool CustomEmojiManager::resolved(QStringView data, Fn<void()> callback) {
return resolved(ParseCustomEmojiData(data).id, std::move(callback));
}
bool CustomEmojiManager::resolved(
DocumentId documentId,
Fn<void()> callback) {
if (_owner->document(documentId)->sticker()) {
return true;
}
_resolvers[documentId].push_back(std::move(callback));
_pendingForRequest.emplace(documentId);
if (!_requestId && _pendingForRequest.size() == 1) {
crl::on_main(this, [=] { request(); });
}
return false;
}
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
not_null<DocumentData*> document,
SizeTag tag) {
@ -510,6 +523,11 @@ void CustomEmojiManager::request() {
}
}
}
if (const auto callbacks = _resolvers.take(id)) {
for (const auto &callback : *callbacks) {
callback();
}
}
requestSetFor(document);
}
requestFinished();
@ -623,6 +641,12 @@ Session &CustomEmojiManager::owner() const {
return *_owner;
}
int FrameSizeFromTag(SizeTag tag) {
const auto emoji = EmojiSizeFromTag(tag);
const auto factor = style::DevicePixelRatio();
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
}
QString SerializeCustomEmojiId(const CustomEmojiId &id) {
return QString::number(id.id)
+ ':'

View File

@ -54,6 +54,9 @@ public:
Fn<void()> update,
SizeTag tag = SizeTag::Normal);
bool resolved(QStringView data, Fn<void()> callback);
bool resolved(DocumentId documentId, Fn<void()> callback);
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
not_null<DocumentData*> document,
SizeTag tag);
@ -98,15 +101,16 @@ private:
std::array<
base::flat_map<
uint64,
DocumentId,
std::unique_ptr<Ui::CustomEmoji::Instance>>,
kSizeCount> _instances;
std::array<
base::flat_map<
uint64,
DocumentId,
std::vector<base::weak_ptr<CustomEmojiLoader>>>,
kSizeCount> _loaders;
base::flat_set<uint64> _pendingForRequest;
base::flat_map<DocumentId, std::vector<Fn<void()>>> _resolvers;
base::flat_set<DocumentId> _pendingForRequest;
mtpRequestId _requestId = 0;
base::flat_map<crl::time, RepaintBunch> _repaints;
@ -117,6 +121,8 @@ private:
};
[[nodiscard]] int FrameSizeFromTag(CustomEmojiManager::SizeTag tag);
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id);
[[nodiscard]] QString SerializeCustomEmojiId(
not_null<DocumentData*> document);

View File

@ -1311,6 +1311,10 @@ Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
return {};
}
Ui::Text::OnlyCustomEmoji HistoryItem::onlyCustomEmoji() const {
return {};
}
HistoryItem::~HistoryItem() {
applyTTL(0);
}

View File

@ -190,7 +190,12 @@ public:
return isGroupEssential() && isEmpty();
}
[[nodiscard]] bool isIsolatedEmoji() const {
return _flags & MessageFlag::IsolatedEmoji;
return (_flags & MessageFlag::SpecialOnlyEmoji)
&& _text.isIsolatedEmoji();
}
[[nodiscard]] bool isOnlyCustomEmoji() const {
return (_flags & MessageFlag::SpecialOnlyEmoji)
&& _text.isOnlyCustomEmoji();
}
[[nodiscard]] bool hasViews() const {
return _flags & MessageFlag::HasViews;
@ -309,6 +314,7 @@ public:
ToPreviewOptions options) const;
[[nodiscard]] virtual TextWithEntities inReplyText() const;
[[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
[[nodiscard]] virtual Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const;
[[nodiscard]] virtual TextWithEntities originalText() const {
return TextWithEntities();
}

View File

@ -1491,7 +1491,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
return;
}
clearIsolatedEmoji();
clearSpecialOnlyEmoji();
const auto context = Core::MarkedTextContext{
.session = &history()->session(),
.customEmojiRepaint = [=] { customEmojiRepaint(); },
@ -1510,7 +1510,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
EnsureNonEmpty(),
Ui::ItemTextOptions(this));
} else if (!_media) {
checkIsolatedEmoji();
checkSpecialOnlyEmoji();
}
_textWidth = -1;
@ -1523,7 +1523,7 @@ void HistoryMessage::reapplyText() {
}
void HistoryMessage::setEmptyText() {
clearIsolatedEmoji();
clearSpecialOnlyEmoji();
_text.setMarkedText(
st::messageTextStyle,
{ QString(), EntitiesInText() },
@ -1533,17 +1533,17 @@ void HistoryMessage::setEmptyText() {
_textHeight = 0;
}
void HistoryMessage::clearIsolatedEmoji() {
if (!(_flags & MessageFlag::IsolatedEmoji)) {
void HistoryMessage::clearSpecialOnlyEmoji() {
if (!(_flags & MessageFlag::SpecialOnlyEmoji)) {
return;
}
history()->session().emojiStickersPack().remove(this);
_flags &= ~MessageFlag::IsolatedEmoji;
_flags &= ~MessageFlag::SpecialOnlyEmoji;
}
void HistoryMessage::checkIsolatedEmoji() {
void HistoryMessage::checkSpecialOnlyEmoji() {
if (history()->session().emojiStickersPack().add(this)) {
_flags |= MessageFlag::IsolatedEmoji;
_flags |= MessageFlag::SpecialOnlyEmoji;
}
}
@ -1596,6 +1596,10 @@ Ui::Text::IsolatedEmoji HistoryMessage::isolatedEmoji() const {
return _text.toIsolatedEmoji();
}
Ui::Text::OnlyCustomEmoji HistoryMessage::onlyCustomEmoji() const {
return _text.toOnlyCustomEmoji();
}
TextWithEntities HistoryMessage::originalText() const {
if (emptyText()) {
return { QString(), EntitiesInText() };

View File

@ -180,6 +180,7 @@ public:
void setText(const TextWithEntities &textWithEntities) override;
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const override;
[[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const override;
[[nodiscard]] TextWithEntities originalText() const override;
[[nodiscard]] auto originalTextWithLocalEntities() const
-> TextWithEntities override;
@ -232,8 +233,8 @@ private:
[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
void clearIsolatedEmoji();
void checkIsolatedEmoji();
void clearSpecialOnlyEmoji();
void checkSpecialOnlyEmoji();
// For an invoice button we replace the button text with a "Receipt" key.
// It should show the receipt for the payed invoice. Still let mobile apps do that.

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media_grouped.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_large_emoji.h"
#include "history/view/media/history_view_custom_emoji.h"
#include "history/view/history_view_react_animation.h"
#include "history/view/history_view_react_button.h"
#include "history/view/history_view_cursor_state.h"
@ -574,6 +575,13 @@ void Element::refreshMedia(Element *replacing) {
const auto session = &history()->session();
if (const auto media = _data->media()) {
_media = media->createView(this, replacing);
} else if (_data->isOnlyCustomEmoji()
&& Core::App().settings().largeEmoji()) {
_media = std::make_unique<UnwrappedMedia>(
this,
std::make_unique<CustomEmoji>(
this,
_data->onlyCustomEmoji()));
} else if (_data->isIsolatedEmoji()
&& Core::App().settings().largeEmoji()) {
const auto emoji = _data->isolatedEmoji();

View File

@ -0,0 +1,260 @@
/*
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

View File

@ -0,0 +1,84 @@
/*
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
*/
#pragma once
#include "history/view/media/history_view_media_unwrapped.h"
namespace Ui::Text {
struct OnlyCustomEmoji;
} // namespace Ui::Text
namespace Stickers {
struct LargeEmojiImage;
} // namespace Stickers
namespace HistoryView {
class Sticker;
using LargeCustomEmoji = std::variant<
QString,
std::unique_ptr<Sticker>,
std::unique_ptr<Ui::Text::CustomEmoji>>;
class CustomEmoji final : public UnwrappedMedia::Content {
public:
CustomEmoji(
not_null<Element*> parent,
const Ui::Text::OnlyCustomEmoji &emoji);
~CustomEmoji();
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
void draw(
Painter &p,
const PaintContext &context,
const QRect &r) override;
bool alwaysShowOutTimestamp() override {
return true;
}
bool hasTextForCopy() const override {
return true;
}
bool hasHeavyPart() const override;
void unloadHeavyPart() override;
private:
void paintElement(
Painter &p,
int x,
int y,
LargeCustomEmoji &element,
const PaintContext &context,
bool paused);
void paintSticker(
Painter &p,
int x,
int y,
not_null<Sticker*> sticker,
const PaintContext &context,
bool paused);
void paintCustom(
Painter &p,
int x,
int y,
not_null<Ui::Text::CustomEmoji*> emoji,
const PaintContext &context,
bool paused);
const not_null<Element*> _parent;
std::vector<std::vector<LargeCustomEmoji>> _lines;
QImage _selectedFrame;
int _singleSize = 0;
bool _hasHeavyPart = false;
};
} // namespace HistoryView

View File

@ -46,8 +46,8 @@ Dice::Dice(not_null<Element*> parent, not_null<Data::MediaDice*> dice)
Dice::~Dice() = default;
QSize Dice::size() {
return _start ? _start->size() : Sticker::EmojiSize();
QSize Dice::countOptimalSize() {
return _start ? _start->countOptimalSize() : Sticker::EmojiSize();
}
ClickHandlerPtr Dice::link() {

View File

@ -21,7 +21,7 @@ public:
Dice(not_null<Element*> parent, not_null<Data::MediaDice*> dice);
~Dice();
QSize size() override;
QSize countOptimalSize() override;
void draw(
Painter &p,
const PaintContext &context,

View File

@ -68,7 +68,7 @@ LargeEmoji::~LargeEmoji() {
}
}
QSize LargeEmoji::size() {
QSize LargeEmoji::countOptimalSize() {
using namespace rpl::mappers;
const auto count = _images.size()

View File

@ -10,10 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media_unwrapped.h"
#include "ui/text/text_isolated_emoji.h"
namespace Data {
struct FileOrigin;
} // namespace Data
namespace Stickers {
struct LargeEmojiImage;
} // namespace Stickers
@ -32,7 +28,7 @@ public:
const Ui::Text::IsolatedEmoji &emoji);
~LargeEmoji();
QSize size() override;
QSize countOptimalSize() override;
void draw(
Painter &p,
const PaintContext &context,

View File

@ -32,6 +32,10 @@ auto UnwrappedMedia::Content::stickerTakeLottie(
return nullptr;
}
QSize UnwrappedMedia::Content::countCurrentSize(int newWidth) {
return countOptimalSize();
}
UnwrappedMedia::UnwrappedMedia(
not_null<Element*> parent,
std::unique_ptr<Content> content)
@ -41,10 +45,10 @@ UnwrappedMedia::UnwrappedMedia(
QSize UnwrappedMedia::countOptimalSize() {
_content->refreshLink();
_contentSize = DownscaledSize(_content->size(), Sticker::Size());
auto maxWidth = _contentSize.width();
const auto minimal = st::largeEmojiSize + 2 * st::largeEmojiOutline;
auto minHeight = std::max(_contentSize.height(), minimal);
const auto optimal = _content->countOptimalSize();
auto maxWidth = optimal.width();
const auto minimal = st::emojiSize;
auto minHeight = std::max(optimal.height(), minimal);
if (_parent->media() == this) {
const auto item = _parent->data();
const auto via = item->Get<HistoryMessageVia>();
@ -53,17 +57,8 @@ QSize UnwrappedMedia::countOptimalSize() {
if (forwarded) {
forwarded->create(via);
}
const auto additional = additionalWidth(via, reply, forwarded);
maxWidth += additional;
maxWidth += additionalWidth(via, reply, forwarded);
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
if (const auto surrounding = surroundingInfo(via, reply, forwarded, additional - st::msgReplyPadding.left())) {
const auto infoHeight = st::msgDateImgPadding.y() * 2
+ st::msgDateFont->height;
const auto minimal = surrounding.height
+ st::msgDateImgDelta
+ infoHeight;
minHeight = std::max(minHeight, minimal);
}
if (const auto size = _parent->rightActionSize()) {
minHeight = std::max(
minHeight,
@ -76,34 +71,53 @@ QSize UnwrappedMedia::countOptimalSize() {
QSize UnwrappedMedia::countCurrentSize(int newWidth) {
const auto item = _parent->data();
accumulate_min(newWidth, maxWidth());
accumulate_max(newWidth, _parent->reactionsOptimalWidth());
const auto isPageAttach = (_parent->media() != this);
if (!isPageAttach) {
const auto via = item->Get<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto forwarded = getDisplayedForwardedInfo();
if (via || reply || forwarded) {
int usew = maxWidth() - additionalWidth(via, reply, forwarded);
int availw = newWidth - usew - st::msgReplyPadding.left() - st::msgReplyPadding.left() - st::msgReplyPadding.left();
if (via) {
via->resize(availw);
}
if (reply) {
reply->resize(availw);
}
}
_contentSize = _content->countCurrentSize(newWidth);
auto newHeight = std::max(minHeight(), _contentSize.height());
_additionalOnTop = false;
if (_parent->media() != this) {
return { newWidth, newHeight };
}
auto newHeight = minHeight();
if (!isPageAttach
&& _parent->hasOutLayout()
if (_parent->hasOutLayout()
&& !_parent->delegate()->elementIsChatWide()) {
// Add some height to isolated emoji for the timestamp info.
const auto infoHeight = st::msgDateImgPadding.y() * 2
+ st::msgDateFont->height;
const auto minimal = st::largeEmojiSize
+ 2 * st::largeEmojiOutline
+ (st::msgDateImgDelta + infoHeight);
accumulate_max(newHeight, minimal);
const auto minimal = std::min(
st::largeEmojiSize + 2 * st::largeEmojiOutline,
_contentSize.height());
accumulate_max(newHeight, minimal + st::msgDateImgDelta + infoHeight);
}
accumulate_max(newWidth, _parent->reactionsOptimalWidth());
const auto via = item->Get<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto forwarded = getDisplayedForwardedInfo();
if (via || reply || forwarded) {
const auto paddings = 3 * st::msgReplyPadding.left();
const auto additional = additionalWidth(via, reply, forwarded);
const auto optimalw = maxWidth() - additional;
const auto additionalMinWidth = std::min(additional, st::msgMinWidth / 2);
_additionalOnTop = (optimalw + paddings + additionalMinWidth) > newWidth;
const auto surrounding = surroundingInfo(via, reply, forwarded, additional - st::msgReplyPadding.left());
if (_additionalOnTop) {
_topAdded = surrounding.height;
newHeight += _topAdded;
} else {
const auto infoHeight = st::msgDateImgPadding.y() * 2
+ st::msgDateFont->height;
const auto minimal = surrounding.height
+ st::msgDateImgDelta
+ infoHeight;
newHeight = std::max(newHeight, minimal);
}
const auto availw = newWidth
- (_additionalOnTop ? 0 : optimalw)
- paddings;
if (via) {
via->resize(availw);
}
if (reply) {
reply->resize(availw);
}
}
return { newWidth, newHeight };
}
@ -116,22 +130,16 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const {
&& !_parent->delegate()->elementIsChatWide();
const auto inWebPage = (_parent->media() != this);
const auto item = _parent->data();
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
auto usex = 0;
auto usew = maxWidth();
if (!inWebPage) {
usew -= additionalWidth(via, reply, forwarded);
if (rightAligned) {
usex = width() - usew;
}
auto usew = _contentSize.width();
if (!inWebPage && rightAligned) {
usex = width() - usew;
}
if (rtl()) {
usex = width() - usex - usew;
}
const auto usey = rightAligned ? 0 : (height() - _contentSize.height());
const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
const auto useh = rightAligned
? std::max(
_contentSize.height(),
@ -144,6 +152,9 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const {
if (!inWebPage && (context.skipDrawingParts
!= PaintContext::SkipDrawingParts::Surrounding)) {
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
drawSurrounding(p, inner, context, via, reply, forwarded);
}
}
@ -201,10 +212,14 @@ void UnwrappedMedia::drawSurrounding(
InfoDisplayType::Background);
}
auto replyRight = 0;
auto rectw = width() - inner.width() - st::msgReplyPadding.left();
auto rectw = _additionalOnTop
? std::min(width() - st::msgReplyPadding.left(), additionalWidth(via, reply, forwarded))
: (width() - inner.width() - st::msgReplyPadding.left());
if (const auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) {
auto recth = surrounding.height;
int rectx = rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left());
int rectx = _additionalOnTop
? (rightAligned ? (inner.width() + st::msgReplyPadding.left() - rectw) : 0)
: (rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()));
int recty = 0;
if (rtl()) rectx = width() - rectx - rectw;
@ -254,16 +269,10 @@ PointState UnwrappedMedia::pointState(QPoint point) const {
&& !_parent->delegate()->elementIsChatWide();
const auto inWebPage = (_parent->media() != this);
const auto item = _parent->data();
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
auto usex = 0;
auto usew = maxWidth();
if (!inWebPage) {
usew -= additionalWidth(via, reply, forwarded);
if (rightAligned) {
usex = width() - usew;
}
auto usew = _contentSize.width();
if (!inWebPage && rightAligned) {
usex = width() - usew;
}
if (rtl()) {
usex = width() - usex - usew;
@ -271,7 +280,7 @@ PointState UnwrappedMedia::pointState(QPoint point) const {
const auto datey = height() - st::msgDateImgPadding.y() * 2
- st::msgDateFont->height;
const auto usey = rightAligned ? 0 : (height() - _contentSize.height());
const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
const auto useh = rightAligned
? std::max(_contentSize.height(), datey)
: _contentSize.height();
@ -295,22 +304,16 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
&& !_parent->delegate()->elementIsChatWide();
const auto inWebPage = (_parent->media() != this);
const auto item = _parent->data();
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
auto usex = 0;
auto usew = maxWidth();
if (!inWebPage) {
usew -= additionalWidth(via, reply, forwarded);
if (rightAligned) {
usex = width() - usew;
}
auto usew = _contentSize.width();
if (!inWebPage && rightAligned) {
usex = width() - usew;
}
if (rtl()) {
usex = width() - usex - usew;
}
const auto usey = rightAligned ? 0 : (height() - _contentSize.height());
const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
const auto useh = rightAligned
? std::max(
_contentSize.height(),
@ -319,11 +322,18 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
const auto inner = QRect(usex, usey, usew, useh);
if (_parent->media() == this) {
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
auto replyRight = 0;
auto rectw = width() - inner.width() - st::msgReplyPadding.left();
if (auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) {
auto rectw = _additionalOnTop
? std::min(width() - st::msgReplyPadding.left(), additionalWidth(via, reply, forwarded))
: (width() - inner.width() - st::msgReplyPadding.left());
if (const auto surrounding = surroundingInfo(via, reply, forwarded, rectw)) {
auto recth = surrounding.height;
int rectx = rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left());
int rectx = _additionalOnTop
? (rightAligned ? (inner.width() + st::msgReplyPadding.left() - rectw) : 0)
: (rightAligned ? 0 : (inner.width() + st::msgReplyPadding.left()));
int recty = 0;
if (rtl()) rectx = width() - rectx - rectw;
@ -420,10 +430,7 @@ QRect UnwrappedMedia::contentRectForReactions() const {
const auto reply = _parent->displayedReply();
const auto forwarded = getDisplayedForwardedInfo();
auto usex = 0;
auto usew = maxWidth();
if (!inWebPage) {
usew -= additionalWidth(via, reply, forwarded);
}
auto usew = _contentSize.width();
accumulate_max(usew, _parent->reactionsOptimalWidth());
if (rightAligned) {
usex = width() - usew;
@ -431,7 +438,7 @@ QRect UnwrappedMedia::contentRectForReactions() const {
if (rtl()) {
usex = width() - usex - usew;
}
const auto usey = rightAligned ? 0 : (height() - _contentSize.height());
const auto usey = rightAligned ? _topAdded : (height() - _contentSize.height());
const auto useh = rightAligned
? std::max(
_contentSize.height(),

View File

@ -21,7 +21,8 @@ class UnwrappedMedia final : public Media {
public:
class Content {
public:
[[nodiscard]] virtual QSize size() = 0;
[[nodiscard]] virtual QSize countOptimalSize() = 0;
[[nodiscard]] virtual QSize countCurrentSize(int newWidth);
virtual void draw(
Painter &p,
@ -161,6 +162,8 @@ private:
std::unique_ptr<Content> _content;
QSize _contentSize;
int _topAdded = 0;
bool _additionalOnTop = false;
};

View File

@ -139,8 +139,8 @@ bool SlotMachine::isEndResolved() const {
return _end[0].has_value() || (_dice->value() != kWinValue);
}
QSize SlotMachine::size() {
return _pull ? _pull->size() : Sticker::EmojiSize();
QSize SlotMachine::countOptimalSize() {
return _pull ? _pull->countOptimalSize() : Sticker::EmojiSize();
}
ClickHandlerPtr SlotMachine::link() {

View File

@ -21,7 +21,7 @@ public:
SlotMachine(not_null<Element*> parent, not_null<Data::MediaDice*> dice);
~SlotMachine();
QSize size() override;
QSize countOptimalSize() override;
void draw(
Painter &p,
const PaintContext &context,

View File

@ -68,6 +68,10 @@ Sticker::Sticker(
: _parent(parent)
, _data(data)
, _replacements(replacements)
, _cachingTag(ChatHelpers::StickerLottieSize::MessageHistory)
, _lottieOncePlayed(false)
, _premiumEffectPlayed(false)
, _nextLastDiceFrame(false)
, _skipPremiumEffect(skipPremiumEffect) {
if ((_dataMedia = _data->activeMediaView())) {
dataMediaCreated();
@ -106,6 +110,10 @@ bool Sticker::hasPremiumEffect() const {
return !_skipPremiumEffect && _data->isPremiumSticker();
}
bool Sticker::customEmojiPart() const {
return (_cachingTag != ChatHelpers::StickerLottieSize::MessageHistory);
}
bool Sticker::isEmojiSticker() const {
return (_parent->data()->media() == nullptr);
}
@ -126,11 +134,11 @@ void Sticker::initSize() {
}
}
QSize Sticker::size() {
QSize Sticker::countOptimalSize() {
if (_size.isEmpty()) {
initSize();
}
return _size;
return DownscaledSize(_size, Size());
}
bool Sticker::readyToDrawLottie() {
@ -184,6 +192,10 @@ void Sticker::draw(
Painter &p,
const PaintContext &context,
const QRect &r) {
if (!customEmojiPart() && isEmojiSticker()) {
_parent->clearCustomEmojiRepaint();
}
ensureDataMediaCreated();
if (readyToDrawLottie()) {
paintLottie(p, context, r);
@ -257,7 +269,7 @@ void Sticker::paintLottie(
? true
: (_diceIndex == 0)
? false
: (isEmojiSticker()
: ((!customEmojiPart() && isEmojiSticker())
|| !Core::App().settings().loopAnimatedStickers());
const auto lastDiceFrame = (_diceIndex > 0) && atTheEnd();
const auto switchToNext = /*(_externalInfo.frame >= 0)
@ -445,14 +457,21 @@ void Sticker::setDiceIndex(const QString &emoji, int index) {
_diceIndex = index;
}
void Sticker::setCustomEmojiPart(
int size,
ChatHelpers::StickerLottieSize tag) {
_size = { size, size };
_cachingTag = tag;
}
void Sticker::setupLottie() {
Expects(_dataMedia != nullptr);
_lottie = ChatHelpers::LottiePlayerFromDocument(
_dataMedia.get(),
_replacements,
ChatHelpers::StickerLottieSize::MessageHistory,
size() * style::DevicePixelRatio(),
_cachingTag,
countOptimalSize() * style::DevicePixelRatio(),
Lottie::Quality::High);
checkPremiumEffectStart();
lottieCreated();
@ -473,10 +492,10 @@ void Sticker::lottieCreated() {
_lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {
v::match(update.data, [&](const Lottie::Information &information) {
_parent->history()->owner().requestViewResize(_parent);
_parent->customEmojiRepaint();
//markFramesTillExternal();
}, [&](const Lottie::DisplayFrameRequest &request) {
_parent->history()->owner().requestViewRepaint(_parent);
_parent->customEmojiRepaint();
});
}, _lifetime);
}

View File

@ -24,6 +24,10 @@ class SinglePlayer;
struct ColorReplacements;
} // namespace Lottie
namespace ChatHelpers {
enum class StickerLottieSize : uint8;
} // namespace ChatHelpers
namespace HistoryView {
class Sticker final
@ -39,7 +43,7 @@ public:
~Sticker();
void initSize();
QSize size() override;
QSize countOptimalSize() override;
void draw(
Painter &p,
const PaintContext &context,
@ -65,6 +69,7 @@ public:
}
void setDiceIndex(const QString &emoji, int index);
void setCustomEmojiPart(int size, ChatHelpers::StickerLottieSize tag);
[[nodiscard]] bool atTheEnd() const {
return (_frameIndex >= 0) && (_frameIndex + 1 == _framesCount);
}
@ -92,6 +97,7 @@ public:
private:
[[nodiscard]] bool hasPremiumEffect() const;
[[nodiscard]] bool customEmojiPart() const;
[[nodiscard]] bool isEmojiSticker() const;
void paintLottie(Painter &p, const PaintContext &context, const QRect &r);
bool paintPixmap(Painter &p, const PaintContext &context, const QRect &r);
@ -123,10 +129,11 @@ private:
int _diceIndex = -1;
mutable int _frameIndex = -1;
mutable int _framesCount = -1;
mutable bool _lottieOncePlayed = false;
mutable bool _premiumEffectPlayed = false;
mutable bool _nextLastDiceFrame = false;
bool _skipPremiumEffect = false;
ChatHelpers::StickerLottieSize _cachingTag = {};
mutable bool _lottieOncePlayed : 1;
mutable bool _premiumEffectPlayed : 1;
mutable bool _nextLastDiceFrame : 1;
bool _skipPremiumEffect : 1;
rpl::lifetime _lifetime;

@ -1 +1 @@
Subproject commit d69b49fdd7bcb0b3414bc66fb34606dd56f695ba
Subproject commit 500731e1f9a4a8b98e388e7a06b91b41d8df7211

@ -1 +1 @@
Subproject commit 4768e7ee03aa22f64f73dc13016d5bd94a047496
Subproject commit f27d756bcd7508250a3f7ff73bf6053308c18cd6