Support custom emoji reactions in WhoReacted.

This commit is contained in:
John Preston 2022-08-26 23:53:48 +04:00
parent ba83836922
commit 668a3308be
22 changed files with 538 additions and 246 deletions

View file

@ -2169,6 +2169,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**.";
"lng_context_animated_reaction" = "This reaction is from **{name} pack**.";
"lng_context_animated_reactions" = "Reactions contain emoji from **{name} pack**.";
"lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**.";
"lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**.";
"lng_downloads_section" = "Downloads";
"lng_downloads_view_in_chat" = "View in chat";

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "history/history.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_peer.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
@ -112,7 +113,7 @@ struct Context {
struct Userpic {
not_null<PeerData*> peer;
QString reaction;
QString customEntityData;
mutable std::shared_ptr<Data::CloudImageView> view;
mutable InMemoryKey uniqueKey;
};
@ -377,17 +378,16 @@ bool UpdateUserpics(
auto now = std::vector<Userpic>();
for (const auto &resolved : peers) {
const auto peer = not_null{ resolved.peer };
if (ranges::contains(now, peer, &Userpic::peer)) {
continue;
}
const auto &data = ReactionEntityData(resolved.reaction);
const auto i = ranges::find(was, peer, &Userpic::peer);
if (i != end(was)) {
if (i != end(was) && i->view) {
now.push_back(std::move(*i));
now.back().customEntityData = data;
continue;
}
now.push_back(Userpic{
.peer = peer,
.reaction = resolved.reaction.emoji(), // #TODO reactions
.customEntityData = data,
});
auto &userpic = now.back();
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
@ -436,7 +436,7 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
}
now.push_back({
.name = peer->name(),
.reaction = userpic.reaction,
.customEntityData = userpic.customEntityData,
.userpicLarge = GenerateUserpic(userpic, large),
.userpicKey = userpic.uniqueKey,
.id = id,
@ -493,13 +493,12 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
&Data::MessageReaction::id);
return (i != end(list)) ? i->count : 0;
}();
// #TODO reactions
state->current.singleReaction = (!reaction.empty()
state->current.singleCustomEntityData = ReactionEntityData(
!reaction.empty()
? reaction
: (list.size() == 1)
? list.front().id
: ReactionId()).emoji();
: ReactionId());
}
std::move(
idsWithReactions

View file

@ -7,8 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_message_reaction_id.h"
#include "data/stickers/data_custom_emoji.h"
namespace Data {
QString ReactionEntityData(const ReactionId &id) {
if (id.empty()) {
return {};
} else if (const auto custom = id.custom()) {
return SerializeCustomEmojiId({
.id = custom,
});
}
return u"default:"_q + id.emoji();
}
ReactionId ReactionFromMTP(const MTPReaction &reaction) {
return reaction.match([](MTPDreactionEmpty) {
return ReactionId{ QString() };

View file

@ -39,6 +39,8 @@ inline bool operator==(const ReactionId &a, const ReactionId &b) {
return a.data == b.data;
}
[[nodiscard]] QString ReactionEntityData(const ReactionId &id);
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id);

View file

@ -72,7 +72,6 @@ constexpr auto kTopReactionsLimit = 10;
.centerIcon = document,
.active = true,
};
}
} // namespace
@ -99,16 +98,13 @@ PossibleItemReactionsRef LookupPossibleReactions(
return true; // #TODO reactions
}();
auto added = base::flat_set<ReactionId>();
const auto addOne = [&](const Reaction &reaction) {
if (added.emplace(reaction.id).second) {
result.recent.push_back(&reaction);
}
};
const auto add = [&](auto predicate) {
auto &&all = ranges::views::concat(top, recent, full);
auto &&all = ranges::views::concat(recent, top, full);
for (const auto &reaction : all) {
if (predicate(reaction)) {
addOne(reaction);
if (added.emplace(reaction.id).second) {
result.recent.push_back(&reaction);
}
}
}
};

View file

@ -14,20 +14,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h"
#include "data/data_message_reactions.h"
#include "data/stickers/data_stickers.h"
#include "lottie/lottie_common.h"
#include "lottie/lottie_emoji.h"
#include "ffmpeg/ffmpeg_emoji.h"
#include "chat_helpers/stickers_lottie.h"
#include "ui/text/text_block.h"
#include "ui/widgets/input_fields.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/ui_utility.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "data/stickers/data_stickers.h"
#include "ui/widgets/input_fields.h"
#include "ui/text/text_block.h"
namespace Data {
namespace {
@ -42,7 +41,6 @@ using SizeTag = CustomEmojiManager::SizeTag;
case SizeTag::Normal: return LottieSize::EmojiInteraction;
case SizeTag::Large: return LottieSize::EmojiInteractionReserved1;
case SizeTag::Isolated: return LottieSize::EmojiInteractionReserved2;
case SizeTag::ReactionFake: return LottieSize::EmojiInteractionReserved3;
}
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
}
@ -54,12 +52,16 @@ using SizeTag = CustomEmojiManager::SizeTag;
case SizeTag::Isolated:
return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
* style::DevicePixelRatio();
case SizeTag::ReactionFake:
return st::reactStripImage * style::DevicePixelRatio();
}
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
}
[[nodiscard]] int FrameSizeFromTag(SizeTag tag, int sizeOverride) {
return sizeOverride
? (sizeOverride * style::DevicePixelRatio())
: FrameSizeFromTag(tag);
}
} // namespace
class CustomEmojiLoader final
@ -69,8 +71,12 @@ public:
CustomEmojiLoader(
not_null<Session*> owner,
const CustomEmojiId id,
SizeTag tag);
CustomEmojiLoader(not_null<DocumentData*> document, SizeTag tag);
SizeTag tag,
int sizeOverride);
CustomEmojiLoader(
not_null<DocumentData*> document,
SizeTag tag,
int sizeOverride);
[[nodiscard]] bool resolving() const;
void resolved(not_null<DocumentData*> document);
@ -120,6 +126,7 @@ private:
const CustomEmojiId &id);
std::variant<Resolve, Lookup, Load> _state;
ushort _sizeOverride = 0;
SizeTag _tag = SizeTag::Normal;
};
@ -127,16 +134,24 @@ private:
CustomEmojiLoader::CustomEmojiLoader(
not_null<Session*> owner,
const CustomEmojiId id,
SizeTag tag)
SizeTag tag,
int sizeOverride)
: _state(InitialState(owner, id))
, _sizeOverride(sizeOverride)
, _tag(tag) {
Expects(sizeOverride >= 0
&& sizeOverride <= std::numeric_limits<ushort>::max());
}
CustomEmojiLoader::CustomEmojiLoader(
not_null<DocumentData*> document,
SizeTag tag)
SizeTag tag,
int sizeOverride)
: _state(Lookup{ document })
, _sizeOverride(sizeOverride)
, _tag(tag) {
Expects(sizeOverride >= 0
&& sizeOverride <= std::numeric_limits<ushort>::max());
}
bool CustomEmojiLoader::resolving() const {
@ -232,7 +247,7 @@ void CustomEmojiLoader::startCacheLookup(
lookup->process = std::make_unique<Process>(Process{
.loaded = std::move(loaded),
});
const auto size = FrameSizeFromTag(_tag);
const auto size = FrameSizeFromTag(_tag, _sizeOverride);
const auto weak = base::make_weak(&lookup->process->guard);
document->owner().cacheBigFile().get(key, [=](QByteArray value) {
auto cache = Ui::CustomEmoji::Cache::FromSerialized(value, size);
@ -251,8 +266,12 @@ void CustomEmojiLoader::lookupDone(
return;
}
const auto tag = _tag;
const auto sizeOverride = int(_sizeOverride);
auto loader = [=] {
return std::make_unique<CustomEmojiLoader>(document, tag);
return std::make_unique<CustomEmojiLoader>(
document,
tag,
sizeOverride);
};
auto done = std::move(lookup->process->loaded);
done(Ui::CustomEmoji::Cached(
@ -285,10 +304,14 @@ void CustomEmojiLoader::check() {
load->process->lifetime.destroy();
const auto tag = _tag;
const auto size = FrameSizeFromTag(_tag);
const auto sizeOverride = int(_sizeOverride);
const auto size = FrameSizeFromTag(_tag, _sizeOverride);
auto bytes = Lottie::ReadContent(data, filepath);
auto loader = [=] {
return std::make_unique<CustomEmojiLoader>(document, tag);
return std::make_unique<CustomEmojiLoader>(
document,
tag,
sizeOverride);
};
auto put = [=, key = cacheKey(document)](QByteArray value) {
document->owner().cacheBigFile().put(key, std::move(value));
@ -347,7 +370,7 @@ Ui::CustomEmoji::Preview CustomEmojiLoader::preview() {
|| !dimensions.width()) {
return {};
}
const auto scale = (FrameSizeFromTag(_tag) * 1.)
const auto scale = (FrameSizeFromTag(_tag, _sizeOverride) * 1.)
/ (style::DevicePixelRatio() * dimensions.width());
return { document->createMediaView()->thumbnailPath(), scale };
};
@ -371,6 +394,7 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
DocumentId documentId,
Fn<void()> update,
SizeTag tag,
int sizeOverride,
LoaderFactory factory) {
auto &instances = _instances[SizeIndex(tag)];
auto i = instances.find(documentId);
@ -385,10 +409,10 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
documentId,
std::make_unique<Ui::CustomEmoji::Instance>(Loading{
factory(),
prepareNonExactPreview(documentId, tag)
prepareNonExactPreview(documentId, tag, sizeOverride)
}, std::move(repaint))).first;
} else if (!i->second->hasImagePreview()) {
auto preview = prepareNonExactPreview(documentId, tag);
auto preview = prepareNonExactPreview(documentId, tag, sizeOverride);
if (preview.isImage()) {
i->second->updatePreview(std::move(preview));
}
@ -400,7 +424,8 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
DocumentId documentId,
SizeTag tag) const {
SizeTag tag,
int sizeOverride) const {
for (auto i = _instances.size(); i != 0;) {
if (SizeIndex(tag) == --i) {
continue;
@ -410,7 +435,7 @@ Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
if (j == end(other)) {
continue;
} else if (const auto nonExact = j->second->imagePreview()) {
const auto size = FrameSizeFromTag(tag);
const auto size = FrameSizeFromTag(tag, sizeOverride);
return {
nonExact.image().scaled(
size,
@ -427,26 +452,31 @@ Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
QStringView data,
Fn<void()> update,
SizeTag tag) {
SizeTag tag,
int sizeOverride) {
const auto parsed = ParseCustomEmojiData(data);
return parsed.id ? create(parsed.id, std::move(update), tag) : nullptr;
return parsed.id
? create(parsed.id, std::move(update), tag, sizeOverride)
: nullptr;
}
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
DocumentId documentId,
Fn<void()> update,
SizeTag tag) {
return create(documentId, std::move(update), tag, [&] {
return createLoader(documentId, tag);
SizeTag tag,
int sizeOverride) {
return create(documentId, std::move(update), tag, sizeOverride, [&] {
return createLoader(documentId, tag, sizeOverride);
});
}
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
not_null<DocumentData*> document,
Fn<void()> update,
SizeTag tag) {
return create(document->id, std::move(update), tag, [&] {
return createLoader(document, tag);
SizeTag tag,
int sizeOverride) {
return create(document->id, std::move(update), tag, sizeOverride, [&] {
return createLoader(document, tag, sizeOverride);
});
}
@ -485,17 +515,20 @@ void CustomEmojiManager::unregisterListener(not_null<Listener*> listener) {
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
not_null<DocumentData*> document,
SizeTag tag) {
return std::make_unique<CustomEmojiLoader>(document, tag);
SizeTag tag,
int sizeOverride) {
return std::make_unique<CustomEmojiLoader>(document, tag, sizeOverride);
}
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
DocumentId documentId,
SizeTag tag) {
SizeTag tag,
int sizeOverride) {
auto result = std::make_unique<CustomEmojiLoader>(
_owner,
CustomEmojiId{ .id = documentId },
tag);
tag,
sizeOverride);
if (result->resolving()) {
const auto i = SizeIndex(tag);
_loaders[i][documentId].push_back(base::make_weak(result.get()));
@ -667,9 +700,6 @@ Session &CustomEmojiManager::owner() const {
int FrameSizeFromTag(SizeTag tag) {
const auto emoji = EmojiSizeFromTag(tag);
if (tag == SizeTag::ReactionFake) {
return emoji;
}
const auto factor = style::DevicePixelRatio();
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
}
@ -705,4 +735,37 @@ void InsertCustomEmoji(
Ui::InputField::CustomEmojiLink(SerializeCustomEmojiId(document)));
}
Ui::Text::CustomEmojiFactory ReactedMenuFactory(
not_null<Main::Session*> session) {
return [owner = &session->data()](
QStringView data,
Fn<void()> repaint) -> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto prefix = u"default:"_q;
if (data.startsWith(prefix)) {
const auto &list = owner->reactions().list(
Data::Reactions::Type::All);
const auto emoji = data.mid(prefix.size()).toString();
const auto id = Data::ReactionId{ { emoji } };
const auto i = ranges::find(list, id, &Data::Reaction::id);
if (i != end(list)) {
const auto document = i->centerIcon
? not_null(i->centerIcon)
: i->selectAnimation;
const auto size = st::emojiSize * (i->centerIcon ? 2 : 1);
const auto tag = Data::CustomEmojiManager::SizeTag::Normal;
const auto skip = (Data::FrameSizeFromTag(tag) - size) / 2;
return std::make_unique<Ui::Text::FirstFrameEmoji>(
std::make_unique<Ui::Text::ShiftedEmoji>(
owner->customEmojiManager().create(
document,
std::move(repaint),
tag,
size),
QPoint(skip, skip)));
}
}
return owner->customEmojiManager().create(data, std::move(repaint));
};
}
} // namespace Data

View file

@ -29,11 +29,10 @@ struct CustomEmojiId {
class CustomEmojiManager final : public base::has_weak_ptr {
public:
enum class SizeTag {
enum class SizeTag : uchar {
Normal,
Large,
Isolated,
ReactionFake,
kCount,
};
@ -44,15 +43,18 @@ public:
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
QStringView data,
Fn<void()> update,
SizeTag tag = SizeTag::Normal);
SizeTag tag = SizeTag::Normal,
int sizeOverride = 0);
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
DocumentId documentId,
Fn<void()> update,
SizeTag tag = SizeTag::Normal);
SizeTag tag = SizeTag::Normal,
int sizeOverride = 0);
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
not_null<DocumentData*> document,
Fn<void()> update,
SizeTag tag = SizeTag::Normal);
SizeTag tag = SizeTag::Normal,
int sizeOverride = 0);
class Listener {
public:
@ -65,10 +67,12 @@ public:
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
not_null<DocumentData*> document,
SizeTag tag);
SizeTag tag,
int sizeOverride = 0);
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
DocumentId documentId,
SizeTag tag);
SizeTag tag,
int sizeOverride = 0);
[[nodiscard]] QString lookupSetName(uint64 setId);
@ -94,12 +98,14 @@ private:
[[nodiscard]] Ui::CustomEmoji::Preview prepareNonExactPreview(
DocumentId documentId,
SizeTag tag) const;
SizeTag tag,
int sizeOverride) const;
template <typename LoaderFactory>
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
DocumentId documentId,
Fn<void()> update,
SizeTag tag,
int sizeOverride,
LoaderFactory factory);
[[nodiscard]] static int SizeIndex(SizeTag tag);
@ -145,4 +151,7 @@ void InsertCustomEmoji(
not_null<Ui::InputField*> field,
not_null<DocumentData*> document);
[[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory(
not_null<Main::Session*> session);
} // namespace Data

View file

@ -2418,14 +2418,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
auto emojiPackIds = _dragStateItem
? HistoryView::CollectEmojiPacks(_dragStateItem)
: std::vector<StickerSetIdentifier>();
if (!emojiPackIds.empty()) {
if (_dragStateItem) {
HistoryView::AddEmojiPacksAction(
_menu,
this,
std::move(emojiPackIds),
_dragStateItem,
HistoryView::EmojiPacksSource::Message,
_controller);
}
if (hasWhoReactedItem) {

View file

@ -1004,14 +1004,11 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
AddCopyLinkAction(result, link);
AddMessageActions(result, request, list);
auto emojiPackIds = item
? CollectEmojiPacks(item)
: std::vector<StickerSetIdentifier>();
if (!emojiPackIds.empty()) {
if (item) {
AddEmojiPacksAction(
result,
list,
std::move(emojiPackIds),
item,
HistoryView::EmojiPacksSource::Message,
list->controller());
}
if (hasWhoReactedItem) {
@ -1162,6 +1159,7 @@ void AddWhoReactedAction(
menu->addAction(Ui::WhoReactedContextAction(
menu.get(),
Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds),
Data::ReactedMenuFactory(&controller->session()),
participantChosen,
showAllChosen));
}
@ -1185,12 +1183,15 @@ void ShowWhoReactedMenu(
id));
}
};
const auto reactions = &controller->session().data().reactions();
const auto owner = &controller->session().data();
const auto reactions = &owner->reactions();
const auto &list = reactions->list(
Data::Reactions::Type::Active);
const auto activeNonQuick = (id != reactions->favoriteId())
&& ranges::contains(list, id, &Data::Reaction::id);
&& (ranges::contains(list, id, &Data::Reaction::id)
|| (controller->session().premium() && id.custom()));
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
Data::ReactedMenuFactory(&controller->session()),
participantChosen,
showAllChosen);
Api::WhoReacted(
@ -1219,6 +1220,17 @@ void ShowWhoReactedMenu(
}
filler->populate(menu->get(), content);
if (const auto custom = id.custom()) {
if (const auto set = owner->document(custom)->sticker()) {
if (set->set.id) {
AddEmojiPacksAction(
menu->get(),
{ set->set },
EmojiPacksSource::Reaction,
controller);
}
}
}
if (creating) {
(*menu)->popup(position);
}
@ -1226,31 +1238,51 @@ void ShowWhoReactedMenu(
}
std::vector<StickerSetIdentifier> CollectEmojiPacks(
not_null<HistoryItem*> item) {
not_null<HistoryItem*> item,
EmojiPacksSource source) {
auto result = std::vector<StickerSetIdentifier>();
const auto owner = &item->history()->owner();
for (const auto &entity : item->originalText().entities) {
if (entity.type() == EntityType::CustomEmoji) {
const auto data = Data::ParseCustomEmojiData(entity.data());
if (const auto set = owner->document(data.id)->sticker()) {
if (set->set.id
&& !ranges::contains(
result,
set->set.id,
&StickerSetIdentifier::id)) {
result.push_back(set->set);
}
const auto push = [&](DocumentId id) {
if (const auto set = owner->document(id)->sticker()) {
if (set->set.id
&& !ranges::contains(
result,
set->set.id,
&StickerSetIdentifier::id)) {
result.push_back(set->set);
}
}
};
switch (source) {
case EmojiPacksSource::Message:
for (const auto &entity : item->originalText().entities) {
if (entity.type() == EntityType::CustomEmoji) {
const auto data = Data::ParseCustomEmojiData(entity.data());
push(data.id);
}
}
break;
case EmojiPacksSource::Reactions:
for (const auto &reaction : item->reactions()) {
if (const auto customId = reaction.id.custom()) {
push(customId);
}
}
break;
default: Unexpected("Source in CollectEmojiPacks.");
}
return result;
}
void AddEmojiPacksAction(
not_null<Ui::PopupMenu*> menu,
not_null<QWidget*> context,
std::vector<StickerSetIdentifier> packIds,
EmojiPacksSource source,
not_null<Window::SessionController*> controller) {
if (packIds.empty()) {
return;
}
class Item final : public Ui::Menu::ItemBase {
public:
Item(
@ -1333,20 +1365,48 @@ void AddEmojiPacksAction(
if (!menu->empty()) {
menu->addSeparator();
}
auto text = [&] {
switch (source) {
case EmojiPacksSource::Message:
return name.text.isEmpty()
? tr::lng_context_animated_emoji_many(
tr::now,
lt_count,
count,
Ui::Text::RichLangValue)
: tr::lng_context_animated_emoji(
tr::now,
lt_name,
TextWithEntities{ name },
Ui::Text::RichLangValue);
case EmojiPacksSource::Reaction:
if (!name.text.isEmpty()) {
return tr::lng_context_animated_reaction(
tr::now,
lt_name,
TextWithEntities{ name },
Ui::Text::RichLangValue);
}
[[fallthrough]];
case EmojiPacksSource::Reactions:
return name.text.isEmpty()
? tr::lng_context_animated_reactions_many(
tr::now,
lt_count,
count,
Ui::Text::RichLangValue)
: tr::lng_context_animated_reactions(
tr::now,
lt_name,
TextWithEntities{ name },
Ui::Text::RichLangValue);
}
Unexpected("Source in AddEmojiPacksAction.");
}();
auto button = base::make_unique_q<Item>(
menu->menu(),
menu->st().menu,
(name.text.isEmpty()
? tr::lng_context_animated_emoji_many(
tr::now,
lt_count,
count,
Ui::Text::RichLangValue)
: tr::lng_context_animated_emoji(
tr::now,
lt_name,
TextWithEntities{ name },
Ui::Text::RichLangValue)));
std::move(text));
const auto weak = base::make_weak(controller.get());
button->setClickedCallback([=] {
const auto strong = weak.get();
@ -1368,4 +1428,16 @@ void AddEmojiPacksAction(
menu->addAction(std::move(button));
}
void AddEmojiPacksAction(
not_null<Ui::PopupMenu*> menu,
not_null<HistoryItem*> item,
EmojiPacksSource source,
not_null<Window::SessionController*> controller) {
AddEmojiPacksAction(
menu,
CollectEmojiPacks(item, source),
source,
controller);
}
} // namespace HistoryView

View file

@ -83,12 +83,23 @@ void ShowWhoReactedMenu(
not_null<Window::SessionController*> controller,
rpl::lifetime &lifetime);
enum class EmojiPacksSource {
Message,
Reaction,
Reactions,
};
[[nodiscard]] std::vector<StickerSetIdentifier> CollectEmojiPacks(
not_null<HistoryItem*> item);
not_null<HistoryItem*> item,
EmojiPacksSource source);
void AddEmojiPacksAction(
not_null<Ui::PopupMenu*> menu,
not_null<QWidget*> context,
std::vector<StickerSetIdentifier> packIds,
EmojiPacksSource source,
not_null<Window::SessionController*> controller);
void AddEmojiPacksAction(
not_null<Ui::PopupMenu*> menu,
not_null<HistoryItem*> item,
EmojiPacksSource source,
not_null<Window::SessionController*> controller);
} // namespace HistoryView

View file

@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_session.h"
#include "lang/lang_tag.h"
#include "ui/text/text_block.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/chat/chat_style.h"
#include "styles/style_chat.h"
@ -95,6 +95,7 @@ void InlineList::unloadCustomEmoji() {
custom->unload();
}
}
_customCache = QImage();
}
void InlineList::layout() {
@ -406,19 +407,13 @@ void InlineList::paint(
inner.topLeft() + QPoint(skip, skip),
QSize(st::reactionInlineImage, st::reactionInlineImage));
if (!skipImage) {
if (button.custom) {
if (!_customSkip) {
using namespace Ui::Text;
const auto size = st::emojiSize;
_customSkip = (size - AdjustCustomEmojiSize(size)) / 2;
}
button.custom->paint(p, {
.preview = textFg.color(),
.now = context.now,
.position = (inner.topLeft()
+ QPoint(_customSkip, _customSkip)),
.paused = p.inactive(),
});
if (const auto custom = button.custom.get()) {
paintCustomFrame(
p,
custom,
inner.topLeft(),
context.now,
textFg.color());
} else if (!button.image.isNull()) {
p.drawImage(image.topLeft(), button.image);
}
@ -535,6 +530,42 @@ void InlineList::resolveUserpicsImage(const Button &button) const {
kMaxRecentUserpics);
}
void InlineList::paintCustomFrame(
Painter &p,
not_null<Ui::Text::CustomEmoji*> emoji,
QPoint innerTopLeft,
crl::time now,
const QColor &preview) const {
if (_customCache.isNull()) {
using namespace Ui::Text;
const auto size = st::emojiSize;
const auto factor = style::DevicePixelRatio();
const auto adjusted = AdjustCustomEmojiSize(size);
_customCache = QImage(
QSize(adjusted, adjusted) * factor,
QImage::Format_ARGB32_Premultiplied);
_customCache.setDevicePixelRatio(factor);
_customSkip = (size - adjusted) / 2;
}
_customCache.fill(Qt::transparent);
auto q = QPainter(&_customCache);
emoji->paint(q, {
.preview = preview,
.now = now,
.paused = p.inactive(),
});
q.end();
_customCache = Images::Round(
std::move(_customCache),
(Images::Option::RoundLarge
| Images::Option::RoundSkipTopRight
| Images::Option::RoundSkipBottomRight));
p.drawImage(
innerTopLeft + QPoint(_customSkip, _customSkip),
_customCache);
}
auto InlineList::takeAnimations()
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>> {
auto result = base::flat_map<

View file

@ -104,6 +104,12 @@ private:
const std::vector<not_null<PeerData*>> &peers);
[[nodiscard]] Button prepareButtonWithId(const ReactionId &id);
void resolveUserpicsImage(const Button &button) const;
void paintCustomFrame(
Painter &p,
not_null<Ui::Text::CustomEmoji*> emoji,
QPoint innerTopLeft,
crl::time now,
const QColor &preview) const;
QSize countOptimalSize() override;
@ -113,6 +119,7 @@ private:
Data _data;
std::vector<Button> _buttons;
QSize _skipBlock;
mutable QImage _customCache;
mutable int _customSkip = 0;
bool _hasCustomEmoji = false;

View file

@ -714,7 +714,8 @@ void Manager::paintButton(
*q,
size,
expandRatio,
radiusMin + expandRatio * (radiusMax - radiusMin),
radiusMin,
radiusMax,
scale);
layeredPainter.reset();
p.drawImage(

View file

@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "api/api_who_reacted.h"
#include "ui/controls/who_reacted_context_action.h"
#include "ui/text/text_custom_emoji.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_message_reaction_id.h"
#include "main/main_session.h"
#include "data/data_session.h"
@ -31,7 +33,13 @@ using ::Data::ReactionId;
class Row final : public PeerListRow {
public:
Row(not_null<PeerData*> peer, const ReactionId &id);
Row(
uint64 id,
not_null<PeerData*> peer,
const Ui::Text::CustomEmojiFactory &factory,
QStringView reactionEntityData,
Fn<void(Row*)> repaint,
Fn<bool()> paused);
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
@ -45,7 +53,8 @@ public:
bool actionSelected) override;
private:
EmojiPtr _emoji = nullptr;
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
Fn<bool()> _paused;
};
@ -74,14 +83,22 @@ private:
ReactionId reaction) const;
void showReaction(const ReactionId &reaction);
[[nodiscard]] uint64 id(
not_null<PeerData*> peer,
const ReactionId &reaction) const;
const not_null<Window::SessionController*> _window;
const not_null<HistoryItem*> _item;
const Ui::Text::CustomEmojiFactory _factory;
MTP::Sender _api;
ReactionId _shownReaction;
std::shared_ptr<Api::WhoReadList> _whoReadIds;
std::vector<not_null<PeerData*>> _whoRead;
mutable base::flat_map<std::pair<PeerId, ReactionId>, uint64> _idsMap;
mutable uint64 _idsCounter = 0;
std::vector<AllEntry> _all;
QString _allOffset;
@ -92,18 +109,27 @@ private:
};
Row::Row(not_null<PeerData*> peer, const ReactionId &id)
: PeerListRow(peer)
, _emoji(Ui::Emoji::Find(id.emoji())) { // #TODO reaction
Row::Row(
uint64 id,
not_null<PeerData*> peer,
const Ui::Text::CustomEmojiFactory &factory,
QStringView reactionEntityData,
Fn<void(Row*)> repaint,
Fn<bool()> paused)
: PeerListRow(peer, id)
, _custom(reactionEntityData.isEmpty()
? nullptr
: factory(reactionEntityData, [=] { repaint(this); }))
, _paused(std::move(paused)) {
}
QSize Row::rightActionSize() const {
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
return _emoji ? QSize(size, size) : QSize();
return _custom ? QSize(size, size) : QSize();
}
QMargins Row::rightActionMargins() const {
if (!_emoji) {
if (!_custom) {
return QMargins();
}
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
@ -125,13 +151,20 @@ void Row::rightActionPaint(
int outerWidth,
bool selected,
bool actionSelected) {
if (!_emoji) {
if (!_custom) {
return;
}
// #TODO reactions
Ui::Emoji::Draw(p, _emoji, Ui::Emoji::GetSizeNormal(), x, y);
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
const auto skip = (size - Ui::Text::AdjustCustomEmojiSize(size)) / 2;
_custom->paint(p, {
.preview = st::windowBgRipple->c,
.now = crl::now(),
.position = { x + skip, y + skip },
.paused = _paused(),
});
}
Controller::Controller(
not_null<Window::SessionController*> window,
not_null<HistoryItem*> item,
@ -140,6 +173,7 @@ Controller::Controller(
std::shared_ptr<Api::WhoReadList> whoReadIds)
: _window(window)
, _item(item)
, _factory(Data::ReactedMenuFactory(&window->session()))
, _api(&window->session().mtp())
, _shownReaction(selected)
, _whoReadIds(whoReadIds) {
@ -203,6 +237,16 @@ void Controller::showReaction(const ReactionId &reaction) {
delegate()->peerListRefreshRows();
}
uint64 Controller::id(
not_null<PeerData*> peer,
const ReactionId &reaction) const {
const auto key = std::pair{ peer->id, reaction };
const auto i = _idsMap.find(key);
return (i != end(_idsMap)
? i
: _idsMap.emplace(key, ++_idsCounter).first)->second;
}
void Controller::fillWhoRead() {
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
auto &owner = _window->session().data();
@ -293,7 +337,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
}
bool Controller::appendRow(not_null<PeerData*> peer, ReactionId reaction) {
if (delegate()->peerListFindRow(peer->id.value)) {
if (delegate()->peerListFindRow(id(peer, reaction))) {
return false;
}
delegate()->peerListAppendRow(createRow(peer, reaction));
@ -303,7 +347,14 @@ bool Controller::appendRow(not_null<PeerData*> peer, ReactionId reaction) {
std::unique_ptr<PeerListRow> Controller::createRow(
not_null<PeerData*> peer,
ReactionId reaction) const {
return std::make_unique<Row>(peer, reaction);
return std::make_unique<Row>(
id(peer, reaction),
peer,
_factory,
Data::ReactionEntityData(reaction),
[=](Row *row) { delegate()->peerListUpdateRow(row); },
[=] { return _window->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer); });
}
} // namespace
@ -338,6 +389,9 @@ object_ptr<Ui::BoxContent> FullListBox(
}
const auto tabs = CreateTabs(
box,
Data::ReactedMenuFactory(&item->history()->session()),
[=] { return window->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer); },
map,
selected,
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/text/text_custom_emoji.h"
#include "history/history_item.h"
#include "data/data_document.h"
#include "data/data_session.h"
@ -33,25 +34,6 @@ constexpr auto kScaleDuration = crl::time(120);
constexpr auto kFullDuration = kExpandDuration + kScaleDuration;
constexpr auto kExpandDelay = crl::time(40);
class ShiftedEmoji final : public Ui::Text::CustomEmoji {
public:
ShiftedEmoji(
not_null<Data::CustomEmojiManager*> manager,
DocumentId id,
Fn<void()> repaint,
QPoint shift);
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
bool ready() override;
private:
const std::unique_ptr<Ui::Text::CustomEmoji> _real;
const QPoint _shift;
};
class StripEmoji final : public Ui::Text::CustomEmoji {
public:
StripEmoji(
@ -74,36 +56,6 @@ private:
};
ShiftedEmoji::ShiftedEmoji(
not_null<Data::CustomEmojiManager*> manager,
DocumentId id,
Fn<void()> repaint,
QPoint shift)
: _real(manager->create(
id,
std::move(repaint),
Data::CustomEmojiManager::SizeTag::ReactionFake))
, _shift(shift) {
}
QString ShiftedEmoji::entityData() {
return _real->entityData();
}
void ShiftedEmoji::paint(QPainter &p, const Context &context) {
auto copy = context;
copy.position += _shift;
_real->paint(p, copy);
}
void ShiftedEmoji::unload() {
_real->unload();
}
bool ShiftedEmoji::ready() {
return _real->ready();
}
StripEmoji::StripEmoji(
std::unique_ptr<Ui::Text::CustomEmoji> wrapped,
not_null<Strip*> strip,
@ -310,7 +262,13 @@ void Selector::paintAppearing(QPainter &p) {
_cachedRound.setShadowColor(st::shadowFg->c);
q.translate(QPoint(0, _collapsedTopSkip) - _inner.topLeft());
const auto radius = st::reactStripHeight / 2;
_cachedRound.overlayExpandedBorder(q, size, _appearProgress, radius, 1.);
_cachedRound.overlayExpandedBorder(
q,
size,
_appearProgress,
radius,
radius,
1.);
q.setCompositionMode(QPainter::CompositionMode_Source);
q.fillRect(
QRect{ 0, size.height(), width(), height() - size.height() },
@ -622,17 +580,14 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
) - _stripPaintOneShift;
auto factory = [=](DocumentId id, Fn<void()> repaint)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
const auto sizeOverride = st::reactStripImage;
const auto isDefaultReaction = defaultReactionIds.contains(id);
auto result = isDefaultReaction
? std::make_unique<ShiftedEmoji>(
manager,
id,
std::move(repaint),
? std::make_unique<Ui::Text::ShiftedEmoji>(
manager->create(id, std::move(repaint), tag, sizeOverride),
_defaultReactionShift)
: manager->create(
id,
std::move(repaint),
Data::CustomEmojiManager::SizeTag::Large);
: manager->create(id, std::move(repaint), tag);
const auto i = _defaultReactionInStripMap.find(id);
if (i != end(_defaultReactionInStripMap)) {
return std::make_unique<StripEmoji>(
@ -862,6 +817,16 @@ AttachSelectorResult AttachSelectorToMenu(
state.toggling);
}, selector->lifetime());
const auto weak = base::make_weak(controller.get());
controller->enableGifPauseReason(
Window::GifPauseReason::MediaPreview);
QObject::connect(menu.get(), &QObject::destroyed, [weak] {
if (const auto strong = weak.get()) {
strong->disableGifPauseReason(
Window::GifPauseReason::MediaPreview);
}
});
return AttachSelectorResult::Attached;
}

View file

@ -22,13 +22,16 @@ using ::Data::ReactionId;
not_null<Ui::AbstractButton*> CreateTab(
not_null<QWidget*> parent,
const style::MultiSelect &st,
const Ui::Text::CustomEmojiFactory &factory,
Fn<bool()> paused,
const ReactionId &reaction,
Ui::WhoReadType whoReadType,
int count,
rpl::producer<bool> selected) {
struct State {
bool selected = false;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
QImage cache;
bool selected = false;
};
const auto stm = &st.item;
const auto text = QString("%L1").arg(count);
@ -49,10 +52,19 @@ not_null<Ui::AbstractButton*> CreateTab(
result->update();
}, result->lifetime());
state->custom = reaction.empty()
? nullptr
: factory(
Data::ReactionEntityData(reaction),
[=] { result->update(); });
result->paintRequest(
) | rpl::start_with_next([=] {
const auto factor = style::DevicePixelRatio();
const auto height = stm->height;
const auto skip = st::reactionsTabIconSkip;
const auto icon = QRect(skip, 0, height, height);
if (state->cache.isNull()) {
const auto factor = style::DevicePixelRatio();
state->cache = QImage(
result->size() * factor,
QImage::Format_ARGB32_Premultiplied);
@ -70,12 +82,7 @@ not_null<Ui::AbstractButton*> CreateTab(
}
const auto skip = st::reactionsTabIconSkip;
const auto icon = QRect(skip, 0, height, height);
// #TODO reactions
if (const auto emoji = Ui::Emoji::Find(reaction.emoji())) {
const auto size = Ui::Emoji::GetSizeNormal();
const auto shift = (height - (size / factor)) / 2;
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
} else {
if (!state->custom) {
using Type = Ui::WhoReadType;
(reaction.emoji().isEmpty()
? (state->selected
@ -96,7 +103,25 @@ not_null<Ui::AbstractButton*> CreateTab(
p.setFont(font);
p.drawText(textLeft, stm->padding.top() + font->ascent, text);
}
QPainter(result).drawImage(0, 0, state->cache);
auto p = QPainter(result);
p.drawImage(0, 0, state->cache);
if (const auto custom = state->custom.get()) {
using namespace Ui::Text;
const auto size = Ui::Emoji::GetSizeNormal();
const auto shift = (height - (size / factor)) / 2;
const auto skip = (size - AdjustCustomEmojiSize(size)) / 2;
custom->paint(p, {
.preview = (state->selected
? QColor(
st::activeButtonFg->c.red(),
st::activeButtonFg->c.green(),
st::activeButtonFg->c.blue(),
st::activeButtonFg->c.alpha() / 3)
: st::windowBgRipple->c),
.now = crl::now(),
.position = { icon.x() + shift + skip, shift + skip },
});
}
}, result->lifetime());
return result;
}
@ -105,8 +130,10 @@ not_null<Ui::AbstractButton*> CreateTab(
not_null<Tabs*> CreateTabs(
not_null<QWidget*> parent,
Ui::Text::CustomEmojiFactory factory,
Fn<bool()> paused,
const std::vector<Data::MessageReaction> &items,
const ReactionId &selected,
const Data::ReactionId &selected,
Ui::WhoReadType whoReadType) {
struct State {
rpl::variable<ReactionId> selected;
@ -123,6 +150,8 @@ not_null<Tabs*> CreateTabs(
const auto tab = CreateTab(
tabs,
*st,
factory,
paused,
reaction,
whoReadType,
count,

View file

@ -27,6 +27,8 @@ struct Tabs {
not_null<Tabs*> CreateTabs(
not_null<QWidget*> parent,
Ui::Text::CustomEmojiFactory factory,
Fn<bool()> paused,
const std::vector<Data::MessageReaction> &items,
const Data::ReactionId &selected,
Ui::WhoReadType whoReadType);

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/effects/ripple_animation.h"
#include "ui/chat/group_call_userpics.h"
#include "ui/text/text_custom_emoji.h"
#include "lang/lang_keys.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
@ -67,9 +68,11 @@ StringWithReacted ReplaceTag<StringWithReacted>::Call(
namespace Ui {
namespace {
using Text::CustomEmojiFactory;
struct EntryData {
QString text;
QString reaction;
QString customEntityData;
QImage userpic;
Fn<void()> callback;
};
@ -79,6 +82,7 @@ public:
Action(
not_null<PopupMenu*> parentMenu,
rpl::producer<WhoReadContent> content,
CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen);
@ -108,10 +112,12 @@ private:
const Fn<void()> _showAllChosen;
const std::unique_ptr<GroupCallUserpics> _userpics;
const style::Menu &_st;
const CustomEmojiFactory _customEmojiFactory;
WhoReactedListMenu _submenu;
Text::String _text;
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
int _textWidth = 0;
const int _height = 0;
int _userpicsWidth = 0;
@ -143,6 +149,7 @@ TextParseOptions MenuTextOptions = {
Action::Action(
not_null<PopupMenu*> parentMenu,
rpl::producer<WhoReadContent> content,
Text::CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen)
: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
@ -155,7 +162,8 @@ Action::Action(
rpl::never<bool>(),
[=] { update(); }))
, _st(parentMenu->menu()->st())
, _submenu(_participantChosen, _showAllChosen)
, _customEmojiFactory(std::move(factory))
, _submenu(_customEmojiFactory, _participantChosen, _showAllChosen)
, _height(st::defaultWhoRead.itemPadding.top()
+ _st.itemStyle.font->height
+ st::defaultWhoRead.itemPadding.bottom()) {
@ -300,18 +308,28 @@ void Action::paint(Painter &p) {
if (selected && _st.itemBgOver->c.alpha() < 255) {
p.fillRect(0, 0, width(), _height, _st.itemBg);
}
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
const auto &bg = selected ? _st.itemBgOver : _st.itemBg;
p.fillRect(0, 0, width(), _height, bg);
if (enabled) {
paintRipple(p, 0, 0);
}
if (const auto emoji = Emoji::Find(_content.singleReaction)) {
// #TODO reactions
if (!_custom && !_content.singleCustomEntityData.isEmpty()) {
_custom = _customEmojiFactory(
_content.singleCustomEntityData,
[=] { update(); });
}
if (_custom) {
const auto ratio = style::DevicePixelRatio();
const auto size = Emoji::GetSizeNormal();
const auto size = Emoji::GetSizeNormal() / ratio;
const auto adjusted = Text::AdjustCustomEmojiSize(size);
const auto x = st::defaultWhoRead.iconPosition.x()
+ (st::whoReadChecks.width() - (size / ratio)) / 2;
const auto y = (_height - (size / ratio)) / 2;
Emoji::Draw(p, emoji, size, x, y);
+ (st::whoReadChecks.width() - adjusted) / 2;
const auto y = (_height - adjusted) / 2;
_custom->paint(p, {
.preview = _st.ripple.color->c,
.now = crl::now(),
.position = { x, y },
});
} else {
const auto &icon = (_content.fullReactionsCount)
? (!enabled
@ -357,7 +375,7 @@ void Action::refreshText() {
const auto onlySeenCount = ranges::count(
_content.participants,
QString(),
&WhoReadParticipant::reaction);
&WhoReadParticipant::customEntityData);
const auto count = std::max(_content.fullReactionsCount, usersCount);
_text.setMarkedText(
_st.itemStyle,
@ -448,6 +466,7 @@ class WhoReactedListMenu::EntryAction final : public Menu::ItemBase {
public:
EntryAction(
not_null<RpWidget*> parent,
CustomEmojiFactory factory,
const style::Menu &st,
EntryData &&data);
@ -462,22 +481,26 @@ private:
void paint(Painter &&p);
const not_null<QAction*> _dummyAction;
const CustomEmojiFactory _customEmojiFactory;
const style::Menu &_st;
const int _height = 0;
Text::String _text;
EmojiPtr _emoji = nullptr;
int _textWidth = 0;
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
QImage _userpic;
int _textWidth = 0;
int _customSize = 0;
};
WhoReactedListMenu::EntryAction::EntryAction(
not_null<RpWidget*> parent,
CustomEmojiFactory customEmojiFactory,
const style::Menu &st,
EntryData &&data)
: ItemBase(parent, st)
, _dummyAction(CreateChild<QAction>(parent.get()))
, _customEmojiFactory(std::move(customEmojiFactory))
, _st(st)
, _height(st::defaultWhoRead.photoSkip * 2 + st::defaultWhoRead.photoSize) {
setAcceptBoth(true);
@ -509,14 +532,14 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
setClickedCallback(std::move(data.callback));
_userpic = std::move(data.userpic);
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
_emoji = Emoji::Find(data.reaction);
_custom = _customEmojiFactory(data.customEntityData, [=] { update(); });
const auto ratio = style::DevicePixelRatio();
const auto size = Emoji::GetSizeNormal() / ratio;
_customSize = Text::AdjustCustomEmojiSize(size);
const auto textWidth = _text.maxWidth();
const auto &padding = _st.itemPadding;
const auto rightSkip = padding.right()
+ (_emoji
? ((Emoji::GetSizeNormal() / style::DevicePixelRatio())
+ padding.right())
: 0);
+ (_custom ? (size + padding.right()) : 0);
const auto goodWidth = st::defaultWhoRead.nameLeft
+ textWidth
+ rightSkip;
@ -541,7 +564,7 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
const auto photoTop = (height() - photoSize) / 2;
if (!_userpic.isNull()) {
p.drawImage(photoLeft, photoTop, _userpic);
} else if (!_emoji) {
} else if (!_custom) {
st::menuIconReactions.paintInCenter(
p,
QRect(photoLeft, photoTop, photoSize, photoSize));
@ -559,16 +582,17 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
_textWidth,
width());
if (_emoji) {
// #TODO reactions
const auto size = Emoji::GetSizeNormal();
if (_custom) {
const auto ratio = style::DevicePixelRatio();
Emoji::Draw(
p,
_emoji,
size,
width() - _st.itemPadding.right() - (size / ratio),
(height() - (size / ratio)) / 2);
const auto size = Emoji::GetSizeNormal() / ratio;
const auto skip = (size - _customSize) / 2;
_custom->paint(p, {
.preview = _st.ripple.color->c,
.now = crl::now(),
.position = QPoint(
width() - _st.itemPadding.right() - (size / ratio) + skip,
(height() - _customSize) / 2),
});
}
}
@ -585,19 +609,23 @@ bool operator!=(const WhoReadParticipant &a, const WhoReadParticipant &b) {
base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
not_null<PopupMenu*> menu,
rpl::producer<WhoReadContent> content,
CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen) {
return base::make_unique_q<Action>(
menu,
std::move(content),
std::move(factory),
std::move(participantChosen),
std::move(showAllChosen));
}
WhoReactedListMenu::WhoReactedListMenu(
CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen)
: _participantChosen(std::move(participantChosen))
: _customEmojiFactory(std::move(factory))
, _participantChosen(std::move(participantChosen))
, _showAllChosen(std::move(showAllChosen)) {
}
@ -611,7 +639,7 @@ void WhoReactedListMenu::populate(
Fn<void()> refillTopActions) {
const auto reactions = ranges::count_if(
content.participants,
[](const auto &p) { return !p.reaction.isEmpty(); });
[](const auto &p) { return !p.customEntityData.isEmpty(); });
const auto addShowAll = (content.fullReactionsCount > reactions);
const auto actionsCount = int(content.participants.size())
+ (addShowAll ? 1 : 0);
@ -629,6 +657,7 @@ void WhoReactedListMenu::populate(
} else {
auto item = base::make_unique_q<EntryAction>(
menu->menu(),
_customEmojiFactory,
menu->menu()->st(),
std::move(data));
_actions.push_back(item.get());
@ -642,7 +671,7 @@ void WhoReactedListMenu::populate(
};
append({
.text = participant.name,
.reaction = participant.reaction,
.customEntityData = participant.customEntityData,
.userpic = participant.userpicLarge,
.callback = chosen,
});

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/unique_qptr.h"
#include "ui/text/text_block.h"
namespace Ui {
namespace Menu {
@ -18,7 +19,7 @@ class PopupMenu;
struct WhoReadParticipant {
QString name;
QString reaction;
QString customEntityData;
QImage userpicSmall;
QImage userpicLarge;
std::pair<uint64, uint64> userpicKey = {};
@ -40,7 +41,7 @@ enum class WhoReadType {
struct WhoReadContent {
std::vector<WhoReadParticipant> participants;
WhoReadType type = WhoReadType::Seen;
QString singleReaction;
QString singleCustomEntityData;
int fullReactionsCount = 0;
int fullReadCount = 0;
bool unknown = false;
@ -49,12 +50,14 @@ struct WhoReadContent {
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
not_null<PopupMenu*> menu,
rpl::producer<WhoReadContent> content,
Text::CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen);
class WhoReactedListMenu final {
public:
WhoReactedListMenu(
Text::CustomEmojiFactory factory,
Fn<void(uint64)> participantChosen,
Fn<void()> showAllChosen);
@ -67,6 +70,7 @@ public:
private:
class EntryAction;
const Text::CustomEmojiFactory _customEmojiFactory;
const Fn<void(uint64)> _participantChosen;
const Fn<void()> _showAllChosen;

View file

@ -134,6 +134,7 @@ std::optional<Cache> Cache::FromSerialized(
const QByteArray &serialized,
int requestedSize) {
Expects(requestedSize > 0 && requestedSize <= kMaxSize);
if (serialized.size() <= sizeof(CacheHeader)) {
return {};
}
@ -341,27 +342,30 @@ PaintFrameResult Cache::paintCurrentFrame(
if (!_frames) {
return {};
}
const auto now = context.paused ? 0 : context.now;
const auto finishes = now ? currentFrameFinishes() : 0;
if (finishes && now >= finishes) {
++_frame;
if (_finished && _frame == _frames) {
_frame = 0;
const auto first = context.firstFrameOnly;
if (!first) {
const auto now = context.paused ? 0 : context.now;
const auto finishes = now ? currentFrameFinishes() : 0;
if (finishes && now >= finishes) {
++_frame;
if (_finished && _frame == _frames) {
_frame = 0;
}
_shown = now;
} else if (!_shown) {
_shown = now;
}
_shown = now;
} else if (!_shown) {
_shown = now;
}
const auto info = frame(std::min(_frame, _frames - 1));
const auto index = first ? 0 : std::min(_frame, _frames - 1);
const auto info = frame(index);
const auto size = _size / style::DevicePixelRatio();
const auto rect = QRect(context.position, QSize(size, size));
PaintScaledImage(p, rect, info, context);
const auto next = currentFrameFinishes();
const auto duration = next ? (next - _shown) : 0;
const auto next = first ? 0 : currentFrameFinishes();
return {
.painted = true,
.next = currentFrameFinishes(),
.duration = duration,
.next = next,
.duration = next ? (next - _shown) : 0,
};
}

View file

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/text/text_block.h"
#include "ui/text/text_custom_emoji.h"
#include "base/weak_ptr.h"
#include "base/bytes.h"
#include "base/timer.h"

@ -1 +1 @@
Subproject commit fc2c55367099ca7bdeacd0d52ff6007f00a6ba72
Subproject commit f876d15eedbce39a445b020b92f45d137952ed2a