Use a custom animated emoji for collectible status.

This commit is contained in:
John Preston 2025-01-13 17:41:16 +04:00
parent d2be10cd4e
commit c25adf8b57
13 changed files with 195 additions and 89 deletions

View file

@ -935,13 +935,9 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto session = &show->session();
std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) {
state->statusId = id;
state->emoji = id.collectible // todo collectibles
state->emoji = id
? session->data().customEmojiManager().create(
id.collectible->documentId,
[=] { right->update(); })
: id.documentId
? session->data().customEmojiManager().create(
id.documentId,
Data::EmojiStatusCustomId(id),
[=] { right->update(); })
: nullptr;
right->resize(

View file

@ -1129,17 +1129,16 @@ void EmojiListWidget::fillRecentFrom(
});
_recentCustomIds.emplace(fakeId);
} else {
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
_recent.push_back({
.collectible = id.collectible,
.custom = resolveCustomRecent(documentId),
.custom = resolveCustomRecent(id),
.id = {
RecentEmojiDocument{ .id = documentId, .test = test },
RecentEmojiDocument{ .id = id.documentId, .test = test },
},
});
_recentCustomIds.emplace(documentId);
_recentCustomIds.emplace(id.collectible
? id.collectible->documentId
: id.documentId);
}
}
}
@ -1490,27 +1489,6 @@ void EmojiListWidget::drawCollapsedBadge(
text);
}
void EmojiListWidget::drawCollectible(
QPainter &p,
QPoint position,
Data::EmojiStatusCollectible *collectible) {
if (!collectible) {
return;
}
const auto inner = QRect(position, st::emojiPanArea);
auto gradient = QRadialGradient(inner.center(), inner.height() / 2);
gradient.setStops({
{ 0., collectible->centerColor },
{ 1., collectible->edgeColor },
});
p.setBrush(gradient);
p.setPen(Qt::NoPen);
p.drawRoundedRect(
inner,
st::emojiPanRadius,
st::emojiPanRadius);
}
void EmojiListWidget::drawRecent(
QPainter &p,
const ExpandingContext &context,
@ -1547,14 +1525,12 @@ void EmojiListWidget::drawRecent(
auto q = Painter(&_premiumMarkFrameCache);
_emojiPaintContext->position = QPoint();
drawCollectible(q, position, recent.collectible.get());
custom->paint(q, *_emojiPaintContext);
q.end();
p.drawImage(exactPosition, _premiumMarkFrameCache);
} else {
_emojiPaintContext->position = exactPosition;
drawCollectible(p, position, recent.collectible.get());
custom->paint(p, *_emojiPaintContext);
}
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
@ -1609,7 +1585,6 @@ void EmojiListWidget::drawCustom(
_emojiPaintContext->position = position
+ _innerPosition
+ _customPosition;
drawCollectible(p, position, entry.collectible.get());
entry.custom->paint(p, *_emojiPaintContext);
}
@ -1643,8 +1618,15 @@ EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
}
return {};
} else if (section == int(Section::Recent) && index < _recent.size()) {
const auto &recent = _recent[index];
if (recent.collectible) {
return {
session().data().document(recent.collectible->documentId),
recent.collectible,
};
}
const auto document = std::get_if<RecentEmojiDocument>(
&_recent[index].id.data);
&recent.id.data);
if (document) {
return { session().data().document(document->id) };
}
@ -2342,11 +2324,12 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector<CustomOne>();
set.reserve(list.size());
for (const auto document : list) {
if (_restrictedCustomList.contains(document->id)) {
const auto id = EmojiStatusId{ document->id };
if (_restrictedCustomList.contains(id.documentId)) {
continue;
} else if (const auto sticker = document->sticker()) {
set.push_back({
.custom = resolveCustomEmoji(document, lookupId),
.custom = resolveCustomEmoji(id, document, lookupId),
.document = document,
.emoji = Ui::Emoji::Find(sticker->alt),
});
@ -2401,18 +2384,19 @@ Fn<void()> EmojiListWidget::repaintCallback(
}
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
EmojiStatusId id,
not_null<DocumentData*> document,
uint64 setId) {
Expects(document->sticker() != nullptr);
const auto documentId = document->id;
const auto i = _customEmoji.find(documentId);
const auto i = _customEmoji.find(id);
const auto recentOnly = (i != end(_customEmoji)) && i->second.recentOnly;
if (i != end(_customEmoji) && !recentOnly) {
return i->second.emoji.get();
}
auto instance = document->owner().customEmojiManager().create(
document,
Data::EmojiStatusCustomId(id),
repaintCallback(documentId, setId),
Data::CustomEmojiManager::SizeTag::Large);
if (recentOnly) {
@ -2426,7 +2410,7 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
return i->second.emoji.get();
}
return _customEmoji.emplace(
documentId,
id,
CustomEmojiInstance{ .emoji = std::move(instance) }
).first->second.emoji.get();
}
@ -2444,27 +2428,37 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent(
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
DocumentId documentId) {
const auto i = _customRecent.find(documentId);
return resolveCustomRecent(EmojiStatusId{ documentId });
}
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
EmojiStatusId id) {
const auto i = id.collectible
? end(_customRecent)
: _customRecent.find(id.documentId);
if (i != end(_customRecent)) {
return i->second.get();
}
const auto j = _customEmoji.find(documentId);
const auto j = _customEmoji.find(id);
if (j != end(_customEmoji)) {
return j->second.emoji.get();
}
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId());
if (_customRecentFactory) {
if (_customRecentFactory && !id.collectible) {
return _customRecent.emplace(
documentId,
_customRecentFactory(documentId, std::move(repaint))
id.documentId,
_customRecentFactory(id.documentId, std::move(repaint))
).first->second.get();
}
auto custom = session().data().customEmojiManager().create(
documentId,
Data::EmojiStatusCustomId(id),
std::move(repaint),
Data::CustomEmojiManager::SizeTag::Large);
return _customEmoji.emplace(
documentId,
id,
CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true }
).first->second.emoji.get();
}
@ -2489,7 +2483,7 @@ void EmojiListWidget::refreshEmojiStatusCollectibles() {
if (const auto sticker = document->sticker()) {
set.push_back({
.collectible = status.collectible,
.custom = resolveCustomEmoji(document, setId),
.custom = resolveCustomEmoji(status, document, setId),
.document = document,
.emoji = Ui::Emoji::Find(sticker->alt),
});

View file

@ -325,10 +325,6 @@ private:
void selectCustom(FileChosen data);
void paint(Painter &p, ExpandingContext context, QRect clip);
void drawCollapsedBadge(QPainter &p, QPoint position, int count);
void drawCollectible(
QPainter &p,
QPoint position,
Data::EmojiStatusCollectible *collectible);
void drawRecent(
QPainter &p,
const ExpandingContext &context,
@ -389,12 +385,15 @@ private:
void fillRecent();
void fillRecentFrom(const std::vector<EmojiStatusId> &list);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
EmojiStatusId id,
not_null<DocumentData*> document,
uint64 setId);
[[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent(
Core::RecentEmojiId customId);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
DocumentId documentId);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
EmojiStatusId id);
[[nodiscard]] Fn<void()> repaintCallback(
DocumentId documentId,
uint64 setId);
@ -432,7 +431,7 @@ private:
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom;
base::flat_set<DocumentId> _restrictedCustomList;
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
base::flat_map<EmojiStatusId, CustomEmojiInstance> _customEmoji;
base::flat_map<
DocumentId,
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;

View file

@ -559,4 +559,9 @@ EmojiStatusId EmojiStatuses::fromUniqueGift(
return { .collectible = collectible };
}
EmojiStatusCollectible *EmojiStatuses::collectibleInfo(CollectibleId id) {
const auto i = _collectibleData.find(id);
return (i != end(_collectibleData)) ? i->second.get() : nullptr;
}
} // namespace Data

View file

@ -81,6 +81,7 @@ public:
void set(EmojiStatusId id, TimeId until = 0);
void set(not_null<PeerData*> peer, EmojiStatusId id, TimeId until = 0);
[[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift);
[[nodiscard]] EmojiStatusCollectible *collectibleInfo(CollectibleId id);
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_emoji_statuses.h"
#include "data/data_file_origin.h"
#include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity.
#include "data/data_peer.h"
@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h"
#include "storage/file_download.h" // kMaxFileInMemory
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/credits_graphics.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/text/custom_emoji_instance.h"
@ -117,6 +119,10 @@ private:
return u"force-static:"_q;
}
[[nodiscard]] QString CollectiblePrefix() {
return u"collectible:"_q;
}
[[nodiscard]] QString InternalPadding(QMargins value) {
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
).arg(value.left()
@ -568,6 +574,20 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
const auto ratio = style::DevicePixelRatio();
const auto size = EmojiSizeFromTag(tag) / ratio;
return userpic(data, std::move(update), size);
} else if (data.startsWith(CollectiblePrefix())) {
const auto id = data.mid(CollectiblePrefix().size()).toULongLong();
const auto emojiStatuses = &session().data().emojiStatuses();
auto info = emojiStatuses->collectibleInfo(id);
Assert(info != nullptr);
const auto documentId = info->documentId;
auto inner = create(documentId, base::duplicate(update), tag);
return Ui::Premium::MakeCollectibleEmoji(
data,
info->centerColor,
info->edgeColor,
std::move(inner),
std::move(update),
FrameSizeFromTag(tag) / style::DevicePixelRatio());
} else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) {
return MakeTopicIconEmoji(parsed, std::move(update), tag);
}
@ -1147,4 +1167,14 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory(
};
}
QString CollectibleCustomEmojiId(Data::EmojiStatusCollectible &data) {
return CollectiblePrefix() + QString::number(data.id);
}
QString EmojiStatusCustomId(const EmojiStatusId &id) {
return id.collectible
? CollectibleCustomEmojiId(*id.collectible)
: SerializeCustomEmojiId(id.documentId);
}
} // namespace Data

View file

@ -223,4 +223,8 @@ void InsertCustomEmoji(
[[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory(
not_null<Main::Session*> session);
[[nodiscard]] QString CollectibleCustomEmojiId(
Data::EmojiStatusCollectible &data);
[[nodiscard]] QString EmojiStatusCustomId(const EmojiStatusId &id);
} // namespace Data

View file

@ -113,11 +113,11 @@ namespace {
Data::PeerUpdate::Flag::EmojiStatus
) | rpl::map([=] {
const auto id = peer->emojiStatusId();
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
return documentId
? ResolveIsCustom(owner, documentId)
return id.collectible
? rpl::single(Ui::Text::SingleCustomEmoji(
Data::EmojiStatusCustomId(id)))
: id.documentId
? ResolveIsCustom(owner, id.documentId)
: rpl::single(TextWithEntities());
}) | rpl::flatten_latest() | rpl::distinct_until_changed();
}

View file

@ -371,7 +371,7 @@ struct Message::CommentsButton {
};
struct Message::FromNameStatus {
DocumentId id = 0;
EmojiStatusId id;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
int skip = 0;
};
@ -1767,14 +1767,14 @@ void Message::paintFromName(
+ std::min(availableWidth - statusWidth, nameText->maxWidth());
const auto y = trect.top();
auto color = nameFg;
color.setAlpha(115); // todo collectibles
const auto id = from ? from->emojiStatusId().documentId : 0;
color.setAlpha(115);
const auto id = from ? from->emojiStatusId() : EmojiStatusId();
if (_fromNameStatus->id != id) {
const auto that = const_cast<Message*>(this);
_fromNameStatus->custom = id
? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
history()->owner().customEmojiManager().create(
id,
Data::EmojiStatusCustomId(id),
[=] { that->customEmojiRepaint(); }),
kPlayStatusLimit)
: nullptr;
@ -2379,7 +2379,7 @@ void Message::unloadHeavyPart() {
_comments = nullptr;
if (_fromNameStatus) {
_fromNameStatus->custom = nullptr;
_fromNameStatus->id = 0;
_fromNameStatus->id = EmojiStatusId();
}
}

View file

@ -130,12 +130,9 @@ void Badge::setContent(Content content) {
: id
? nullptr
: &_st.premium;
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
if (documentId) {
if (id) {
_emojiStatus = _session->data().customEmojiManager().create(
documentId,
Data::EmojiStatusCustomId(id),
[raw = _view.data()] { raw->update(); },
sizeTag());
if (_customStatusLoopsLimit > 0) {

View file

@ -8,10 +8,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_graphics.h" // GiftGradientStops.
#include "ui/text/text_custom_emoji.h"
#include "ui/rp_widget.h"
namespace Ui {
namespace Premium {
namespace Ui::Premium {
ColoredMiniStars::ColoredMiniStars(
not_null<Ui::RpWidget*> parent,
@ -106,5 +106,79 @@ void ColoredMiniStars::setCenter(const QRect &rect) {
setSize(ministarsRect.size());
}
} // namespace Premium
} // namespace Ui
std::unique_ptr<Text::CustomEmoji> MakeCollectibleEmoji(
QStringView entityData,
QColor centerColor,
QColor edgeColor,
std::unique_ptr<Text::CustomEmoji> inner,
Fn<void()> update,
int size) {
class Emoji final : public Text::CustomEmoji {
public:
Emoji(
QStringView entityData,
QColor centerColor,
QColor edgeColor,
std::unique_ptr<Ui::Text::CustomEmoji> inner,
Fn<void()> update,
int size)
: _entityData(entityData.toString())
, _stars([=](QRect) { update(); }, MiniStars::Type::SlowStars)
, _centerColor(centerColor)
, _edgeColor(edgeColor)
, _inner(std::move(inner))
, _size(size) {
_stars.setColorOverride(QGradientStops{
{ 0., edgeColor },
{ 1., centerColor },
});
_stars.setSize(QSize(size, size));
}
int width() override {
return _inner->width();
}
QString entityData() override {
return _entityData;
}
void paint(QPainter &p, const Context &context) override {
_stars.setPosition(context.position);
_stars.paint(p);
_inner->paint(p, context);
}
void unload() override {
_inner->unload();
}
bool ready() override {
return _inner->ready();
}
bool readyInDefaultState() override {
return _inner->readyInDefaultState();
}
private:
QString _entityData;
ColoredMiniStars _stars;
QColor _centerColor;
QColor _edgeColor;
std::unique_ptr<Text::CustomEmoji> _inner;
int _size = 0;
};
return std::make_unique<Emoji>(
entityData,
centerColor,
edgeColor,
std::move(inner),
std::move(update),
size);
}
} // namespace Ui::Premium

View file

@ -11,8 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Premium {
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Ui::Premium {
class ColoredMiniStars final {
public:
@ -32,7 +37,7 @@ public:
void setPaused(bool paused);
private:
Ui::Premium::MiniStars _ministars;
MiniStars _ministars;
QRectF _ministarsRect;
QImage _frame;
QImage _mask;
@ -42,5 +47,12 @@ private:
};
} // namespace Premium
} // namespace Ui
[[nodiscard]] std::unique_ptr<Text::CustomEmoji> MakeCollectibleEmoji(
QStringView entityData,
QColor centerColor,
QColor edgeColor,
std::unique_ptr<Text::CustomEmoji> inner,
Fn<void()> update,
int size);
} // namespace Ui::Premium

View file

@ -239,17 +239,11 @@ int PeerBadge::drawPremiumEmojiStatus(
using namespace Ui::Text;
auto &manager = peer->session().data().customEmojiManager();
_emojiStatus->id = id;
_emojiStatus->emoji = id.collectible // todo collectibles
? std::make_unique<LimitedLoopsEmoji>(
manager.create(
id.collectible->documentId,
descriptor.customEmojiRepaint),
kPlayStatusLimit)
: std::make_unique<LimitedLoopsEmoji>(
manager.create(
id.documentId,
descriptor.customEmojiRepaint),
kPlayStatusLimit);
_emojiStatus->emoji = std::make_unique<LimitedLoopsEmoji>(
manager.create(
Data::EmojiStatusCustomId(id),
descriptor.customEmojiRepaint),
kPlayStatusLimit);
}
_emojiStatus->emoji->paint(p, {
.textColor = (*descriptor.premiumFg)->c,