mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-03 21:07:53 +00:00
Start optimized custom emoji, show path thumbnail.
This commit is contained in:
parent
2e6733e433
commit
9d280da80b
@ -13,27 +13,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/stickers/data_stickers_set.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/text/text_block.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
struct CustomEmojiId {
|
||||
StickerSetIdentifier set;
|
||||
uint64 id = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id) {
|
||||
const auto &info = id.set;
|
||||
const auto set = info.id
|
||||
? (QString::number(info.id) + ':' + QString::number(info.accessHash))
|
||||
: info.shortName;
|
||||
return QString::number(id.id) + '@' + set;
|
||||
return QString::number(id.id)
|
||||
+ '@'
|
||||
+ QString::number(id.set.id)
|
||||
+ ':'
|
||||
+ QString::number(id.set.accessHash);
|
||||
}
|
||||
|
||||
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(const QString &data) {
|
||||
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data) {
|
||||
const auto parts = data.split('@');
|
||||
if (parts.size() != 2) {
|
||||
return {};
|
||||
@ -43,23 +47,20 @@ struct CustomEmojiId {
|
||||
return {};
|
||||
}
|
||||
const auto second = parts[1].split(':');
|
||||
if (const auto set = second[0].toULongLong()) {
|
||||
return {
|
||||
.set = {.id = set, .accessHash = second[1].toULongLong() },
|
||||
.id = id
|
||||
};
|
||||
}
|
||||
return {
|
||||
.set = {.shortName = second[1] },
|
||||
.set = {
|
||||
.id = second[0].toULongLong(),
|
||||
.accessHash = second[1].toULongLong(),
|
||||
},
|
||||
.id = id
|
||||
};
|
||||
}
|
||||
|
||||
class CustomEmojiWithData : public Ui::Text::CustomEmoji {
|
||||
class CustomEmojiWithData {
|
||||
public:
|
||||
explicit CustomEmojiWithData(const QString &data);
|
||||
|
||||
QString entityData() final override;
|
||||
QString entityData();
|
||||
|
||||
private:
|
||||
const QString _data;
|
||||
@ -80,7 +81,7 @@ public:
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> update);
|
||||
|
||||
void paint(QPainter &p, int x, int y) override;
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
|
||||
private:
|
||||
not_null<DocumentData*> _document;
|
||||
@ -100,7 +101,7 @@ DocumentCustomEmoji::DocumentCustomEmoji(
|
||||
, _update(update) {
|
||||
}
|
||||
|
||||
void DocumentCustomEmoji::paint(QPainter &p, int x, int y) {
|
||||
void DocumentCustomEmoji::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (!_media) {
|
||||
_media = _document->createMediaView();
|
||||
_media->automaticLoad(_document->stickerSetOrigin(), nullptr);
|
||||
@ -128,32 +129,112 @@ void DocumentCustomEmoji::paint(QPainter &p, int x, int y) {
|
||||
}
|
||||
}
|
||||
|
||||
class ResolvingCustomEmoji final : public CustomEmojiWithData {
|
||||
public:
|
||||
ResolvingCustomEmoji(const QString &data, Fn<void()> update);
|
||||
} // namespace
|
||||
|
||||
void paint(QPainter &p, int x, int y) override;
|
||||
class CustomEmojiLoader final
|
||||
: public Ui::CustomEmoji::Loader
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
CustomEmojiLoader(not_null<Session*> owner, const CustomEmojiId id);
|
||||
|
||||
[[nodiscard]] bool resolving() const;
|
||||
void resolved(not_null<DocumentData*> document);
|
||||
|
||||
void load(Fn<void(Ui::CustomEmoji::Caching)> ready) override;
|
||||
void cancel() override;
|
||||
Ui::CustomEmoji::Preview preview() override;
|
||||
|
||||
private:
|
||||
std::optional<DocumentCustomEmoji> _resolved;
|
||||
Fn<void()> _update;
|
||||
struct Resolve {
|
||||
Fn<void(Ui::CustomEmoji::Caching)> requested;
|
||||
};
|
||||
struct Process {
|
||||
std::shared_ptr<DocumentMedia> media;
|
||||
Fn<void(Ui::CustomEmoji::Caching)> callback;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
struct Load {
|
||||
not_null<DocumentData*> document;
|
||||
std::unique_ptr<Process> process;
|
||||
};
|
||||
|
||||
[[nodiscard]] static std::variant<Resolve, Load> InitialState(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId &id);
|
||||
|
||||
std::variant<Resolve, Load> _state;
|
||||
|
||||
};
|
||||
|
||||
ResolvingCustomEmoji::ResolvingCustomEmoji(
|
||||
const QString &data,
|
||||
Fn<void()> update)
|
||||
: CustomEmojiWithData(data)
|
||||
, _update(update) {
|
||||
CustomEmojiLoader::CustomEmojiLoader(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId id)
|
||||
: _state(InitialState(owner, id)) {
|
||||
}
|
||||
|
||||
void ResolvingCustomEmoji::paint(QPainter &p, int x, int y) {
|
||||
if (_resolved) {
|
||||
_resolved->paint(p, x, y);
|
||||
bool CustomEmojiLoader::resolving() const {
|
||||
return v::is<Resolve>(_state);
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::resolved(not_null<DocumentData*> document) {
|
||||
Expects(resolving());
|
||||
|
||||
auto requested = std::move(v::get<Resolve>(_state).requested);
|
||||
_state = Load{ document };
|
||||
if (requested) {
|
||||
load(std::move(requested));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
void CustomEmojiLoader::load(Fn<void(Ui::CustomEmoji::Caching)> ready) {
|
||||
if (const auto resolve = std::get_if<Resolve>(&_state)) {
|
||||
resolve->requested = std::move(ready);
|
||||
} else if (const auto load = std::get_if<Load>(&_state)) {
|
||||
if (!load->process) {
|
||||
load->process = std::make_unique<Process>(Process{
|
||||
.media = load->document->createMediaView(),
|
||||
.callback = std::move(ready),
|
||||
});
|
||||
load->process->media->checkStickerLarge();
|
||||
} else {
|
||||
load->process->callback = std::move(ready);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto CustomEmojiLoader::InitialState(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId &id)
|
||||
-> std::variant<Resolve, Load> {
|
||||
const auto document = owner->document(id.id);
|
||||
if (!document->isNull()) {
|
||||
return Load{ document };
|
||||
}
|
||||
return Resolve();
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::cancel() {
|
||||
if (const auto load = std::get_if<Load>(&_state)) {
|
||||
if (base::take(load->process)) {
|
||||
load->document->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ui::CustomEmoji::Preview CustomEmojiLoader::preview() {
|
||||
if (const auto load = std::get_if<Load>(&_state)) {
|
||||
if (const auto process = load->process.get()) {
|
||||
const auto dimensions = load->document->dimensions;
|
||||
if (!dimensions.width()) {
|
||||
return {};
|
||||
}
|
||||
const auto scale = (Ui::Emoji::GetSizeNormal() * 1.)
|
||||
/ (style::DevicePixelRatio() * dimensions.width());
|
||||
return { process->media->thumbnailPath(), scale };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
CustomEmojiManager::CustomEmojiManager(not_null<Session*> owner)
|
||||
: _owner(owner) {
|
||||
@ -165,14 +246,80 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||
const QString &data,
|
||||
Fn<void()> update) {
|
||||
const auto parsed = ParseCustomEmojiData(data);
|
||||
if (!parsed.id) {
|
||||
if (!parsed.id || !parsed.set.id) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto document = _owner->document(parsed.id);
|
||||
if (!document->isNull()) {
|
||||
return std::make_unique<DocumentCustomEmoji>(data, document, update);
|
||||
auto i = _instances.find(parsed.set.id);
|
||||
if (i == end(_instances)) {
|
||||
i = _instances.emplace(parsed.set.id).first;
|
||||
}
|
||||
return std::make_unique<ResolvingCustomEmoji>(data, update);
|
||||
auto j = i->second.find(parsed.id);
|
||||
if (j == end(i->second)) {
|
||||
using Loading = Ui::CustomEmoji::Loading;
|
||||
auto loader = std::make_unique<CustomEmojiLoader>(_owner, parsed);
|
||||
if (loader->resolving()) {
|
||||
_loaders[parsed.id].push_back(base::make_weak(loader.get()));
|
||||
}
|
||||
j = i->second.emplace(
|
||||
parsed.id,
|
||||
std::make_unique<Ui::CustomEmoji::Instance>(data, Loading{
|
||||
std::move(loader),
|
||||
Ui::CustomEmoji::Preview()
|
||||
})).first;
|
||||
}
|
||||
requestSetIfNeeded(parsed);
|
||||
|
||||
return std::make_unique<Ui::CustomEmoji::Object>(j->second.get());
|
||||
}
|
||||
|
||||
void CustomEmojiManager::requestSetIfNeeded(const CustomEmojiId &id) {
|
||||
const auto setId = id.set.id;
|
||||
auto i = _sets.find(setId);
|
||||
if (i == end(_sets)) {
|
||||
i = _sets.emplace(setId).first;
|
||||
}
|
||||
if (i->second.documents.contains(id.id)) {
|
||||
return;
|
||||
} else if (!i->second.waiting.emplace(id.id).second
|
||||
|| i->second.requestId) {
|
||||
return;
|
||||
}
|
||||
const auto api = &_owner->session().api();
|
||||
i->second.requestId = api->request(MTPmessages_GetStickerSet(
|
||||
InputStickerSet(id.set),
|
||||
MTP_int(i->second.hash)
|
||||
)).done([=](const MTPmessages_StickerSet &result) {
|
||||
const auto i = _sets.find(setId);
|
||||
Assert(i != end(_sets));
|
||||
i->second.requestId = 0;
|
||||
result.match([&](const MTPDmessages_stickerSet &data) {
|
||||
data.vset().match([&](const MTPDstickerSet &data) {
|
||||
i->second.hash = data.vhash().v;
|
||||
});
|
||||
for (const auto &entry : data.vdocuments().v) {
|
||||
const auto document = _owner->processDocument(entry);
|
||||
const auto id = document->id;
|
||||
i->second.documents.emplace(id);
|
||||
i->second.waiting.remove(id);
|
||||
if (const auto loaders = _loaders.take(id)) {
|
||||
for (const auto &weak : *loaders) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->resolved(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessages_stickerSetNotModified &) {
|
||||
});
|
||||
for (const auto &id : base::take(i->second.waiting)) {
|
||||
DEBUG_LOG(("API Error: Sticker '%1' not found for emoji.").arg(id));
|
||||
}
|
||||
}).fail([=] {
|
||||
const auto i = _sets.find(setId);
|
||||
Assert(i != end(_sets));
|
||||
i->second.requestId = 0;
|
||||
LOG(("API Error: Failed getting set '%1' for emoji.").arg(setId));
|
||||
}).send();
|
||||
}
|
||||
|
||||
Main::Session &CustomEmojiManager::session() const {
|
||||
@ -192,17 +339,18 @@ void FillTestCustomEmoji(
|
||||
auto length = 0;
|
||||
if (const auto emoji = Ui::Emoji::Find(ch, end, &length)) {
|
||||
if (const auto found = pack->stickerForEmoji(emoji)) {
|
||||
Expects(found.document->sticker() != nullptr);
|
||||
|
||||
text.entities.push_back({
|
||||
EntityType::CustomEmoji,
|
||||
(ch - begin),
|
||||
length,
|
||||
SerializeCustomEmojiId({
|
||||
found.document->sticker()->set,
|
||||
found.document->id,
|
||||
}),
|
||||
});
|
||||
Assert(found.document->sticker() != nullptr);
|
||||
if (found.document->sticker()->set.id) {
|
||||
text.entities.push_back({
|
||||
EntityType::CustomEmoji,
|
||||
(ch - begin),
|
||||
length,
|
||||
SerializeCustomEmojiId({
|
||||
found.document->sticker()->set,
|
||||
found.document->id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
ch += length;
|
||||
} else if (ch->isHighSurrogate()
|
||||
|
@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
|
||||
struct StickerSetIdentifier;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@ -14,6 +18,8 @@ class Session;
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
struct CustomEmojiId;
|
||||
class CustomEmojiLoader;
|
||||
|
||||
class CustomEmojiManager final {
|
||||
public:
|
||||
@ -28,8 +34,25 @@ public:
|
||||
[[nodiscard]] Session &owner() const;
|
||||
|
||||
private:
|
||||
struct Set {
|
||||
int32 hash = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
base::flat_set<uint64> documents;
|
||||
base::flat_set<uint64> waiting;
|
||||
};
|
||||
|
||||
void requestSetIfNeeded(const CustomEmojiId &id);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
base::flat_map<uint64, base::flat_map<
|
||||
uint64,
|
||||
std::unique_ptr<Ui::CustomEmoji::Instance>>> _instances;
|
||||
base::flat_map<uint64, Set> _sets;
|
||||
base::flat_map<
|
||||
uint64,
|
||||
std::vector<base::weak_ptr<CustomEmojiLoader>>> _loaders;
|
||||
|
||||
};
|
||||
|
||||
void FillTestCustomEmoji(
|
||||
|
@ -1242,6 +1242,10 @@ void Message::paintText(
|
||||
p.setPen(stm->historyTextFg);
|
||||
p.setFont(st::msgFont);
|
||||
item->_text.draw(p, trect.x(), trect.y(), trect.width(), style::al_left, 0, -1, context.selection);
|
||||
if (!_heavyCustomEmoji && item->_text.hasCustomEmoji()) {
|
||||
_heavyCustomEmoji = true;
|
||||
history()->owner().registerHeavyViewPart(const_cast<Message*>(this));
|
||||
}
|
||||
}
|
||||
|
||||
PointState Message::pointState(QPoint point) const {
|
||||
@ -1368,12 +1372,16 @@ void Message::toggleCommentsButtonRipple(bool pressed) {
|
||||
}
|
||||
|
||||
bool Message::hasHeavyPart() const {
|
||||
return _comments || Element::hasHeavyPart();
|
||||
return _heavyCustomEmoji || _comments || Element::hasHeavyPart();
|
||||
}
|
||||
|
||||
void Message::unloadHeavyPart() {
|
||||
Element::unloadHeavyPart();
|
||||
_comments = nullptr;
|
||||
if (_heavyCustomEmoji) {
|
||||
_heavyCustomEmoji = false;
|
||||
message()->_text.unloadCustomEmoji();
|
||||
}
|
||||
}
|
||||
|
||||
bool Message::showForwardsFromSender(
|
||||
|
@ -252,6 +252,7 @@ private:
|
||||
|
||||
Ui::Text::String _rightBadge;
|
||||
int _bubbleWidthLimit = 0;
|
||||
mutable bool _heavyCustomEmoji = false;
|
||||
|
||||
BottomInfo _bottomInfo;
|
||||
|
||||
|
206
Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp
Normal file
206
Telegram/SourceFiles/ui/text/custom_emoji_instance.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
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 "ui/text/custom_emoji_instance.h"
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
|
||||
Preview::Preview(QPainterPath path, float64 scale)
|
||||
: _data(ScaledPath{ std::move(path), scale }) {
|
||||
}
|
||||
|
||||
Preview::Preview(QImage image) : _data(std::move(image)) {
|
||||
}
|
||||
|
||||
void Preview::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (const auto path = std::get_if<ScaledPath>(&_data)) {
|
||||
paintPath(p, x, y, preview, *path);
|
||||
} else if (const auto image = std::get_if<QImage>(&_data)) {
|
||||
p.drawImage(x, y, *image);
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::paintPath(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
const QColor &preview,
|
||||
const ScaledPath &path) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(preview);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto scale = path.scale;
|
||||
const auto required = (scale != 1.);
|
||||
if (required) {
|
||||
p.save();
|
||||
}
|
||||
p.translate(x, y);
|
||||
if (required) {
|
||||
p.scale(scale, scale);
|
||||
}
|
||||
p.drawPath(path.path);
|
||||
if (required) {
|
||||
p.restore();
|
||||
} else {
|
||||
p.translate(-x, -y);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Cache(QSize size) : _size(size) {
|
||||
}
|
||||
|
||||
int Cache::frames() const {
|
||||
return _frames;
|
||||
}
|
||||
|
||||
QImage Cache::frame(int index) const {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
void Cache::reserve(int frames) {
|
||||
}
|
||||
|
||||
Cached::Cached(std::unique_ptr<Unloader> unloader, Cache cache)
|
||||
: _unloader(std::move(unloader))
|
||||
, _cache(cache) {
|
||||
}
|
||||
|
||||
void Cached::paint(QPainter &p, int x, int y) {
|
||||
p.drawImage(x, y, _cache.frame(0));
|
||||
}
|
||||
|
||||
Loading Cached::unload() {
|
||||
return Loading(_unloader->unload(), Preview(_cache.frame(0)));
|
||||
}
|
||||
|
||||
void Cacher::reserve(int frames) {
|
||||
_cache.reserve(frames);
|
||||
}
|
||||
|
||||
void Cacher::add(crl::time duration, QImage frame) {
|
||||
}
|
||||
|
||||
Cache Cacher::takeCache() {
|
||||
return std::move(_cache);
|
||||
}
|
||||
|
||||
Caching::Caching(std::unique_ptr<Cacher> cacher, Preview preview)
|
||||
: _cacher(std::move(cacher))
|
||||
, _preview(std::move(preview)) {
|
||||
}
|
||||
|
||||
void Caching::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (!_cacher->paint(p, x, y)) {
|
||||
_preview.paint(p, x, y, preview);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Cached> Caching::ready() {
|
||||
return _cacher->ready();
|
||||
}
|
||||
|
||||
Loading Caching::cancel() {
|
||||
return Loading(_cacher->cancel(), std::move(_preview));
|
||||
}
|
||||
|
||||
Loading::Loading(std::unique_ptr<Loader> loader, Preview preview)
|
||||
: _loader(std::move(loader))
|
||||
, _preview(std::move(preview)) {
|
||||
}
|
||||
|
||||
void Loading::load(Fn<void(Caching)> done) {
|
||||
_loader->load(crl::guard(this, std::move(done)));
|
||||
}
|
||||
|
||||
void Loading::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (!_preview) {
|
||||
if (auto preview = _loader->preview()) {
|
||||
_preview = std::move(preview);
|
||||
}
|
||||
}
|
||||
_preview.paint(p, x, y, preview);
|
||||
}
|
||||
|
||||
void Loading::cancel() {
|
||||
_loader->cancel();
|
||||
invalidate_weak_ptrs(this);
|
||||
}
|
||||
|
||||
Instance::Instance(const QString &entityData, Loading loading)
|
||||
: _state(std::move(loading))
|
||||
, _entityData(entityData) {
|
||||
}
|
||||
|
||||
QString Instance::entityData() const {
|
||||
return _entityData;
|
||||
}
|
||||
|
||||
void Instance::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (const auto loading = std::get_if<Loading>(&_state)) {
|
||||
loading->paint(p, x, y, preview);
|
||||
loading->load([=](Caching caching) {
|
||||
_state = std::move(caching);
|
||||
});
|
||||
} else if (const auto caching = std::get_if<Caching>(&_state)) {
|
||||
caching->paint(p, x, y, preview);
|
||||
if (auto cached = caching->ready()) {
|
||||
_state = std::move(*cached);
|
||||
}
|
||||
} else if (const auto cached = std::get_if<Cached>(&_state)) {
|
||||
cached->paint(p, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::incrementUsage() {
|
||||
++_usage;
|
||||
}
|
||||
|
||||
void Instance::decrementUsage() {
|
||||
Expects(_usage > 0);
|
||||
|
||||
if (--_usage > 0) {
|
||||
return;
|
||||
}
|
||||
if (const auto loading = std::get_if<Loading>(&_state)) {
|
||||
loading->cancel();
|
||||
} else if (const auto caching = std::get_if<Caching>(&_state)) {
|
||||
_state = caching->cancel();
|
||||
} else if (const auto cached = std::get_if<Cached>(&_state)) {
|
||||
_state = cached->unload();
|
||||
}
|
||||
}
|
||||
|
||||
Object::Object(not_null<Instance*> instance)
|
||||
: _instance(instance) {
|
||||
}
|
||||
|
||||
Object::~Object() {
|
||||
unload();
|
||||
}
|
||||
|
||||
QString Object::entityData() {
|
||||
return _instance->entityData();
|
||||
}
|
||||
|
||||
void Object::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (!_using) {
|
||||
_using = true;
|
||||
_instance->incrementUsage();
|
||||
}
|
||||
_instance->paint(p, x, y, preview);
|
||||
}
|
||||
|
||||
void Object::unload() {
|
||||
if (_using) {
|
||||
_using = false;
|
||||
_instance->decrementUsage();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui::CustomEmoji
|
181
Telegram/SourceFiles/ui/text/custom_emoji_instance.h
Normal file
181
Telegram/SourceFiles/ui/text/custom_emoji_instance.h
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
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 "ui/text/text_block.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/bytes.h"
|
||||
|
||||
class QColor;
|
||||
class QPainter;
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
|
||||
class Preview final {
|
||||
public:
|
||||
Preview() = default;
|
||||
Preview(QImage image);
|
||||
Preview(QPainterPath path, float64 scale);
|
||||
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return !v::is_null(_data);
|
||||
}
|
||||
|
||||
private:
|
||||
struct ScaledPath {
|
||||
QPainterPath path;
|
||||
float64 scale = 1.;
|
||||
};
|
||||
|
||||
void paintPath(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
const QColor &preview,
|
||||
const ScaledPath &path);
|
||||
|
||||
std::variant<v::null_t, ScaledPath, QImage> _data;
|
||||
|
||||
};
|
||||
|
||||
class Cache final {
|
||||
public:
|
||||
Cache(QSize size);
|
||||
|
||||
[[nodiscard]] int frames() const;
|
||||
[[nodiscard]] QImage frame(int index) const;
|
||||
void reserve(int frames);
|
||||
|
||||
private:
|
||||
static constexpr auto kPerRow = 30;
|
||||
|
||||
std::vector<bytes::vector> _bytes;
|
||||
std::vector<int> _durations;
|
||||
QSize _size;
|
||||
int _frames = 0;
|
||||
|
||||
};
|
||||
|
||||
class Loader;
|
||||
class Loading;
|
||||
|
||||
class Unloader {
|
||||
public:
|
||||
[[nodiscard]] virtual std::unique_ptr<Loader> unload() = 0;
|
||||
virtual ~Unloader() = default;
|
||||
};
|
||||
|
||||
class Cached final {
|
||||
public:
|
||||
Cached(std::unique_ptr<Unloader> unloader, Cache cache);
|
||||
|
||||
void paint(QPainter &p, int x, int y);
|
||||
[[nodiscard]] Loading unload();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Unloader> _unloader;
|
||||
Cache _cache;
|
||||
|
||||
};
|
||||
|
||||
class Cacher {
|
||||
public:
|
||||
virtual bool paint(QPainter &p, int x, int y) = 0;
|
||||
[[nodiscard]] virtual std::optional<Cached> ready() = 0;
|
||||
[[nodiscard]] virtual std::unique_ptr<Loader> cancel() = 0;
|
||||
virtual ~Cacher() = default;
|
||||
|
||||
protected:
|
||||
void reserve(int frames);
|
||||
void add(crl::time duration, QImage frame);
|
||||
|
||||
[[nodiscard]] Cache takeCache();
|
||||
|
||||
private:
|
||||
Cache _cache;
|
||||
|
||||
};
|
||||
|
||||
class Caching final {
|
||||
public:
|
||||
Caching(std::unique_ptr<Cacher> cacher, Preview preview);
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
|
||||
[[nodiscard]] std::optional<Cached> ready();
|
||||
[[nodiscard]] Loading cancel();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Cacher> _cacher;
|
||||
Preview _preview;
|
||||
|
||||
};
|
||||
|
||||
class Loader {
|
||||
public:
|
||||
virtual void load(Fn<void(Caching)> ready) = 0;
|
||||
virtual void cancel() = 0;
|
||||
[[nodiscard]] virtual Preview preview() = 0;
|
||||
virtual ~Loader() = default;
|
||||
};
|
||||
|
||||
class Loading final : public base::has_weak_ptr {
|
||||
public:
|
||||
Loading(std::unique_ptr<Loader> loader, Preview preview);
|
||||
|
||||
void load(Fn<void(Caching)> done);
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Loader> _loader;
|
||||
Preview _preview;
|
||||
|
||||
};
|
||||
|
||||
class Instance final {
|
||||
public:
|
||||
Instance(const QString &entityData, Loading loading);
|
||||
|
||||
[[nodiscard]] QString entityData() const;
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
|
||||
void incrementUsage();
|
||||
void decrementUsage();
|
||||
|
||||
private:
|
||||
std::variant<Loading, Caching, Cached> _state;
|
||||
QString _entityData;
|
||||
|
||||
int _usage = 0;
|
||||
|
||||
};
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
[[nodiscard]] virtual bool paused() = 0;
|
||||
virtual ~Delegate() = default;
|
||||
};
|
||||
|
||||
class Object final : public Ui::Text::CustomEmoji {
|
||||
public:
|
||||
Object(not_null<Instance*> instance);
|
||||
~Object();
|
||||
|
||||
QString entityData() override;
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview) override;
|
||||
void unload() override;
|
||||
|
||||
private:
|
||||
const not_null<Instance*> _instance;
|
||||
bool _using = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::CustomEmoji
|
@ -232,6 +232,8 @@ PRIVATE
|
||||
ui/effects/round_checkbox.h
|
||||
ui/effects/scroll_content_shadow.cpp
|
||||
ui/effects/scroll_content_shadow.h
|
||||
ui/text/custom_emoji_instance.cpp
|
||||
ui/text/custom_emoji_instance.h
|
||||
ui/text/format_song_name.cpp
|
||||
ui/text/format_song_name.h
|
||||
ui/text/format_values.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user