mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Support optimized lottie emoji.
This commit is contained in:
parent
9d280da80b
commit
6db3a0ec98
10 changed files with 831 additions and 213 deletions
|
@ -27,6 +27,10 @@ constexpr auto kDontCacheLottieAfterArea = 512 * 512;
|
|||
|
||||
} // namespace
|
||||
|
||||
uint8 LottieCacheKeyShift(uint8 replacementsTag, StickerLottieSize sizeTag) {
|
||||
return ((replacementsTag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
||||
}
|
||||
|
||||
template <typename Method>
|
||||
auto LottieCachedFromContent(
|
||||
Method &&method,
|
||||
|
@ -115,8 +119,9 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
|||
replacements,
|
||||
std::move(renderer));
|
||||
};
|
||||
const auto tag = replacements ? replacements->tag : uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
||||
const auto keyShift = LottieCacheKeyShift(
|
||||
replacements ? replacements->tag : uint8(0),
|
||||
sizeTag);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ using StickersSetFlags = base::flags<StickersSetFlag>;
|
|||
|
||||
namespace ChatHelpers {
|
||||
|
||||
enum class StickerLottieSize : uchar {
|
||||
enum class StickerLottieSize : uint8 {
|
||||
MessageHistory,
|
||||
StickerSet,
|
||||
StickersPanel,
|
||||
|
@ -66,6 +66,9 @@ enum class StickerLottieSize : uchar {
|
|||
EmojiInteractionReserved7,
|
||||
PremiumReactionPreview,
|
||||
};
|
||||
[[nodiscard]] uint8 LottieCacheKeyShift(
|
||||
uint8 replacementsTag,
|
||||
StickerLottieSize sizeTag);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
|
|
|
@ -15,9 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#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 "lottie/lottie_emoji.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/text/text_block.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
|
@ -29,6 +30,25 @@ struct CustomEmojiId {
|
|||
|
||||
namespace {
|
||||
|
||||
using SizeTag = CustomEmojiManager::SizeTag;
|
||||
|
||||
[[nodiscard]] ChatHelpers::StickerLottieSize LottieSizeFromTag(SizeTag tag) {
|
||||
using LottieSize = ChatHelpers::StickerLottieSize;
|
||||
switch (tag) {
|
||||
case SizeTag::Normal: return LottieSize::MessageHistory;
|
||||
case SizeTag::Large: return LottieSize::EmojiInteraction;
|
||||
}
|
||||
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
|
||||
}
|
||||
|
||||
[[nodiscard]] int SizeFromTag(SizeTag tag) {
|
||||
switch (tag) {
|
||||
case SizeTag::Normal: return Ui::Emoji::GetSizeNormal();
|
||||
case SizeTag::Large: return Ui::Emoji::GetSizeLarge();
|
||||
}
|
||||
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
|
||||
}
|
||||
|
||||
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id) {
|
||||
return QString::number(id.id)
|
||||
+ '@'
|
||||
|
@ -37,6 +57,15 @@ namespace {
|
|||
+ QString::number(id.set.accessHash);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString SerializeCustomEmojiId(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto sticker = document->sticker();
|
||||
return SerializeCustomEmojiId({
|
||||
sticker ? sticker->set : StickerSetIdentifier(),
|
||||
document->id,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data) {
|
||||
const auto parts = data.split('@');
|
||||
if (parts.size() != 2) {
|
||||
|
@ -56,120 +85,83 @@ namespace {
|
|||
};
|
||||
}
|
||||
|
||||
class CustomEmojiWithData {
|
||||
public:
|
||||
explicit CustomEmojiWithData(const QString &data);
|
||||
|
||||
QString entityData();
|
||||
|
||||
private:
|
||||
const QString _data;
|
||||
|
||||
};
|
||||
|
||||
CustomEmojiWithData::CustomEmojiWithData(const QString &data) : _data(data) {
|
||||
}
|
||||
|
||||
QString CustomEmojiWithData::entityData() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
class DocumentCustomEmoji final : public CustomEmojiWithData {
|
||||
public:
|
||||
DocumentCustomEmoji(
|
||||
const QString &data,
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> update);
|
||||
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
|
||||
private:
|
||||
not_null<DocumentData*> _document;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _lottie;
|
||||
Fn<void()> _update;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
DocumentCustomEmoji::DocumentCustomEmoji(
|
||||
const QString &data,
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> update)
|
||||
: CustomEmojiWithData(data)
|
||||
, _document(document)
|
||||
, _update(update) {
|
||||
}
|
||||
|
||||
void DocumentCustomEmoji::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
if (!_media) {
|
||||
_media = _document->createMediaView();
|
||||
_media->automaticLoad(_document->stickerSetOrigin(), nullptr);
|
||||
}
|
||||
if (_media->loaded() && !_lottie) {
|
||||
const auto size = Ui::Emoji::GetSizeNormal();
|
||||
_lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
_media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||
QSize(size, size),
|
||||
Lottie::Quality::High);
|
||||
_lottie->updates() | rpl::start_with_next(_update, _lifetime);
|
||||
}
|
||||
if (_lottie && _lottie->ready()) {
|
||||
const auto frame = _lottie->frame();
|
||||
p.drawImage(
|
||||
QRect(
|
||||
x,
|
||||
y,
|
||||
frame.width() / frame.devicePixelRatio(),
|
||||
frame.height() / frame.devicePixelRatio()),
|
||||
frame);
|
||||
_lottie->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CustomEmojiLoader final
|
||||
: public Ui::CustomEmoji::Loader
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
CustomEmojiLoader(not_null<Session*> owner, const CustomEmojiId id);
|
||||
CustomEmojiLoader(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId id,
|
||||
SizeTag tag);
|
||||
CustomEmojiLoader(not_null<DocumentData*> document, SizeTag tag);
|
||||
|
||||
[[nodiscard]] bool resolving() const;
|
||||
void resolved(not_null<DocumentData*> document);
|
||||
|
||||
void load(Fn<void(Ui::CustomEmoji::Caching)> ready) override;
|
||||
QString entityData() override;
|
||||
|
||||
void load(Fn<void(LoadResult)> loaded) override;
|
||||
bool loading() override;
|
||||
void cancel() override;
|
||||
Ui::CustomEmoji::Preview preview() override;
|
||||
|
||||
private:
|
||||
struct Resolve {
|
||||
Fn<void(Ui::CustomEmoji::Caching)> requested;
|
||||
Fn<void(LoadResult)> requested;
|
||||
QString entityData;
|
||||
};
|
||||
struct Process {
|
||||
std::shared_ptr<DocumentMedia> media;
|
||||
Fn<void(Ui::CustomEmoji::Caching)> callback;
|
||||
Fn<void(LoadResult)> loaded;
|
||||
base::has_weak_ptr guard;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
struct Load {
|
||||
struct Requested {
|
||||
not_null<DocumentData*> document;
|
||||
std::unique_ptr<Process> process;
|
||||
};
|
||||
struct Lookup : Requested {
|
||||
};
|
||||
struct Load : Requested {
|
||||
};
|
||||
|
||||
[[nodiscard]] static std::variant<Resolve, Load> InitialState(
|
||||
void check();
|
||||
[[nodiscard]] Storage::Cache::Key cacheKey(
|
||||
not_null<DocumentData*> document) const;
|
||||
void startCacheLookup(
|
||||
not_null<Lookup*> lookup,
|
||||
Fn<void(LoadResult)> loaded);
|
||||
void lookupDone(
|
||||
not_null<Lookup*> lookup,
|
||||
std::optional<Ui::CustomEmoji::Cache> result);
|
||||
void loadNoCache(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void(LoadResult)> loaded);
|
||||
|
||||
[[nodiscard]] static std::variant<Resolve, Lookup, Load> InitialState(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId &id);
|
||||
|
||||
std::variant<Resolve, Load> _state;
|
||||
std::variant<Resolve, Lookup, Load> _state;
|
||||
SizeTag _tag = SizeTag::Normal;
|
||||
|
||||
};
|
||||
|
||||
CustomEmojiLoader::CustomEmojiLoader(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId id)
|
||||
: _state(InitialState(owner, id)) {
|
||||
const CustomEmojiId id,
|
||||
SizeTag tag)
|
||||
: _state(InitialState(owner, id))
|
||||
, _tag(tag) {
|
||||
}
|
||||
|
||||
CustomEmojiLoader::CustomEmojiLoader(
|
||||
not_null<DocumentData*> document,
|
||||
SizeTag tag)
|
||||
: _state(Lookup{ document })
|
||||
, _tag(tag) {
|
||||
}
|
||||
|
||||
bool CustomEmojiLoader::resolving() const {
|
||||
|
@ -180,41 +172,179 @@ void CustomEmojiLoader::resolved(not_null<DocumentData*> document) {
|
|||
Expects(resolving());
|
||||
|
||||
auto requested = std::move(v::get<Resolve>(_state).requested);
|
||||
_state = Load{ document };
|
||||
_state = Lookup{ document };
|
||||
if (requested) {
|
||||
load(std::move(requested));
|
||||
}
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::load(Fn<void(Ui::CustomEmoji::Caching)> ready) {
|
||||
void CustomEmojiLoader::load(Fn<void(LoadResult)> loaded) {
|
||||
if (const auto resolve = std::get_if<Resolve>(&_state)) {
|
||||
resolve->requested = std::move(ready);
|
||||
resolve->requested = std::move(loaded);
|
||||
} else if (const auto lookup = std::get_if<Lookup>(&_state)) {
|
||||
if (!lookup->process) {
|
||||
startCacheLookup(lookup, std::move(loaded));
|
||||
} else {
|
||||
lookup->process->loaded = std::move(loaded);
|
||||
}
|
||||
} 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),
|
||||
.loaded = std::move(loaded),
|
||||
});
|
||||
load->process->media->checkStickerLarge();
|
||||
if (load->process->media->loaded()) {
|
||||
check();
|
||||
} else {
|
||||
load->document->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
check();
|
||||
}, load->process->lifetime);
|
||||
}
|
||||
} else {
|
||||
load->process->callback = std::move(ready);
|
||||
load->process->loaded = std::move(loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString CustomEmojiLoader::entityData() {
|
||||
if (const auto resolve = std::get_if<Resolve>(&_state)) {
|
||||
return resolve->entityData;
|
||||
} else if (const auto lookup = std::get_if<Lookup>(&_state)) {
|
||||
return SerializeCustomEmojiId(lookup->document);
|
||||
} else if (const auto load = std::get_if<Load>(&_state)) {
|
||||
return SerializeCustomEmojiId(load->document);
|
||||
}
|
||||
Unexpected("State in CustomEmojiLoader::entityData.");
|
||||
}
|
||||
|
||||
bool CustomEmojiLoader::loading() {
|
||||
if (const auto resolve = std::get_if<Resolve>(&_state)) {
|
||||
return (resolve->requested != nullptr);
|
||||
} else if (const auto lookup = std::get_if<Lookup>(&_state)) {
|
||||
return (lookup->process != nullptr);
|
||||
} else if (const auto load = std::get_if<Load>(&_state)) {
|
||||
return (load->process != nullptr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Storage::Cache::Key CustomEmojiLoader::cacheKey(
|
||||
not_null<DocumentData*> document) const {
|
||||
const auto baseKey = document->bigFileBaseCacheKey();
|
||||
if (!baseKey) {
|
||||
return {};
|
||||
}
|
||||
return Storage::Cache::Key{
|
||||
baseKey.high,
|
||||
baseKey.low + ChatHelpers::LottieCacheKeyShift(
|
||||
0x0F,
|
||||
LottieSizeFromTag(_tag)),
|
||||
};
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::startCacheLookup(
|
||||
not_null<Lookup*> lookup,
|
||||
Fn<void(LoadResult)> loaded) {
|
||||
const auto document = lookup->document;
|
||||
const auto key = cacheKey(document);
|
||||
if (!key) {
|
||||
loadNoCache(document, std::move(loaded));
|
||||
return;
|
||||
}
|
||||
lookup->process = std::make_unique<Process>(Process{
|
||||
.loaded = std::move(loaded),
|
||||
});
|
||||
const auto weak = base::make_weak(&lookup->process->guard);
|
||||
document->owner().cacheBigFile().get(key, [=](QByteArray value) {
|
||||
auto cache = Ui::CustomEmoji::Cache::FromSerialized(value);
|
||||
crl::on_main(weak, [=, result = std::move(cache)]() mutable {
|
||||
lookupDone(lookup, std::move(result));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::lookupDone(
|
||||
not_null<Lookup*> lookup,
|
||||
std::optional<Ui::CustomEmoji::Cache> result) {
|
||||
const auto document = lookup->document;
|
||||
if (!result) {
|
||||
loadNoCache(document, std::move(lookup->process->loaded));
|
||||
return;
|
||||
}
|
||||
const auto tag = _tag;
|
||||
auto loader = [=] {
|
||||
return std::make_unique<CustomEmojiLoader>(document, tag);
|
||||
};
|
||||
lookup->process->loaded(Ui::CustomEmoji::Cached(
|
||||
SerializeCustomEmojiId(document),
|
||||
std::move(loader),
|
||||
std::move(*result)));
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::loadNoCache(
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void(LoadResult)> loaded) {
|
||||
_state = Load{ document };
|
||||
load(std::move(loaded));
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::check() {
|
||||
using namespace Ui::CustomEmoji;
|
||||
|
||||
const auto load = std::get_if<Load>(&_state);
|
||||
Assert(load != nullptr);
|
||||
Assert(load->process != nullptr);
|
||||
|
||||
const auto media = load->process->media.get();
|
||||
const auto document = media->owner();
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
if (data.isEmpty() && filepath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
load->process->lifetime.destroy();
|
||||
|
||||
const auto tag = _tag;
|
||||
const auto size = SizeFromTag(_tag);
|
||||
auto bytes = Lottie::ReadContent(data, filepath);
|
||||
auto loader = [=] {
|
||||
return std::make_unique<CustomEmojiLoader>(document, tag);
|
||||
};
|
||||
auto put = [=, key = cacheKey(document)](QByteArray value) {
|
||||
document->owner().cacheBigFile().put(key, std::move(value));
|
||||
};
|
||||
auto generator = [=, bytes = Lottie::ReadContent(data, filepath)]() {
|
||||
return std::make_unique<Lottie::EmojiGenerator>(bytes);
|
||||
};
|
||||
auto renderer = std::make_unique<Renderer>(RendererDescriptor{
|
||||
.generator = std::move(generator),
|
||||
.put = std::move(put),
|
||||
.loader = std::move(loader),
|
||||
.size = SizeFromTag(_tag),
|
||||
});
|
||||
base::take(load->process)->loaded(Caching{
|
||||
std::move(renderer),
|
||||
SerializeCustomEmojiId(document),
|
||||
});
|
||||
}
|
||||
|
||||
auto CustomEmojiLoader::InitialState(
|
||||
not_null<Session*> owner,
|
||||
const CustomEmojiId &id)
|
||||
-> std::variant<Resolve, Load> {
|
||||
-> std::variant<Resolve, Lookup, Load> {
|
||||
const auto document = owner->document(id.id);
|
||||
if (!document->isNull()) {
|
||||
return Load{ document };
|
||||
return Lookup{ document };
|
||||
}
|
||||
return Resolve();
|
||||
}
|
||||
|
||||
void CustomEmojiLoader::cancel() {
|
||||
if (const auto load = std::get_if<Load>(&_state)) {
|
||||
if (const auto lookup = std::get_if<Lookup>(&_state)) {
|
||||
base::take(lookup->process);
|
||||
} else if (const auto load = std::get_if<Load>(&_state)) {
|
||||
if (base::take(load->process)) {
|
||||
load->document->cancel();
|
||||
}
|
||||
|
@ -222,22 +352,28 @@ void CustomEmojiLoader::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 };
|
||||
using Preview = Ui::CustomEmoji::Preview;
|
||||
const auto make = [&](not_null<DocumentData*> document) -> Preview {
|
||||
const auto dimensions = document->dimensions;
|
||||
if (!document->inlineThumbnailIsPath()
|
||||
|| !dimensions.width()) {
|
||||
return {};
|
||||
}
|
||||
const auto scale = (SizeFromTag(_tag) * 1.)
|
||||
/ (style::DevicePixelRatio() * dimensions.width());
|
||||
return { document->createMediaView()->thumbnailPath(), scale };
|
||||
};
|
||||
if (const auto lookup = std::get_if<Lookup>(&_state)) {
|
||||
return make(lookup->document);
|
||||
} else if (const auto load = std::get_if<Load>(&_state)) {
|
||||
return make(load->document);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
CustomEmojiManager::CustomEmojiManager(not_null<Session*> owner)
|
||||
: _owner(owner) {
|
||||
: _owner(owner)
|
||||
, _repaintTimer([=] { invokeRepaints(); }) {
|
||||
}
|
||||
|
||||
CustomEmojiManager::~CustomEmojiManager() = default;
|
||||
|
@ -256,20 +392,30 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
|||
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);
|
||||
auto loader = std::make_unique<CustomEmojiLoader>(
|
||||
_owner,
|
||||
parsed,
|
||||
SizeTag::Normal);
|
||||
if (loader->resolving()) {
|
||||
_loaders[parsed.id].push_back(base::make_weak(loader.get()));
|
||||
}
|
||||
const auto repaint = [=](
|
||||
not_null<Ui::CustomEmoji::Instance*> instance,
|
||||
Ui::CustomEmoji::RepaintRequest request) {
|
||||
repaintLater(instance, request);
|
||||
};
|
||||
j = i->second.emplace(
|
||||
parsed.id,
|
||||
std::make_unique<Ui::CustomEmoji::Instance>(data, Loading{
|
||||
std::make_unique<Ui::CustomEmoji::Instance>(Loading{
|
||||
std::move(loader),
|
||||
Ui::CustomEmoji::Preview()
|
||||
})).first;
|
||||
}, std::move(repaint))).first;
|
||||
}
|
||||
requestSetIfNeeded(parsed);
|
||||
|
||||
return std::make_unique<Ui::CustomEmoji::Object>(j->second.get());
|
||||
return std::make_unique<Ui::CustomEmoji::Object>(
|
||||
j->second.get(),
|
||||
std::move(update));
|
||||
}
|
||||
|
||||
void CustomEmojiManager::requestSetIfNeeded(const CustomEmojiId &id) {
|
||||
|
@ -322,6 +468,64 @@ void CustomEmojiManager::requestSetIfNeeded(const CustomEmojiId &id) {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void CustomEmojiManager::repaintLater(
|
||||
not_null<Ui::CustomEmoji::Instance*> instance,
|
||||
Ui::CustomEmoji::RepaintRequest request) {
|
||||
auto &bunch = _repaints[request.duration];
|
||||
if (bunch.when < request.when) {
|
||||
bunch.when = request.when;
|
||||
}
|
||||
bunch.instances.emplace_back(instance);
|
||||
scheduleRepaintTimer();
|
||||
}
|
||||
|
||||
void CustomEmojiManager::scheduleRepaintTimer() {
|
||||
if (_repaintTimerScheduled) {
|
||||
return;
|
||||
}
|
||||
_repaintTimerScheduled = true;
|
||||
Ui::PostponeCall(this, [=] {
|
||||
_repaintTimerScheduled = false;
|
||||
|
||||
auto next = crl::time();
|
||||
for (const auto &[duration, bunch] : _repaints) {
|
||||
if (!next || next > bunch.when) {
|
||||
next = bunch.when;
|
||||
}
|
||||
}
|
||||
if (next && (!_repaintNext || _repaintNext > next)) {
|
||||
const auto now = crl::now();
|
||||
if (now >= next) {
|
||||
_repaintNext = 0;
|
||||
_repaintTimer.cancel();
|
||||
invokeRepaints();
|
||||
} else {
|
||||
_repaintNext = next;
|
||||
_repaintTimer.callOnce(next - now);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CustomEmojiManager::invokeRepaints() {
|
||||
_repaintNext = 0;
|
||||
const auto now = crl::now();
|
||||
for (auto i = begin(_repaints); i != end(_repaints);) {
|
||||
if (i->second.when > now) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
auto bunch = std::move(i->second);
|
||||
i = _repaints.erase(i);
|
||||
for (const auto &weak : bunch.instances) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleRepaintTimer();
|
||||
}
|
||||
|
||||
Main::Session &CustomEmojiManager::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
struct StickerSetIdentifier;
|
||||
|
||||
|
@ -21,8 +23,13 @@ class Session;
|
|||
struct CustomEmojiId;
|
||||
class CustomEmojiLoader;
|
||||
|
||||
class CustomEmojiManager final {
|
||||
class CustomEmojiManager final : public base::has_weak_ptr {
|
||||
public:
|
||||
enum class SizeTag {
|
||||
Normal,
|
||||
Large,
|
||||
};
|
||||
|
||||
CustomEmojiManager(not_null<Session*> owner);
|
||||
~CustomEmojiManager();
|
||||
|
||||
|
@ -40,8 +47,17 @@ private:
|
|||
base::flat_set<uint64> documents;
|
||||
base::flat_set<uint64> waiting;
|
||||
};
|
||||
struct RepaintBunch {
|
||||
crl::time when = 0;
|
||||
std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>> instances;
|
||||
};
|
||||
|
||||
void requestSetIfNeeded(const CustomEmojiId &id);
|
||||
void repaintLater(
|
||||
not_null<Ui::CustomEmoji::Instance*> instance,
|
||||
Ui::CustomEmoji::RepaintRequest request);
|
||||
void scheduleRepaintTimer();
|
||||
void invokeRepaints();
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
|
@ -53,6 +69,11 @@ private:
|
|||
uint64,
|
||||
std::vector<base::weak_ptr<CustomEmojiLoader>>> _loaders;
|
||||
|
||||
base::flat_map<crl::time, RepaintBunch> _repaints;
|
||||
crl::time _repaintNext = 0;
|
||||
base::Timer _repaintTimer;
|
||||
bool _repaintTimerScheduled = false;
|
||||
|
||||
};
|
||||
|
||||
void FillTestCustomEmoji(
|
||||
|
|
|
@ -275,8 +275,12 @@ Message::Message(
|
|||
}
|
||||
|
||||
Message::~Message() {
|
||||
if (_comments) {
|
||||
if (_comments || _heavyCustomEmoji) {
|
||||
_comments = nullptr;
|
||||
if (_heavyCustomEmoji) {
|
||||
_heavyCustomEmoji = false;
|
||||
message()->_text.unloadCustomEmoji();
|
||||
}
|
||||
checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
@ -1241,8 +1245,20 @@ void Message::paintText(
|
|||
const auto stm = context.messageStyle();
|
||||
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()) {
|
||||
const auto custom = item->_text.hasCustomEmoji();
|
||||
if (custom) {
|
||||
p.setInactive(delegate()->elementIsGifPaused());
|
||||
}
|
||||
item->_text.draw(
|
||||
p,
|
||||
trect.x(),
|
||||
trect.y(),
|
||||
trect.width(),
|
||||
style::al_left,
|
||||
0,
|
||||
-1,
|
||||
context.selection);
|
||||
if (!_heavyCustomEmoji && custom) {
|
||||
_heavyCustomEmoji = true;
|
||||
history()->owner().registerHeavyViewPart(const_cast<Message*>(this));
|
||||
}
|
||||
|
|
|
@ -7,9 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
|
||||
#include "ui/effects/frame_generator.h"
|
||||
|
||||
#include <crl/crl_async.h>
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxFrameDuration = 86400 * crl::time(1000);
|
||||
|
||||
struct CacheHelper {
|
||||
int version = 0;
|
||||
int size = 0;
|
||||
int frames = 0;
|
||||
int length = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Preview::Preview(QPainterPath path, float64 scale)
|
||||
: _data(ScaledPath{ std::move(path), scale }) {
|
||||
|
@ -26,6 +42,10 @@ void Preview::paint(QPainter &p, int x, int y, const QColor &preview) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Preview::image() const {
|
||||
return v::is<QImage>(_data);
|
||||
}
|
||||
|
||||
void Preview::paintPath(
|
||||
QPainter &p,
|
||||
int x,
|
||||
|
@ -52,7 +72,15 @@ void Preview::paintPath(
|
|||
}
|
||||
}
|
||||
|
||||
Cache::Cache(QSize size) : _size(size) {
|
||||
Cache::Cache(int size) : _size(size) {
|
||||
}
|
||||
|
||||
std::optional<Cache> Cache::FromSerialized(const QByteArray &serialized) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray Cache::serialize() {
|
||||
return {};
|
||||
}
|
||||
|
||||
int Cache::frames() const {
|
||||
|
@ -60,53 +88,247 @@ int Cache::frames() const {
|
|||
}
|
||||
|
||||
QImage Cache::frame(int index) const {
|
||||
return QImage();
|
||||
Expects(index < _frames);
|
||||
|
||||
const auto row = index / kPerRow;
|
||||
const auto inrow = index % kPerRow;
|
||||
const auto bytes = _bytes[row].data() + inrow * frameByteSize();
|
||||
const auto data = reinterpret_cast<const uchar*>(bytes);
|
||||
return QImage(data, _size, _size, QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
|
||||
int Cache::size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
Preview Cache::makePreview() const {
|
||||
Expects(_frames > 0);
|
||||
|
||||
auto image = frame(0);
|
||||
image.detach();
|
||||
return { std::move(image) };
|
||||
}
|
||||
|
||||
void Cache::reserve(int frames) {
|
||||
const auto rows = (frames + kPerRow - 1) / kPerRow;
|
||||
if (const auto add = rows - int(_bytes.size()); add > 0) {
|
||||
_bytes.resize(rows);
|
||||
for (auto e = end(_bytes), i = e - add; i != e; ++i) {
|
||||
i->resize(kPerRow * frameByteSize());
|
||||
}
|
||||
}
|
||||
_durations.reserve(frames);
|
||||
}
|
||||
|
||||
Cached::Cached(std::unique_ptr<Unloader> unloader, Cache cache)
|
||||
: _unloader(std::move(unloader))
|
||||
, _cache(cache) {
|
||||
int Cache::frameRowByteSize() const {
|
||||
return _size * 4;
|
||||
}
|
||||
|
||||
void Cached::paint(QPainter &p, int x, int y) {
|
||||
p.drawImage(x, y, _cache.frame(0));
|
||||
int Cache::frameByteSize() const {
|
||||
return _size * frameRowByteSize();
|
||||
}
|
||||
|
||||
Loading Cached::unload() {
|
||||
return Loading(_unloader->unload(), Preview(_cache.frame(0)));
|
||||
void Cache::add(crl::time duration, const QImage &frame) {
|
||||
Expects(duration < kMaxFrameDuration);
|
||||
Expects(frame.size() == QSize(_size, _size));
|
||||
Expects(frame.format() == QImage::Format_ARGB32_Premultiplied);
|
||||
|
||||
const auto rowSize = frameRowByteSize();
|
||||
const auto frameSize = frameByteSize();
|
||||
const auto row = (_frames / kPerRow);
|
||||
const auto inrow = (_frames % kPerRow);
|
||||
const auto rows = row + 1;
|
||||
while (_bytes.size() < rows) {
|
||||
_bytes.emplace_back();
|
||||
_bytes.back().resize(kPerRow * frameSize);
|
||||
}
|
||||
const auto perLine = frame.bytesPerLine();
|
||||
auto dst = _bytes[row].data() + inrow * frameSize;
|
||||
auto src = frame.constBits();
|
||||
for (auto y = 0; y != _size; ++y) {
|
||||
memcpy(dst, src, rowSize);
|
||||
dst += rowSize;
|
||||
src += perLine;
|
||||
}
|
||||
++_frames;
|
||||
_durations.push_back(duration);
|
||||
}
|
||||
|
||||
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);
|
||||
void Cache::finish() {
|
||||
_finished = true;
|
||||
if (_frame == _frames) {
|
||||
_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Cached> Caching::ready() {
|
||||
return _cacher->ready();
|
||||
PaintFrameResult Cache::paintCurrentFrame(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
crl::time now) {
|
||||
if (!_frames) {
|
||||
return {};
|
||||
}
|
||||
const auto finishes = now ? currentFrameFinishes() : 0;
|
||||
if (finishes && now >= finishes) {
|
||||
++_frame;
|
||||
if (_finished && _frame == _frames) {
|
||||
_frame = 0;
|
||||
}
|
||||
_shown = now;
|
||||
} else if (!_shown) {
|
||||
_shown = now;
|
||||
}
|
||||
p.drawImage(
|
||||
QRect(x, y, _size, _size),
|
||||
frame(std::min(_frame, _frames - 1)));
|
||||
const auto next = currentFrameFinishes();
|
||||
const auto duration = next ? (next - _shown) : 0;
|
||||
return {
|
||||
.painted = true,
|
||||
.next = currentFrameFinishes(),
|
||||
.duration = duration,
|
||||
};
|
||||
}
|
||||
|
||||
Loading Caching::cancel() {
|
||||
return Loading(_cacher->cancel(), std::move(_preview));
|
||||
int Cache::currentFrame() const {
|
||||
return _frame;
|
||||
}
|
||||
|
||||
crl::time Cache::currentFrameFinishes() const {
|
||||
if (!_shown || _frame >= _durations.size()) {
|
||||
return 0;
|
||||
} else if (const auto duration = _durations[_frame]) {
|
||||
return _shown + duration;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Cached::Cached(
|
||||
const QString &entityData,
|
||||
Fn<std::unique_ptr<Loader>()> unloader,
|
||||
Cache cache)
|
||||
: _unloader(std::move(unloader))
|
||||
, _cache(std::move(cache))
|
||||
, _entityData(entityData) {
|
||||
}
|
||||
|
||||
QString Cached::entityData() const {
|
||||
return _entityData;
|
||||
}
|
||||
|
||||
PaintFrameResult Cached::paint(QPainter &p, int x, int y, crl::time now) {
|
||||
return _cache.paintCurrentFrame(p, x, y, now);
|
||||
}
|
||||
|
||||
Loading Cached::unload() {
|
||||
return Loading(_unloader(), _cache.makePreview());
|
||||
}
|
||||
|
||||
Renderer::Renderer(RendererDescriptor &&descriptor)
|
||||
: _cache(descriptor.size)
|
||||
, _put(std::move(descriptor.put))
|
||||
, _loader(std::move(descriptor.loader)) {
|
||||
Expects(_loader != nullptr);
|
||||
|
||||
const auto size = _cache.size();
|
||||
const auto guard = base::make_weak(this);
|
||||
crl::async([=, factory = std::move(descriptor.generator)]() mutable {
|
||||
auto generator = factory();
|
||||
auto rendered = generator->renderNext(
|
||||
QImage(),
|
||||
QSize(size, size) * style::DevicePixelRatio(),
|
||||
Qt::KeepAspectRatio);
|
||||
if (rendered.image.isNull()) {
|
||||
return;
|
||||
}
|
||||
crl::on_main(guard, [
|
||||
=,
|
||||
frame = std::move(rendered),
|
||||
generator = std::move(generator)
|
||||
]() mutable {
|
||||
frameReady(
|
||||
std::move(generator),
|
||||
frame.duration,
|
||||
std::move(frame.image));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void Renderer::frameReady(
|
||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||
crl::time duration,
|
||||
QImage frame) {
|
||||
if (frame.isNull()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (const auto count = generator->count()) {
|
||||
if (!_cache.frames()) {
|
||||
_cache.reserve(count);
|
||||
}
|
||||
}
|
||||
const auto explicitRepaint = (_cache.frames() == _cache.currentFrame());
|
||||
_cache.add(duration, frame);
|
||||
const auto size = _cache.size();
|
||||
const auto guard = base::make_weak(this);
|
||||
crl::async([
|
||||
=,
|
||||
frame = std::move(frame),
|
||||
generator = std::move(generator)
|
||||
]() mutable {
|
||||
auto rendered = generator->renderNext(
|
||||
std::move(frame),
|
||||
QSize(size, size) * style::DevicePixelRatio(),
|
||||
Qt::KeepAspectRatio);
|
||||
crl::on_main(guard, [
|
||||
=,
|
||||
frame = std::move(rendered),
|
||||
generator = std::move(generator)
|
||||
]() mutable {
|
||||
frameReady(
|
||||
std::move(generator),
|
||||
frame.duration,
|
||||
std::move(frame.image));
|
||||
});
|
||||
});
|
||||
if (explicitRepaint && _repaint) {
|
||||
_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::finish() {
|
||||
_finished = true;
|
||||
_cache.finish();
|
||||
if (_put) {
|
||||
_put(_cache.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
PaintFrameResult Renderer::paint(QPainter &p, int x, int y, crl::time now) {
|
||||
return _cache.paintCurrentFrame(p, x, y, now);
|
||||
}
|
||||
|
||||
std::optional<Cached> Renderer::ready(const QString &entityData) {
|
||||
return _finished
|
||||
? Cached{ entityData, std::move(_loader), std::move(_cache) }
|
||||
: std::optional<Cached>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Loader> Renderer::cancel() {
|
||||
return _loader();
|
||||
}
|
||||
|
||||
Preview Renderer::makePreview() const {
|
||||
return _cache.makePreview();
|
||||
}
|
||||
|
||||
void Renderer::setRepaintCallback(Fn<void()> repaint) {
|
||||
_repaint = std::move(repaint);
|
||||
}
|
||||
|
||||
Cache Renderer::takeCache() {
|
||||
return std::move(_cache);
|
||||
}
|
||||
|
||||
Loading::Loading(std::unique_ptr<Loader> loader, Preview preview)
|
||||
|
@ -114,8 +336,24 @@ Loading::Loading(std::unique_ptr<Loader> loader, Preview preview)
|
|||
, _preview(std::move(preview)) {
|
||||
}
|
||||
|
||||
void Loading::load(Fn<void(Caching)> done) {
|
||||
_loader->load(crl::guard(this, std::move(done)));
|
||||
QString Loading::entityData() const {
|
||||
return _loader->entityData();
|
||||
}
|
||||
|
||||
void Loading::load(Fn<void(Loader::LoadResult)> done) {
|
||||
_loader->load(crl::guard(this, [this, done = std::move(done)](
|
||||
Loader::LoadResult result) mutable {
|
||||
if (const auto caching = std::get_if<Caching>(&result)) {
|
||||
caching->preview = _preview
|
||||
? std::move(_preview)
|
||||
: _loader->preview();
|
||||
}
|
||||
done(std::move(result));
|
||||
}));
|
||||
}
|
||||
|
||||
bool Loading::loading() const {
|
||||
return _loader->loading();
|
||||
}
|
||||
|
||||
void Loading::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
|
@ -132,52 +370,97 @@ void Loading::cancel() {
|
|||
invalidate_weak_ptrs(this);
|
||||
}
|
||||
|
||||
Instance::Instance(const QString &entityData, Loading loading)
|
||||
Instance::Instance(
|
||||
Loading loading,
|
||||
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater)
|
||||
: _state(std::move(loading))
|
||||
, _entityData(entityData) {
|
||||
, _repaintLater(std::move(repaintLater)) {
|
||||
}
|
||||
|
||||
QString Instance::entityData() const {
|
||||
return _entityData;
|
||||
if (const auto loading = std::get_if<Loading>(&_state)) {
|
||||
return loading->entityData();
|
||||
} else if (const auto caching = std::get_if<Caching>(&_state)) {
|
||||
return caching->entityData;
|
||||
} else if (const auto cached = std::get_if<Cached>(&_state)) {
|
||||
return cached->entityData();
|
||||
}
|
||||
Unexpected("State in Instance::entityData.");
|
||||
}
|
||||
|
||||
void Instance::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
void Instance::paint(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
crl::time now,
|
||||
const QColor &preview,
|
||||
bool paused) {
|
||||
if (const auto loading = std::get_if<Loading>(&_state)) {
|
||||
loading->paint(p, x, y, preview);
|
||||
loading->load([=](Caching caching) {
|
||||
_state = std::move(caching);
|
||||
loading->load([=](Loader::LoadResult result) {
|
||||
if (auto caching = std::get_if<Caching>(&result)) {
|
||||
caching->renderer->setRepaintCallback([=] { repaint(); });
|
||||
_state = std::move(*caching);
|
||||
} else if (auto cached = std::get_if<Cached>(&result)) {
|
||||
_state = std::move(*cached);
|
||||
} else {
|
||||
Unexpected("Value in Loader::LoadResult.");
|
||||
}
|
||||
});
|
||||
} else if (const auto caching = std::get_if<Caching>(&_state)) {
|
||||
caching->paint(p, x, y, preview);
|
||||
if (auto cached = caching->ready()) {
|
||||
auto result = caching->renderer->paint(p, x, y, paused ? 0 : now);
|
||||
if (!result.painted) {
|
||||
caching->preview.paint(p, x, y, preview);
|
||||
} else {
|
||||
if (!caching->preview.image()) {
|
||||
caching->preview = caching->renderer->makePreview();
|
||||
}
|
||||
if (result.next > now) {
|
||||
_repaintLater(this, { result.next, result.duration });
|
||||
}
|
||||
}
|
||||
if (auto cached = caching->renderer->ready(caching->entityData)) {
|
||||
_state = std::move(*cached);
|
||||
}
|
||||
} else if (const auto cached = std::get_if<Cached>(&_state)) {
|
||||
cached->paint(p, x, y);
|
||||
const auto result = cached->paint(p, x, y, paused ? 0 : now);
|
||||
if (result.next > now) {
|
||||
_repaintLater(this, { result.next, result.duration });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::incrementUsage() {
|
||||
++_usage;
|
||||
void Instance::repaint() {
|
||||
for (const auto &object : _usage) {
|
||||
object->repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::decrementUsage() {
|
||||
Expects(_usage > 0);
|
||||
void Instance::incrementUsage(not_null<Object*> object) {
|
||||
_usage.emplace(object);
|
||||
}
|
||||
|
||||
if (--_usage > 0) {
|
||||
void Instance::decrementUsage(not_null<Object*> object) {
|
||||
_usage.remove(object);
|
||||
if (!_usage.empty()) {
|
||||
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();
|
||||
_state = Loading{
|
||||
caching->renderer->cancel(),
|
||||
std::move(caching->preview),
|
||||
};
|
||||
} else if (const auto cached = std::get_if<Cached>(&_state)) {
|
||||
_state = cached->unload();
|
||||
}
|
||||
_repaintLater(this, RepaintRequest());
|
||||
}
|
||||
|
||||
Object::Object(not_null<Instance*> instance)
|
||||
: _instance(instance) {
|
||||
Object::Object(not_null<Instance*> instance, Fn<void()> repaint)
|
||||
: _instance(instance)
|
||||
, _repaint(std::move(repaint)) {
|
||||
}
|
||||
|
||||
Object::~Object() {
|
||||
|
@ -188,19 +471,29 @@ QString Object::entityData() {
|
|||
return _instance->entityData();
|
||||
}
|
||||
|
||||
void Object::paint(QPainter &p, int x, int y, const QColor &preview) {
|
||||
void Object::paint(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
crl::time now,
|
||||
const QColor &preview,
|
||||
bool paused) {
|
||||
if (!_using) {
|
||||
_using = true;
|
||||
_instance->incrementUsage();
|
||||
_instance->incrementUsage(this);
|
||||
}
|
||||
_instance->paint(p, x, y, preview);
|
||||
_instance->paint(p, x, y, now, preview, paused);
|
||||
}
|
||||
|
||||
void Object::unload() {
|
||||
if (_using) {
|
||||
_using = false;
|
||||
_instance->decrementUsage();
|
||||
_instance->decrementUsage(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Object::repaint() {
|
||||
_repaint();
|
||||
}
|
||||
|
||||
} // namespace Ui::CustomEmoji
|
||||
|
|
|
@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
class QColor;
|
||||
class QPainter;
|
||||
|
||||
namespace Ui {
|
||||
class FrameGenerator;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
|
||||
class Preview final {
|
||||
|
@ -23,6 +27,7 @@ public:
|
|||
Preview(QPainterPath path, float64 scale);
|
||||
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
[[nodiscard]] bool image() const;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return !v::is_null(_data);
|
||||
|
@ -45,81 +50,124 @@ private:
|
|||
|
||||
};
|
||||
|
||||
struct PaintFrameResult {
|
||||
bool painted = false;
|
||||
crl::time next = 0;
|
||||
crl::time duration = 0;
|
||||
};
|
||||
|
||||
class Cache final {
|
||||
public:
|
||||
Cache(QSize size);
|
||||
Cache(int size);
|
||||
|
||||
[[nodiscard]] static std::optional<Cache> FromSerialized(
|
||||
const QByteArray &serialized);
|
||||
[[nodiscard]] QByteArray serialize();
|
||||
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] int frames() const;
|
||||
[[nodiscard]] QImage frame(int index) const;
|
||||
void reserve(int frames);
|
||||
void add(crl::time duration, const QImage &frame);
|
||||
void finish();
|
||||
|
||||
[[nodiscard]] Preview makePreview() const;
|
||||
|
||||
PaintFrameResult paintCurrentFrame(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
crl::time now);
|
||||
[[nodiscard]] int currentFrame() const;
|
||||
|
||||
private:
|
||||
static constexpr auto kPerRow = 30;
|
||||
static constexpr auto kPerRow = 16;
|
||||
|
||||
[[nodiscard]] int frameRowByteSize() const;
|
||||
[[nodiscard]] int frameByteSize() const;
|
||||
[[nodiscard]] crl::time currentFrameFinishes() const;
|
||||
|
||||
std::vector<bytes::vector> _bytes;
|
||||
std::vector<int> _durations;
|
||||
QSize _size;
|
||||
crl::time _shown = 0;
|
||||
int _frame = 0;
|
||||
int _size = 0;
|
||||
int _frames = 0;
|
||||
bool _finished = false;
|
||||
|
||||
};
|
||||
|
||||
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);
|
||||
Cached(
|
||||
const QString &entityData,
|
||||
Fn<std::unique_ptr<Loader>()> unloader,
|
||||
Cache cache);
|
||||
|
||||
void paint(QPainter &p, int x, int y);
|
||||
[[nodiscard]] QString entityData() const;
|
||||
|
||||
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now);
|
||||
[[nodiscard]] Loading unload();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Unloader> _unloader;
|
||||
Fn<std::unique_ptr<Loader>()> _unloader;
|
||||
Cache _cache;
|
||||
QString _entityData;
|
||||
|
||||
};
|
||||
|
||||
class Cacher {
|
||||
struct RendererDescriptor {
|
||||
Fn<std::unique_ptr<Ui::FrameGenerator>()> generator;
|
||||
Fn<void(QByteArray)> put;
|
||||
Fn<std::unique_ptr<Loader>()> loader;
|
||||
int size = 0;
|
||||
};
|
||||
|
||||
class Renderer final : public base::has_weak_ptr {
|
||||
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;
|
||||
explicit Renderer(RendererDescriptor &&descriptor);
|
||||
virtual ~Renderer() = default;
|
||||
|
||||
protected:
|
||||
void reserve(int frames);
|
||||
void add(crl::time duration, QImage frame);
|
||||
PaintFrameResult paint(QPainter &p, int x, int y, crl::time now);
|
||||
[[nodiscard]] std::optional<Cached> ready(const QString &entityData);
|
||||
[[nodiscard]] std::unique_ptr<Loader> cancel();
|
||||
|
||||
[[nodiscard]] Preview makePreview() const;
|
||||
|
||||
void setRepaintCallback(Fn<void()> repaint);
|
||||
[[nodiscard]] Cache takeCache();
|
||||
|
||||
private:
|
||||
void frameReady(
|
||||
std::unique_ptr<Ui::FrameGenerator> generator,
|
||||
crl::time duration,
|
||||
QImage frame);
|
||||
void finish();
|
||||
|
||||
Cache _cache;
|
||||
std::unique_ptr<Ui::FrameGenerator> _generator;
|
||||
Fn<void(QByteArray)> _put;
|
||||
Fn<void()> _repaint;
|
||||
Fn<std::unique_ptr<Loader>()> _loader;
|
||||
bool _finished = false;
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
struct Caching {
|
||||
std::unique_ptr<Renderer> renderer;
|
||||
QString entityData;
|
||||
Preview preview;
|
||||
};
|
||||
|
||||
class Loader {
|
||||
public:
|
||||
virtual void load(Fn<void(Caching)> ready) = 0;
|
||||
using LoadResult = std::variant<Caching, Cached>;
|
||||
[[nodiscard]] virtual QString entityData() = 0;
|
||||
virtual void load(Fn<void(LoadResult)> loaded) = 0;
|
||||
[[nodiscard]] virtual bool loading() = 0;
|
||||
virtual void cancel() = 0;
|
||||
[[nodiscard]] virtual Preview preview() = 0;
|
||||
virtual ~Loader() = default;
|
||||
|
@ -129,7 +177,10 @@ class Loading final : public base::has_weak_ptr {
|
|||
public:
|
||||
Loading(std::unique_ptr<Loader> loader, Preview preview);
|
||||
|
||||
void load(Fn<void(Caching)> done);
|
||||
[[nodiscard]] QString entityData() const;
|
||||
|
||||
void load(Fn<void(Loader::LoadResult)> done);
|
||||
[[nodiscard]] bool loading() const;
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
void cancel();
|
||||
|
||||
|
@ -139,21 +190,36 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class Instance final {
|
||||
struct RepaintRequest {
|
||||
crl::time when = 0;
|
||||
crl::time duration = 0;
|
||||
};
|
||||
|
||||
class Object;
|
||||
class Instance final : public base::has_weak_ptr {
|
||||
public:
|
||||
Instance(const QString &entityData, Loading loading);
|
||||
Instance(
|
||||
Loading loading,
|
||||
Fn<void(not_null<Instance*>, RepaintRequest)> repaintLater);
|
||||
|
||||
[[nodiscard]] QString entityData() const;
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview);
|
||||
void paint(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
crl::time now,
|
||||
const QColor &preview,
|
||||
bool paused);
|
||||
|
||||
void incrementUsage();
|
||||
void decrementUsage();
|
||||
void incrementUsage(not_null<Object*> object);
|
||||
void decrementUsage(not_null<Object*> object);
|
||||
|
||||
void repaint();
|
||||
|
||||
private:
|
||||
std::variant<Loading, Caching, Cached> _state;
|
||||
QString _entityData;
|
||||
|
||||
int _usage = 0;
|
||||
base::flat_set<not_null<Object*>> _usage;
|
||||
Fn<void(not_null<Instance*> that, RepaintRequest)> _repaintLater;
|
||||
|
||||
};
|
||||
|
||||
|
@ -165,15 +231,24 @@ public:
|
|||
|
||||
class Object final : public Ui::Text::CustomEmoji {
|
||||
public:
|
||||
Object(not_null<Instance*> instance);
|
||||
Object(not_null<Instance*> instance, Fn<void()> repaint);
|
||||
~Object();
|
||||
|
||||
QString entityData() override;
|
||||
void paint(QPainter &p, int x, int y, const QColor &preview) override;
|
||||
void paint(
|
||||
QPainter &p,
|
||||
int x,
|
||||
int y,
|
||||
crl::time now,
|
||||
const QColor &preview,
|
||||
bool paused) override;
|
||||
void unload() override;
|
||||
|
||||
void repaint();
|
||||
|
||||
private:
|
||||
const not_null<Instance*> _instance;
|
||||
Fn<void()> _repaint;
|
||||
bool _using = false;
|
||||
|
||||
};
|
||||
|
|
|
@ -841,6 +841,7 @@ void SessionController::setupPremiumToast() {
|
|||
}) | rpl::distinct_until_changed() | rpl::skip(
|
||||
1
|
||||
) | rpl::filter([=](bool premium) {
|
||||
session().mtp().requestConfig();
|
||||
return premium;
|
||||
}) | rpl::start_with_next([=] {
|
||||
Ui::Toast::Show(
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7ae2b9480ef61e222fd46e7806e46875d523502c
|
||||
Subproject commit 301708287d619c0ba01058d151d46aba2a15ec64
|
|
@ -1 +1 @@
|
|||
Subproject commit 187110f43820a70d20b77e4063286b692d53294d
|
||||
Subproject commit 6ef5ec3410ef5db33cb6413a7f05cc87a2c970bd
|
Loading…
Add table
Reference in a new issue