mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support custom emoji reactions in WhoReacted.
This commit is contained in:
parent
ba83836922
commit
668a3308be
22 changed files with 538 additions and 246 deletions
|
@ -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" = "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#one" = "This message contains emoji from **{count} pack**.";
|
||||||
"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**.";
|
"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_section" = "Downloads";
|
||||||
"lng_downloads_view_in_chat" = "View in chat";
|
"lng_downloads_view_in_chat" = "View in chat";
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
|
@ -112,7 +113,7 @@ struct Context {
|
||||||
|
|
||||||
struct Userpic {
|
struct Userpic {
|
||||||
not_null<PeerData*> peer;
|
not_null<PeerData*> peer;
|
||||||
QString reaction;
|
QString customEntityData;
|
||||||
mutable std::shared_ptr<Data::CloudImageView> view;
|
mutable std::shared_ptr<Data::CloudImageView> view;
|
||||||
mutable InMemoryKey uniqueKey;
|
mutable InMemoryKey uniqueKey;
|
||||||
};
|
};
|
||||||
|
@ -377,17 +378,16 @@ bool UpdateUserpics(
|
||||||
auto now = std::vector<Userpic>();
|
auto now = std::vector<Userpic>();
|
||||||
for (const auto &resolved : peers) {
|
for (const auto &resolved : peers) {
|
||||||
const auto peer = not_null{ resolved.peer };
|
const auto peer = not_null{ resolved.peer };
|
||||||
if (ranges::contains(now, peer, &Userpic::peer)) {
|
const auto &data = ReactionEntityData(resolved.reaction);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto i = ranges::find(was, peer, &Userpic::peer);
|
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.push_back(std::move(*i));
|
||||||
|
now.back().customEntityData = data;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
now.push_back(Userpic{
|
now.push_back(Userpic{
|
||||||
.peer = peer,
|
.peer = peer,
|
||||||
.reaction = resolved.reaction.emoji(), // #TODO reactions
|
.customEntityData = data,
|
||||||
});
|
});
|
||||||
auto &userpic = now.back();
|
auto &userpic = now.back();
|
||||||
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
|
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
|
||||||
|
@ -436,7 +436,7 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||||
}
|
}
|
||||||
now.push_back({
|
now.push_back({
|
||||||
.name = peer->name(),
|
.name = peer->name(),
|
||||||
.reaction = userpic.reaction,
|
.customEntityData = userpic.customEntityData,
|
||||||
.userpicLarge = GenerateUserpic(userpic, large),
|
.userpicLarge = GenerateUserpic(userpic, large),
|
||||||
.userpicKey = userpic.uniqueKey,
|
.userpicKey = userpic.uniqueKey,
|
||||||
.id = id,
|
.id = id,
|
||||||
|
@ -493,13 +493,12 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
&Data::MessageReaction::id);
|
&Data::MessageReaction::id);
|
||||||
return (i != end(list)) ? i->count : 0;
|
return (i != end(list)) ? i->count : 0;
|
||||||
}();
|
}();
|
||||||
|
state->current.singleCustomEntityData = ReactionEntityData(
|
||||||
// #TODO reactions
|
!reaction.empty()
|
||||||
state->current.singleReaction = (!reaction.empty()
|
|
||||||
? reaction
|
? reaction
|
||||||
: (list.size() == 1)
|
: (list.size() == 1)
|
||||||
? list.front().id
|
? list.front().id
|
||||||
: ReactionId()).emoji();
|
: ReactionId());
|
||||||
}
|
}
|
||||||
std::move(
|
std::move(
|
||||||
idsWithReactions
|
idsWithReactions
|
||||||
|
|
|
@ -7,8 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_message_reaction_id.h"
|
#include "data/data_message_reaction_id.h"
|
||||||
|
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
|
||||||
namespace Data {
|
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) {
|
ReactionId ReactionFromMTP(const MTPReaction &reaction) {
|
||||||
return reaction.match([](MTPDreactionEmpty) {
|
return reaction.match([](MTPDreactionEmpty) {
|
||||||
return ReactionId{ QString() };
|
return ReactionId{ QString() };
|
||||||
|
|
|
@ -39,6 +39,8 @@ inline bool operator==(const ReactionId &a, const ReactionId &b) {
|
||||||
return a.data == b.data;
|
return a.data == b.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString ReactionEntityData(const ReactionId &id);
|
||||||
|
|
||||||
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
|
[[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction);
|
||||||
[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id);
|
[[nodiscard]] MTPReaction ReactionToMTP(ReactionId id);
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,6 @@ constexpr auto kTopReactionsLimit = 10;
|
||||||
.centerIcon = document,
|
.centerIcon = document,
|
||||||
.active = true,
|
.active = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -99,16 +98,13 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
||||||
return true; // #TODO reactions
|
return true; // #TODO reactions
|
||||||
}();
|
}();
|
||||||
auto added = base::flat_set<ReactionId>();
|
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) {
|
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) {
|
for (const auto &reaction : all) {
|
||||||
if (predicate(reaction)) {
|
if (predicate(reaction)) {
|
||||||
addOne(reaction);
|
if (added.emplace(reaction.id).second) {
|
||||||
|
result.recent.push_back(&reaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,20 +14,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_peer.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_common.h"
|
||||||
#include "lottie/lottie_emoji.h"
|
#include "lottie/lottie_emoji.h"
|
||||||
#include "ffmpeg/ffmpeg_emoji.h"
|
#include "ffmpeg/ffmpeg_emoji.h"
|
||||||
#include "chat_helpers/stickers_lottie.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 "ui/ui_utility.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_chat_helpers.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 Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -42,7 +41,6 @@ using SizeTag = CustomEmojiManager::SizeTag;
|
||||||
case SizeTag::Normal: return LottieSize::EmojiInteraction;
|
case SizeTag::Normal: return LottieSize::EmojiInteraction;
|
||||||
case SizeTag::Large: return LottieSize::EmojiInteractionReserved1;
|
case SizeTag::Large: return LottieSize::EmojiInteractionReserved1;
|
||||||
case SizeTag::Isolated: return LottieSize::EmojiInteractionReserved2;
|
case SizeTag::Isolated: return LottieSize::EmojiInteractionReserved2;
|
||||||
case SizeTag::ReactionFake: return LottieSize::EmojiInteractionReserved3;
|
|
||||||
}
|
}
|
||||||
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
|
Unexpected("SizeTag value in CustomEmojiManager-LottieSizeFromTag.");
|
||||||
}
|
}
|
||||||
|
@ -54,12 +52,16 @@ using SizeTag = CustomEmojiManager::SizeTag;
|
||||||
case SizeTag::Isolated:
|
case SizeTag::Isolated:
|
||||||
return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
|
return (st::largeEmojiSize + 2 * st::largeEmojiOutline)
|
||||||
* style::DevicePixelRatio();
|
* style::DevicePixelRatio();
|
||||||
case SizeTag::ReactionFake:
|
|
||||||
return st::reactStripImage * style::DevicePixelRatio();
|
|
||||||
}
|
}
|
||||||
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
|
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int FrameSizeFromTag(SizeTag tag, int sizeOverride) {
|
||||||
|
return sizeOverride
|
||||||
|
? (sizeOverride * style::DevicePixelRatio())
|
||||||
|
: FrameSizeFromTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class CustomEmojiLoader final
|
class CustomEmojiLoader final
|
||||||
|
@ -69,8 +71,12 @@ public:
|
||||||
CustomEmojiLoader(
|
CustomEmojiLoader(
|
||||||
not_null<Session*> owner,
|
not_null<Session*> owner,
|
||||||
const CustomEmojiId id,
|
const CustomEmojiId id,
|
||||||
SizeTag tag);
|
SizeTag tag,
|
||||||
CustomEmojiLoader(not_null<DocumentData*> document, SizeTag tag);
|
int sizeOverride);
|
||||||
|
CustomEmojiLoader(
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
SizeTag tag,
|
||||||
|
int sizeOverride);
|
||||||
|
|
||||||
[[nodiscard]] bool resolving() const;
|
[[nodiscard]] bool resolving() const;
|
||||||
void resolved(not_null<DocumentData*> document);
|
void resolved(not_null<DocumentData*> document);
|
||||||
|
@ -120,6 +126,7 @@ private:
|
||||||
const CustomEmojiId &id);
|
const CustomEmojiId &id);
|
||||||
|
|
||||||
std::variant<Resolve, Lookup, Load> _state;
|
std::variant<Resolve, Lookup, Load> _state;
|
||||||
|
ushort _sizeOverride = 0;
|
||||||
SizeTag _tag = SizeTag::Normal;
|
SizeTag _tag = SizeTag::Normal;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -127,16 +134,24 @@ private:
|
||||||
CustomEmojiLoader::CustomEmojiLoader(
|
CustomEmojiLoader::CustomEmojiLoader(
|
||||||
not_null<Session*> owner,
|
not_null<Session*> owner,
|
||||||
const CustomEmojiId id,
|
const CustomEmojiId id,
|
||||||
SizeTag tag)
|
SizeTag tag,
|
||||||
|
int sizeOverride)
|
||||||
: _state(InitialState(owner, id))
|
: _state(InitialState(owner, id))
|
||||||
|
, _sizeOverride(sizeOverride)
|
||||||
, _tag(tag) {
|
, _tag(tag) {
|
||||||
|
Expects(sizeOverride >= 0
|
||||||
|
&& sizeOverride <= std::numeric_limits<ushort>::max());
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomEmojiLoader::CustomEmojiLoader(
|
CustomEmojiLoader::CustomEmojiLoader(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
SizeTag tag)
|
SizeTag tag,
|
||||||
|
int sizeOverride)
|
||||||
: _state(Lookup{ document })
|
: _state(Lookup{ document })
|
||||||
|
, _sizeOverride(sizeOverride)
|
||||||
, _tag(tag) {
|
, _tag(tag) {
|
||||||
|
Expects(sizeOverride >= 0
|
||||||
|
&& sizeOverride <= std::numeric_limits<ushort>::max());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CustomEmojiLoader::resolving() const {
|
bool CustomEmojiLoader::resolving() const {
|
||||||
|
@ -232,7 +247,7 @@ void CustomEmojiLoader::startCacheLookup(
|
||||||
lookup->process = std::make_unique<Process>(Process{
|
lookup->process = std::make_unique<Process>(Process{
|
||||||
.loaded = std::move(loaded),
|
.loaded = std::move(loaded),
|
||||||
});
|
});
|
||||||
const auto size = FrameSizeFromTag(_tag);
|
const auto size = FrameSizeFromTag(_tag, _sizeOverride);
|
||||||
const auto weak = base::make_weak(&lookup->process->guard);
|
const auto weak = base::make_weak(&lookup->process->guard);
|
||||||
document->owner().cacheBigFile().get(key, [=](QByteArray value) {
|
document->owner().cacheBigFile().get(key, [=](QByteArray value) {
|
||||||
auto cache = Ui::CustomEmoji::Cache::FromSerialized(value, size);
|
auto cache = Ui::CustomEmoji::Cache::FromSerialized(value, size);
|
||||||
|
@ -251,8 +266,12 @@ void CustomEmojiLoader::lookupDone(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto tag = _tag;
|
const auto tag = _tag;
|
||||||
|
const auto sizeOverride = int(_sizeOverride);
|
||||||
auto loader = [=] {
|
auto loader = [=] {
|
||||||
return std::make_unique<CustomEmojiLoader>(document, tag);
|
return std::make_unique<CustomEmojiLoader>(
|
||||||
|
document,
|
||||||
|
tag,
|
||||||
|
sizeOverride);
|
||||||
};
|
};
|
||||||
auto done = std::move(lookup->process->loaded);
|
auto done = std::move(lookup->process->loaded);
|
||||||
done(Ui::CustomEmoji::Cached(
|
done(Ui::CustomEmoji::Cached(
|
||||||
|
@ -285,10 +304,14 @@ void CustomEmojiLoader::check() {
|
||||||
load->process->lifetime.destroy();
|
load->process->lifetime.destroy();
|
||||||
|
|
||||||
const auto tag = _tag;
|
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 bytes = Lottie::ReadContent(data, filepath);
|
||||||
auto loader = [=] {
|
auto loader = [=] {
|
||||||
return std::make_unique<CustomEmojiLoader>(document, tag);
|
return std::make_unique<CustomEmojiLoader>(
|
||||||
|
document,
|
||||||
|
tag,
|
||||||
|
sizeOverride);
|
||||||
};
|
};
|
||||||
auto put = [=, key = cacheKey(document)](QByteArray value) {
|
auto put = [=, key = cacheKey(document)](QByteArray value) {
|
||||||
document->owner().cacheBigFile().put(key, std::move(value));
|
document->owner().cacheBigFile().put(key, std::move(value));
|
||||||
|
@ -347,7 +370,7 @@ Ui::CustomEmoji::Preview CustomEmojiLoader::preview() {
|
||||||
|| !dimensions.width()) {
|
|| !dimensions.width()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto scale = (FrameSizeFromTag(_tag) * 1.)
|
const auto scale = (FrameSizeFromTag(_tag, _sizeOverride) * 1.)
|
||||||
/ (style::DevicePixelRatio() * dimensions.width());
|
/ (style::DevicePixelRatio() * dimensions.width());
|
||||||
return { document->createMediaView()->thumbnailPath(), scale };
|
return { document->createMediaView()->thumbnailPath(), scale };
|
||||||
};
|
};
|
||||||
|
@ -371,6 +394,7 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag,
|
SizeTag tag,
|
||||||
|
int sizeOverride,
|
||||||
LoaderFactory factory) {
|
LoaderFactory factory) {
|
||||||
auto &instances = _instances[SizeIndex(tag)];
|
auto &instances = _instances[SizeIndex(tag)];
|
||||||
auto i = instances.find(documentId);
|
auto i = instances.find(documentId);
|
||||||
|
@ -385,10 +409,10 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
documentId,
|
documentId,
|
||||||
std::make_unique<Ui::CustomEmoji::Instance>(Loading{
|
std::make_unique<Ui::CustomEmoji::Instance>(Loading{
|
||||||
factory(),
|
factory(),
|
||||||
prepareNonExactPreview(documentId, tag)
|
prepareNonExactPreview(documentId, tag, sizeOverride)
|
||||||
}, std::move(repaint))).first;
|
}, std::move(repaint))).first;
|
||||||
} else if (!i->second->hasImagePreview()) {
|
} else if (!i->second->hasImagePreview()) {
|
||||||
auto preview = prepareNonExactPreview(documentId, tag);
|
auto preview = prepareNonExactPreview(documentId, tag, sizeOverride);
|
||||||
if (preview.isImage()) {
|
if (preview.isImage()) {
|
||||||
i->second->updatePreview(std::move(preview));
|
i->second->updatePreview(std::move(preview));
|
||||||
}
|
}
|
||||||
|
@ -400,7 +424,8 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
|
|
||||||
Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
|
Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
SizeTag tag) const {
|
SizeTag tag,
|
||||||
|
int sizeOverride) const {
|
||||||
for (auto i = _instances.size(); i != 0;) {
|
for (auto i = _instances.size(); i != 0;) {
|
||||||
if (SizeIndex(tag) == --i) {
|
if (SizeIndex(tag) == --i) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -410,7 +435,7 @@ Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
|
||||||
if (j == end(other)) {
|
if (j == end(other)) {
|
||||||
continue;
|
continue;
|
||||||
} else if (const auto nonExact = j->second->imagePreview()) {
|
} else if (const auto nonExact = j->second->imagePreview()) {
|
||||||
const auto size = FrameSizeFromTag(tag);
|
const auto size = FrameSizeFromTag(tag, sizeOverride);
|
||||||
return {
|
return {
|
||||||
nonExact.image().scaled(
|
nonExact.image().scaled(
|
||||||
size,
|
size,
|
||||||
|
@ -427,26 +452,31 @@ Ui::CustomEmoji::Preview CustomEmojiManager::prepareNonExactPreview(
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
QStringView data,
|
QStringView data,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag) {
|
SizeTag tag,
|
||||||
|
int sizeOverride) {
|
||||||
const auto parsed = ParseCustomEmojiData(data);
|
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(
|
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag) {
|
SizeTag tag,
|
||||||
return create(documentId, std::move(update), tag, [&] {
|
int sizeOverride) {
|
||||||
return createLoader(documentId, tag);
|
return create(documentId, std::move(update), tag, sizeOverride, [&] {
|
||||||
|
return createLoader(documentId, tag, sizeOverride);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag) {
|
SizeTag tag,
|
||||||
return create(document->id, std::move(update), tag, [&] {
|
int sizeOverride) {
|
||||||
return createLoader(document, tag);
|
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(
|
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
SizeTag tag) {
|
SizeTag tag,
|
||||||
return std::make_unique<CustomEmojiLoader>(document, tag);
|
int sizeOverride) {
|
||||||
|
return std::make_unique<CustomEmojiLoader>(document, tag, sizeOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
|
std::unique_ptr<Ui::CustomEmoji::Loader> CustomEmojiManager::createLoader(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
SizeTag tag) {
|
SizeTag tag,
|
||||||
|
int sizeOverride) {
|
||||||
auto result = std::make_unique<CustomEmojiLoader>(
|
auto result = std::make_unique<CustomEmojiLoader>(
|
||||||
_owner,
|
_owner,
|
||||||
CustomEmojiId{ .id = documentId },
|
CustomEmojiId{ .id = documentId },
|
||||||
tag);
|
tag,
|
||||||
|
sizeOverride);
|
||||||
if (result->resolving()) {
|
if (result->resolving()) {
|
||||||
const auto i = SizeIndex(tag);
|
const auto i = SizeIndex(tag);
|
||||||
_loaders[i][documentId].push_back(base::make_weak(result.get()));
|
_loaders[i][documentId].push_back(base::make_weak(result.get()));
|
||||||
|
@ -667,9 +700,6 @@ Session &CustomEmojiManager::owner() const {
|
||||||
|
|
||||||
int FrameSizeFromTag(SizeTag tag) {
|
int FrameSizeFromTag(SizeTag tag) {
|
||||||
const auto emoji = EmojiSizeFromTag(tag);
|
const auto emoji = EmojiSizeFromTag(tag);
|
||||||
if (tag == SizeTag::ReactionFake) {
|
|
||||||
return emoji;
|
|
||||||
}
|
|
||||||
const auto factor = style::DevicePixelRatio();
|
const auto factor = style::DevicePixelRatio();
|
||||||
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
|
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
|
||||||
}
|
}
|
||||||
|
@ -705,4 +735,37 @@ void InsertCustomEmoji(
|
||||||
Ui::InputField::CustomEmojiLink(SerializeCustomEmojiId(document)));
|
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
|
} // namespace Data
|
||||||
|
|
|
@ -29,11 +29,10 @@ struct CustomEmojiId {
|
||||||
|
|
||||||
class CustomEmojiManager final : public base::has_weak_ptr {
|
class CustomEmojiManager final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
enum class SizeTag {
|
enum class SizeTag : uchar {
|
||||||
Normal,
|
Normal,
|
||||||
Large,
|
Large,
|
||||||
Isolated,
|
Isolated,
|
||||||
ReactionFake,
|
|
||||||
|
|
||||||
kCount,
|
kCount,
|
||||||
};
|
};
|
||||||
|
@ -44,15 +43,18 @@ public:
|
||||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
||||||
QStringView data,
|
QStringView data,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag = SizeTag::Normal);
|
SizeTag tag = SizeTag::Normal,
|
||||||
|
int sizeOverride = 0);
|
||||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag = SizeTag::Normal);
|
SizeTag tag = SizeTag::Normal,
|
||||||
|
int sizeOverride = 0);
|
||||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag = SizeTag::Normal);
|
SizeTag tag = SizeTag::Normal,
|
||||||
|
int sizeOverride = 0);
|
||||||
|
|
||||||
class Listener {
|
class Listener {
|
||||||
public:
|
public:
|
||||||
|
@ -65,10 +67,12 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
SizeTag tag);
|
SizeTag tag,
|
||||||
|
int sizeOverride = 0);
|
||||||
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
SizeTag tag);
|
SizeTag tag,
|
||||||
|
int sizeOverride = 0);
|
||||||
|
|
||||||
[[nodiscard]] QString lookupSetName(uint64 setId);
|
[[nodiscard]] QString lookupSetName(uint64 setId);
|
||||||
|
|
||||||
|
@ -94,12 +98,14 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] Ui::CustomEmoji::Preview prepareNonExactPreview(
|
[[nodiscard]] Ui::CustomEmoji::Preview prepareNonExactPreview(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
SizeTag tag) const;
|
SizeTag tag,
|
||||||
|
int sizeOverride) const;
|
||||||
template <typename LoaderFactory>
|
template <typename LoaderFactory>
|
||||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
|
||||||
DocumentId documentId,
|
DocumentId documentId,
|
||||||
Fn<void()> update,
|
Fn<void()> update,
|
||||||
SizeTag tag,
|
SizeTag tag,
|
||||||
|
int sizeOverride,
|
||||||
LoaderFactory factory);
|
LoaderFactory factory);
|
||||||
[[nodiscard]] static int SizeIndex(SizeTag tag);
|
[[nodiscard]] static int SizeIndex(SizeTag tag);
|
||||||
|
|
||||||
|
@ -145,4 +151,7 @@ void InsertCustomEmoji(
|
||||||
not_null<Ui::InputField*> field,
|
not_null<Ui::InputField*> field,
|
||||||
not_null<DocumentData*> document);
|
not_null<DocumentData*> document);
|
||||||
|
|
||||||
|
[[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory(
|
||||||
|
not_null<Main::Session*> session);
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -2418,14 +2418,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto emojiPackIds = _dragStateItem
|
if (_dragStateItem) {
|
||||||
? HistoryView::CollectEmojiPacks(_dragStateItem)
|
|
||||||
: std::vector<StickerSetIdentifier>();
|
|
||||||
if (!emojiPackIds.empty()) {
|
|
||||||
HistoryView::AddEmojiPacksAction(
|
HistoryView::AddEmojiPacksAction(
|
||||||
_menu,
|
_menu,
|
||||||
this,
|
_dragStateItem,
|
||||||
std::move(emojiPackIds),
|
HistoryView::EmojiPacksSource::Message,
|
||||||
_controller);
|
_controller);
|
||||||
}
|
}
|
||||||
if (hasWhoReactedItem) {
|
if (hasWhoReactedItem) {
|
||||||
|
|
|
@ -1004,14 +1004,11 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
||||||
AddCopyLinkAction(result, link);
|
AddCopyLinkAction(result, link);
|
||||||
AddMessageActions(result, request, list);
|
AddMessageActions(result, request, list);
|
||||||
|
|
||||||
auto emojiPackIds = item
|
if (item) {
|
||||||
? CollectEmojiPacks(item)
|
|
||||||
: std::vector<StickerSetIdentifier>();
|
|
||||||
if (!emojiPackIds.empty()) {
|
|
||||||
AddEmojiPacksAction(
|
AddEmojiPacksAction(
|
||||||
result,
|
result,
|
||||||
list,
|
item,
|
||||||
std::move(emojiPackIds),
|
HistoryView::EmojiPacksSource::Message,
|
||||||
list->controller());
|
list->controller());
|
||||||
}
|
}
|
||||||
if (hasWhoReactedItem) {
|
if (hasWhoReactedItem) {
|
||||||
|
@ -1162,6 +1159,7 @@ void AddWhoReactedAction(
|
||||||
menu->addAction(Ui::WhoReactedContextAction(
|
menu->addAction(Ui::WhoReactedContextAction(
|
||||||
menu.get(),
|
menu.get(),
|
||||||
Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds),
|
Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds),
|
||||||
|
Data::ReactedMenuFactory(&controller->session()),
|
||||||
participantChosen,
|
participantChosen,
|
||||||
showAllChosen));
|
showAllChosen));
|
||||||
}
|
}
|
||||||
|
@ -1185,12 +1183,15 @@ void ShowWhoReactedMenu(
|
||||||
id));
|
id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const auto reactions = &controller->session().data().reactions();
|
const auto owner = &controller->session().data();
|
||||||
|
const auto reactions = &owner->reactions();
|
||||||
const auto &list = reactions->list(
|
const auto &list = reactions->list(
|
||||||
Data::Reactions::Type::Active);
|
Data::Reactions::Type::Active);
|
||||||
const auto activeNonQuick = (id != reactions->favoriteId())
|
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>(
|
const auto filler = lifetime.make_state<Ui::WhoReactedListMenu>(
|
||||||
|
Data::ReactedMenuFactory(&controller->session()),
|
||||||
participantChosen,
|
participantChosen,
|
||||||
showAllChosen);
|
showAllChosen);
|
||||||
Api::WhoReacted(
|
Api::WhoReacted(
|
||||||
|
@ -1219,6 +1220,17 @@ void ShowWhoReactedMenu(
|
||||||
}
|
}
|
||||||
filler->populate(menu->get(), content);
|
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) {
|
if (creating) {
|
||||||
(*menu)->popup(position);
|
(*menu)->popup(position);
|
||||||
}
|
}
|
||||||
|
@ -1226,31 +1238,51 @@ void ShowWhoReactedMenu(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<StickerSetIdentifier> CollectEmojiPacks(
|
std::vector<StickerSetIdentifier> CollectEmojiPacks(
|
||||||
not_null<HistoryItem*> item) {
|
not_null<HistoryItem*> item,
|
||||||
|
EmojiPacksSource source) {
|
||||||
auto result = std::vector<StickerSetIdentifier>();
|
auto result = std::vector<StickerSetIdentifier>();
|
||||||
const auto owner = &item->history()->owner();
|
const auto owner = &item->history()->owner();
|
||||||
for (const auto &entity : item->originalText().entities) {
|
const auto push = [&](DocumentId id) {
|
||||||
if (entity.type() == EntityType::CustomEmoji) {
|
if (const auto set = owner->document(id)->sticker()) {
|
||||||
const auto data = Data::ParseCustomEmojiData(entity.data());
|
if (set->set.id
|
||||||
if (const auto set = owner->document(data.id)->sticker()) {
|
&& !ranges::contains(
|
||||||
if (set->set.id
|
result,
|
||||||
&& !ranges::contains(
|
set->set.id,
|
||||||
result,
|
&StickerSetIdentifier::id)) {
|
||||||
set->set.id,
|
result.push_back(set->set);
|
||||||
&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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddEmojiPacksAction(
|
void AddEmojiPacksAction(
|
||||||
not_null<Ui::PopupMenu*> menu,
|
not_null<Ui::PopupMenu*> menu,
|
||||||
not_null<QWidget*> context,
|
|
||||||
std::vector<StickerSetIdentifier> packIds,
|
std::vector<StickerSetIdentifier> packIds,
|
||||||
|
EmojiPacksSource source,
|
||||||
not_null<Window::SessionController*> controller) {
|
not_null<Window::SessionController*> controller) {
|
||||||
|
if (packIds.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
class Item final : public Ui::Menu::ItemBase {
|
class Item final : public Ui::Menu::ItemBase {
|
||||||
public:
|
public:
|
||||||
Item(
|
Item(
|
||||||
|
@ -1333,20 +1365,48 @@ void AddEmojiPacksAction(
|
||||||
if (!menu->empty()) {
|
if (!menu->empty()) {
|
||||||
menu->addSeparator();
|
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>(
|
auto button = base::make_unique_q<Item>(
|
||||||
menu->menu(),
|
menu->menu(),
|
||||||
menu->st().menu,
|
menu->st().menu,
|
||||||
(name.text.isEmpty()
|
std::move(text));
|
||||||
? 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)));
|
|
||||||
const auto weak = base::make_weak(controller.get());
|
const auto weak = base::make_weak(controller.get());
|
||||||
button->setClickedCallback([=] {
|
button->setClickedCallback([=] {
|
||||||
const auto strong = weak.get();
|
const auto strong = weak.get();
|
||||||
|
@ -1368,4 +1428,16 @@ void AddEmojiPacksAction(
|
||||||
menu->addAction(std::move(button));
|
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
|
} // namespace HistoryView
|
||||||
|
|
|
@ -83,12 +83,23 @@ void ShowWhoReactedMenu(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
rpl::lifetime &lifetime);
|
rpl::lifetime &lifetime);
|
||||||
|
|
||||||
|
enum class EmojiPacksSource {
|
||||||
|
Message,
|
||||||
|
Reaction,
|
||||||
|
Reactions,
|
||||||
|
};
|
||||||
[[nodiscard]] std::vector<StickerSetIdentifier> CollectEmojiPacks(
|
[[nodiscard]] std::vector<StickerSetIdentifier> CollectEmojiPacks(
|
||||||
not_null<HistoryItem*> item);
|
not_null<HistoryItem*> item,
|
||||||
|
EmojiPacksSource source);
|
||||||
void AddEmojiPacksAction(
|
void AddEmojiPacksAction(
|
||||||
not_null<Ui::PopupMenu*> menu,
|
not_null<Ui::PopupMenu*> menu,
|
||||||
not_null<QWidget*> context,
|
|
||||||
std::vector<StickerSetIdentifier> packIds,
|
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);
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "lang/lang_tag.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 "ui/chat/chat_style.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ void InlineList::unloadCustomEmoji() {
|
||||||
custom->unload();
|
custom->unload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_customCache = QImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InlineList::layout() {
|
void InlineList::layout() {
|
||||||
|
@ -406,19 +407,13 @@ void InlineList::paint(
|
||||||
inner.topLeft() + QPoint(skip, skip),
|
inner.topLeft() + QPoint(skip, skip),
|
||||||
QSize(st::reactionInlineImage, st::reactionInlineImage));
|
QSize(st::reactionInlineImage, st::reactionInlineImage));
|
||||||
if (!skipImage) {
|
if (!skipImage) {
|
||||||
if (button.custom) {
|
if (const auto custom = button.custom.get()) {
|
||||||
if (!_customSkip) {
|
paintCustomFrame(
|
||||||
using namespace Ui::Text;
|
p,
|
||||||
const auto size = st::emojiSize;
|
custom,
|
||||||
_customSkip = (size - AdjustCustomEmojiSize(size)) / 2;
|
inner.topLeft(),
|
||||||
}
|
context.now,
|
||||||
button.custom->paint(p, {
|
textFg.color());
|
||||||
.preview = textFg.color(),
|
|
||||||
.now = context.now,
|
|
||||||
.position = (inner.topLeft()
|
|
||||||
+ QPoint(_customSkip, _customSkip)),
|
|
||||||
.paused = p.inactive(),
|
|
||||||
});
|
|
||||||
} else if (!button.image.isNull()) {
|
} else if (!button.image.isNull()) {
|
||||||
p.drawImage(image.topLeft(), button.image);
|
p.drawImage(image.topLeft(), button.image);
|
||||||
}
|
}
|
||||||
|
@ -535,6 +530,42 @@ void InlineList::resolveUserpicsImage(const Button &button) const {
|
||||||
kMaxRecentUserpics);
|
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()
|
auto InlineList::takeAnimations()
|
||||||
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>> {
|
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>> {
|
||||||
auto result = base::flat_map<
|
auto result = base::flat_map<
|
||||||
|
|
|
@ -104,6 +104,12 @@ private:
|
||||||
const std::vector<not_null<PeerData*>> &peers);
|
const std::vector<not_null<PeerData*>> &peers);
|
||||||
[[nodiscard]] Button prepareButtonWithId(const ReactionId &id);
|
[[nodiscard]] Button prepareButtonWithId(const ReactionId &id);
|
||||||
void resolveUserpicsImage(const Button &button) const;
|
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;
|
QSize countOptimalSize() override;
|
||||||
|
|
||||||
|
@ -113,6 +119,7 @@ private:
|
||||||
Data _data;
|
Data _data;
|
||||||
std::vector<Button> _buttons;
|
std::vector<Button> _buttons;
|
||||||
QSize _skipBlock;
|
QSize _skipBlock;
|
||||||
|
mutable QImage _customCache;
|
||||||
mutable int _customSkip = 0;
|
mutable int _customSkip = 0;
|
||||||
bool _hasCustomEmoji = false;
|
bool _hasCustomEmoji = false;
|
||||||
|
|
||||||
|
|
|
@ -714,7 +714,8 @@ void Manager::paintButton(
|
||||||
*q,
|
*q,
|
||||||
size,
|
size,
|
||||||
expandRatio,
|
expandRatio,
|
||||||
radiusMin + expandRatio * (radiusMax - radiusMin),
|
radiusMin,
|
||||||
|
radiusMax,
|
||||||
scale);
|
scale);
|
||||||
layeredPainter.reset();
|
layeredPainter.reset();
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
|
|
|
@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "api/api_who_reacted.h"
|
#include "api/api_who_reacted.h"
|
||||||
#include "ui/controls/who_reacted_context_action.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 "data/data_message_reaction_id.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -31,7 +33,13 @@ using ::Data::ReactionId;
|
||||||
|
|
||||||
class Row final : public PeerListRow {
|
class Row final : public PeerListRow {
|
||||||
public:
|
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;
|
QSize rightActionSize() const override;
|
||||||
QMargins rightActionMargins() const override;
|
QMargins rightActionMargins() const override;
|
||||||
|
@ -45,7 +53,8 @@ public:
|
||||||
bool actionSelected) override;
|
bool actionSelected) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmojiPtr _emoji = nullptr;
|
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
|
||||||
|
Fn<bool()> _paused;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,14 +83,22 @@ private:
|
||||||
ReactionId reaction) const;
|
ReactionId reaction) const;
|
||||||
void showReaction(const ReactionId &reaction);
|
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<Window::SessionController*> _window;
|
||||||
const not_null<HistoryItem*> _item;
|
const not_null<HistoryItem*> _item;
|
||||||
|
const Ui::Text::CustomEmojiFactory _factory;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
ReactionId _shownReaction;
|
ReactionId _shownReaction;
|
||||||
std::shared_ptr<Api::WhoReadList> _whoReadIds;
|
std::shared_ptr<Api::WhoReadList> _whoReadIds;
|
||||||
std::vector<not_null<PeerData*>> _whoRead;
|
std::vector<not_null<PeerData*>> _whoRead;
|
||||||
|
|
||||||
|
mutable base::flat_map<std::pair<PeerId, ReactionId>, uint64> _idsMap;
|
||||||
|
mutable uint64 _idsCounter = 0;
|
||||||
|
|
||||||
std::vector<AllEntry> _all;
|
std::vector<AllEntry> _all;
|
||||||
QString _allOffset;
|
QString _allOffset;
|
||||||
|
|
||||||
|
@ -92,18 +109,27 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Row::Row(not_null<PeerData*> peer, const ReactionId &id)
|
Row::Row(
|
||||||
: PeerListRow(peer)
|
uint64 id,
|
||||||
, _emoji(Ui::Emoji::Find(id.emoji())) { // #TODO reaction
|
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 {
|
QSize Row::rightActionSize() const {
|
||||||
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||||
return _emoji ? QSize(size, size) : QSize();
|
return _custom ? QSize(size, size) : QSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
QMargins Row::rightActionMargins() const {
|
QMargins Row::rightActionMargins() const {
|
||||||
if (!_emoji) {
|
if (!_custom) {
|
||||||
return QMargins();
|
return QMargins();
|
||||||
}
|
}
|
||||||
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||||
|
@ -125,13 +151,20 @@ void Row::rightActionPaint(
|
||||||
int outerWidth,
|
int outerWidth,
|
||||||
bool selected,
|
bool selected,
|
||||||
bool actionSelected) {
|
bool actionSelected) {
|
||||||
if (!_emoji) {
|
if (!_custom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// #TODO reactions
|
const auto size = Ui::Emoji::GetSizeNormal() / style::DevicePixelRatio();
|
||||||
Ui::Emoji::Draw(p, _emoji, Ui::Emoji::GetSizeNormal(), x, y);
|
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(
|
Controller::Controller(
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
|
@ -140,6 +173,7 @@ Controller::Controller(
|
||||||
std::shared_ptr<Api::WhoReadList> whoReadIds)
|
std::shared_ptr<Api::WhoReadList> whoReadIds)
|
||||||
: _window(window)
|
: _window(window)
|
||||||
, _item(item)
|
, _item(item)
|
||||||
|
, _factory(Data::ReactedMenuFactory(&window->session()))
|
||||||
, _api(&window->session().mtp())
|
, _api(&window->session().mtp())
|
||||||
, _shownReaction(selected)
|
, _shownReaction(selected)
|
||||||
, _whoReadIds(whoReadIds) {
|
, _whoReadIds(whoReadIds) {
|
||||||
|
@ -203,6 +237,16 @@ void Controller::showReaction(const ReactionId &reaction) {
|
||||||
delegate()->peerListRefreshRows();
|
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() {
|
void Controller::fillWhoRead() {
|
||||||
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
|
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
|
||||||
auto &owner = _window->session().data();
|
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) {
|
bool Controller::appendRow(not_null<PeerData*> peer, ReactionId reaction) {
|
||||||
if (delegate()->peerListFindRow(peer->id.value)) {
|
if (delegate()->peerListFindRow(id(peer, reaction))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delegate()->peerListAppendRow(createRow(peer, reaction));
|
delegate()->peerListAppendRow(createRow(peer, reaction));
|
||||||
|
@ -303,7 +347,14 @@ bool Controller::appendRow(not_null<PeerData*> peer, ReactionId reaction) {
|
||||||
std::unique_ptr<PeerListRow> Controller::createRow(
|
std::unique_ptr<PeerListRow> Controller::createRow(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
ReactionId reaction) const {
|
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
|
} // namespace
|
||||||
|
@ -338,6 +389,9 @@ object_ptr<Ui::BoxContent> FullListBox(
|
||||||
}
|
}
|
||||||
const auto tabs = CreateTabs(
|
const auto tabs = CreateTabs(
|
||||||
box,
|
box,
|
||||||
|
Data::ReactedMenuFactory(&item->history()->session()),
|
||||||
|
[=] { return window->isGifPausedAtLeastFor(
|
||||||
|
Window::GifPauseReason::Layer); },
|
||||||
map,
|
map,
|
||||||
selected,
|
selected,
|
||||||
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
|
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/text/text_custom_emoji.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -33,25 +34,6 @@ constexpr auto kScaleDuration = crl::time(120);
|
||||||
constexpr auto kFullDuration = kExpandDuration + kScaleDuration;
|
constexpr auto kFullDuration = kExpandDuration + kScaleDuration;
|
||||||
constexpr auto kExpandDelay = crl::time(40);
|
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 {
|
class StripEmoji final : public Ui::Text::CustomEmoji {
|
||||||
public:
|
public:
|
||||||
StripEmoji(
|
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(
|
StripEmoji::StripEmoji(
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji> wrapped,
|
std::unique_ptr<Ui::Text::CustomEmoji> wrapped,
|
||||||
not_null<Strip*> strip,
|
not_null<Strip*> strip,
|
||||||
|
@ -310,7 +262,13 @@ void Selector::paintAppearing(QPainter &p) {
|
||||||
_cachedRound.setShadowColor(st::shadowFg->c);
|
_cachedRound.setShadowColor(st::shadowFg->c);
|
||||||
q.translate(QPoint(0, _collapsedTopSkip) - _inner.topLeft());
|
q.translate(QPoint(0, _collapsedTopSkip) - _inner.topLeft());
|
||||||
const auto radius = st::reactStripHeight / 2;
|
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.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
q.fillRect(
|
q.fillRect(
|
||||||
QRect{ 0, size.height(), width(), height() - size.height() },
|
QRect{ 0, size.height(), width(), height() - size.height() },
|
||||||
|
@ -622,17 +580,14 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
|
||||||
) - _stripPaintOneShift;
|
) - _stripPaintOneShift;
|
||||||
auto factory = [=](DocumentId id, Fn<void()> repaint)
|
auto factory = [=](DocumentId id, Fn<void()> repaint)
|
||||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||||
|
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
|
||||||
|
const auto sizeOverride = st::reactStripImage;
|
||||||
const auto isDefaultReaction = defaultReactionIds.contains(id);
|
const auto isDefaultReaction = defaultReactionIds.contains(id);
|
||||||
auto result = isDefaultReaction
|
auto result = isDefaultReaction
|
||||||
? std::make_unique<ShiftedEmoji>(
|
? std::make_unique<Ui::Text::ShiftedEmoji>(
|
||||||
manager,
|
manager->create(id, std::move(repaint), tag, sizeOverride),
|
||||||
id,
|
|
||||||
std::move(repaint),
|
|
||||||
_defaultReactionShift)
|
_defaultReactionShift)
|
||||||
: manager->create(
|
: manager->create(id, std::move(repaint), tag);
|
||||||
id,
|
|
||||||
std::move(repaint),
|
|
||||||
Data::CustomEmojiManager::SizeTag::Large);
|
|
||||||
const auto i = _defaultReactionInStripMap.find(id);
|
const auto i = _defaultReactionInStripMap.find(id);
|
||||||
if (i != end(_defaultReactionInStripMap)) {
|
if (i != end(_defaultReactionInStripMap)) {
|
||||||
return std::make_unique<StripEmoji>(
|
return std::make_unique<StripEmoji>(
|
||||||
|
@ -862,6 +817,16 @@ AttachSelectorResult AttachSelectorToMenu(
|
||||||
state.toggling);
|
state.toggling);
|
||||||
}, selector->lifetime());
|
}, 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;
|
return AttachSelectorResult::Attached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,16 @@ using ::Data::ReactionId;
|
||||||
not_null<Ui::AbstractButton*> CreateTab(
|
not_null<Ui::AbstractButton*> CreateTab(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
const style::MultiSelect &st,
|
const style::MultiSelect &st,
|
||||||
|
const Ui::Text::CustomEmojiFactory &factory,
|
||||||
|
Fn<bool()> paused,
|
||||||
const ReactionId &reaction,
|
const ReactionId &reaction,
|
||||||
Ui::WhoReadType whoReadType,
|
Ui::WhoReadType whoReadType,
|
||||||
int count,
|
int count,
|
||||||
rpl::producer<bool> selected) {
|
rpl::producer<bool> selected) {
|
||||||
struct State {
|
struct State {
|
||||||
bool selected = false;
|
std::unique_ptr<Ui::Text::CustomEmoji> custom;
|
||||||
QImage cache;
|
QImage cache;
|
||||||
|
bool selected = false;
|
||||||
};
|
};
|
||||||
const auto stm = &st.item;
|
const auto stm = &st.item;
|
||||||
const auto text = QString("%L1").arg(count);
|
const auto text = QString("%L1").arg(count);
|
||||||
|
@ -49,10 +52,19 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
result->update();
|
result->update();
|
||||||
}, result->lifetime());
|
}, result->lifetime());
|
||||||
|
|
||||||
|
state->custom = reaction.empty()
|
||||||
|
? nullptr
|
||||||
|
: factory(
|
||||||
|
Data::ReactionEntityData(reaction),
|
||||||
|
[=] { result->update(); });
|
||||||
|
|
||||||
result->paintRequest(
|
result->paintRequest(
|
||||||
) | rpl::start_with_next([=] {
|
) | 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()) {
|
if (state->cache.isNull()) {
|
||||||
const auto factor = style::DevicePixelRatio();
|
|
||||||
state->cache = QImage(
|
state->cache = QImage(
|
||||||
result->size() * factor,
|
result->size() * factor,
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
@ -70,12 +82,7 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
}
|
}
|
||||||
const auto skip = st::reactionsTabIconSkip;
|
const auto skip = st::reactionsTabIconSkip;
|
||||||
const auto icon = QRect(skip, 0, height, height);
|
const auto icon = QRect(skip, 0, height, height);
|
||||||
// #TODO reactions
|
if (!state->custom) {
|
||||||
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 {
|
|
||||||
using Type = Ui::WhoReadType;
|
using Type = Ui::WhoReadType;
|
||||||
(reaction.emoji().isEmpty()
|
(reaction.emoji().isEmpty()
|
||||||
? (state->selected
|
? (state->selected
|
||||||
|
@ -96,7 +103,25 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
p.setFont(font);
|
p.setFont(font);
|
||||||
p.drawText(textLeft, stm->padding.top() + font->ascent, text);
|
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());
|
}, result->lifetime());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -105,8 +130,10 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
|
|
||||||
not_null<Tabs*> CreateTabs(
|
not_null<Tabs*> CreateTabs(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
|
Ui::Text::CustomEmojiFactory factory,
|
||||||
|
Fn<bool()> paused,
|
||||||
const std::vector<Data::MessageReaction> &items,
|
const std::vector<Data::MessageReaction> &items,
|
||||||
const ReactionId &selected,
|
const Data::ReactionId &selected,
|
||||||
Ui::WhoReadType whoReadType) {
|
Ui::WhoReadType whoReadType) {
|
||||||
struct State {
|
struct State {
|
||||||
rpl::variable<ReactionId> selected;
|
rpl::variable<ReactionId> selected;
|
||||||
|
@ -123,6 +150,8 @@ not_null<Tabs*> CreateTabs(
|
||||||
const auto tab = CreateTab(
|
const auto tab = CreateTab(
|
||||||
tabs,
|
tabs,
|
||||||
*st,
|
*st,
|
||||||
|
factory,
|
||||||
|
paused,
|
||||||
reaction,
|
reaction,
|
||||||
whoReadType,
|
whoReadType,
|
||||||
count,
|
count,
|
||||||
|
|
|
@ -27,6 +27,8 @@ struct Tabs {
|
||||||
|
|
||||||
not_null<Tabs*> CreateTabs(
|
not_null<Tabs*> CreateTabs(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
|
Ui::Text::CustomEmojiFactory factory,
|
||||||
|
Fn<bool()> paused,
|
||||||
const std::vector<Data::MessageReaction> &items,
|
const std::vector<Data::MessageReaction> &items,
|
||||||
const Data::ReactionId &selected,
|
const Data::ReactionId &selected,
|
||||||
Ui::WhoReadType whoReadType);
|
Ui::WhoReadType whoReadType);
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/chat/group_call_userpics.h"
|
#include "ui/chat/group_call_userpics.h"
|
||||||
|
#include "ui/text/text_custom_emoji.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_menu_icons.h"
|
#include "styles/style_menu_icons.h"
|
||||||
|
@ -67,9 +68,11 @@ StringWithReacted ReplaceTag<StringWithReacted>::Call(
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using Text::CustomEmojiFactory;
|
||||||
|
|
||||||
struct EntryData {
|
struct EntryData {
|
||||||
QString text;
|
QString text;
|
||||||
QString reaction;
|
QString customEntityData;
|
||||||
QImage userpic;
|
QImage userpic;
|
||||||
Fn<void()> callback;
|
Fn<void()> callback;
|
||||||
};
|
};
|
||||||
|
@ -79,6 +82,7 @@ public:
|
||||||
Action(
|
Action(
|
||||||
not_null<PopupMenu*> parentMenu,
|
not_null<PopupMenu*> parentMenu,
|
||||||
rpl::producer<WhoReadContent> content,
|
rpl::producer<WhoReadContent> content,
|
||||||
|
CustomEmojiFactory factory,
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen);
|
Fn<void()> showAllChosen);
|
||||||
|
|
||||||
|
@ -108,10 +112,12 @@ private:
|
||||||
const Fn<void()> _showAllChosen;
|
const Fn<void()> _showAllChosen;
|
||||||
const std::unique_ptr<GroupCallUserpics> _userpics;
|
const std::unique_ptr<GroupCallUserpics> _userpics;
|
||||||
const style::Menu &_st;
|
const style::Menu &_st;
|
||||||
|
const CustomEmojiFactory _customEmojiFactory;
|
||||||
|
|
||||||
WhoReactedListMenu _submenu;
|
WhoReactedListMenu _submenu;
|
||||||
|
|
||||||
Text::String _text;
|
Text::String _text;
|
||||||
|
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
|
||||||
int _textWidth = 0;
|
int _textWidth = 0;
|
||||||
const int _height = 0;
|
const int _height = 0;
|
||||||
int _userpicsWidth = 0;
|
int _userpicsWidth = 0;
|
||||||
|
@ -143,6 +149,7 @@ TextParseOptions MenuTextOptions = {
|
||||||
Action::Action(
|
Action::Action(
|
||||||
not_null<PopupMenu*> parentMenu,
|
not_null<PopupMenu*> parentMenu,
|
||||||
rpl::producer<WhoReadContent> content,
|
rpl::producer<WhoReadContent> content,
|
||||||
|
Text::CustomEmojiFactory factory,
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen)
|
Fn<void()> showAllChosen)
|
||||||
: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
|
: ItemBase(parentMenu->menu(), parentMenu->menu()->st())
|
||||||
|
@ -155,7 +162,8 @@ Action::Action(
|
||||||
rpl::never<bool>(),
|
rpl::never<bool>(),
|
||||||
[=] { update(); }))
|
[=] { update(); }))
|
||||||
, _st(parentMenu->menu()->st())
|
, _st(parentMenu->menu()->st())
|
||||||
, _submenu(_participantChosen, _showAllChosen)
|
, _customEmojiFactory(std::move(factory))
|
||||||
|
, _submenu(_customEmojiFactory, _participantChosen, _showAllChosen)
|
||||||
, _height(st::defaultWhoRead.itemPadding.top()
|
, _height(st::defaultWhoRead.itemPadding.top()
|
||||||
+ _st.itemStyle.font->height
|
+ _st.itemStyle.font->height
|
||||||
+ st::defaultWhoRead.itemPadding.bottom()) {
|
+ st::defaultWhoRead.itemPadding.bottom()) {
|
||||||
|
@ -300,18 +308,28 @@ void Action::paint(Painter &p) {
|
||||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||||
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
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) {
|
if (enabled) {
|
||||||
paintRipple(p, 0, 0);
|
paintRipple(p, 0, 0);
|
||||||
}
|
}
|
||||||
if (const auto emoji = Emoji::Find(_content.singleReaction)) {
|
if (!_custom && !_content.singleCustomEntityData.isEmpty()) {
|
||||||
// #TODO reactions
|
_custom = _customEmojiFactory(
|
||||||
|
_content.singleCustomEntityData,
|
||||||
|
[=] { update(); });
|
||||||
|
}
|
||||||
|
if (_custom) {
|
||||||
const auto ratio = style::DevicePixelRatio();
|
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()
|
const auto x = st::defaultWhoRead.iconPosition.x()
|
||||||
+ (st::whoReadChecks.width() - (size / ratio)) / 2;
|
+ (st::whoReadChecks.width() - adjusted) / 2;
|
||||||
const auto y = (_height - (size / ratio)) / 2;
|
const auto y = (_height - adjusted) / 2;
|
||||||
Emoji::Draw(p, emoji, size, x, y);
|
_custom->paint(p, {
|
||||||
|
.preview = _st.ripple.color->c,
|
||||||
|
.now = crl::now(),
|
||||||
|
.position = { x, y },
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const auto &icon = (_content.fullReactionsCount)
|
const auto &icon = (_content.fullReactionsCount)
|
||||||
? (!enabled
|
? (!enabled
|
||||||
|
@ -357,7 +375,7 @@ void Action::refreshText() {
|
||||||
const auto onlySeenCount = ranges::count(
|
const auto onlySeenCount = ranges::count(
|
||||||
_content.participants,
|
_content.participants,
|
||||||
QString(),
|
QString(),
|
||||||
&WhoReadParticipant::reaction);
|
&WhoReadParticipant::customEntityData);
|
||||||
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
||||||
_text.setMarkedText(
|
_text.setMarkedText(
|
||||||
_st.itemStyle,
|
_st.itemStyle,
|
||||||
|
@ -448,6 +466,7 @@ class WhoReactedListMenu::EntryAction final : public Menu::ItemBase {
|
||||||
public:
|
public:
|
||||||
EntryAction(
|
EntryAction(
|
||||||
not_null<RpWidget*> parent,
|
not_null<RpWidget*> parent,
|
||||||
|
CustomEmojiFactory factory,
|
||||||
const style::Menu &st,
|
const style::Menu &st,
|
||||||
EntryData &&data);
|
EntryData &&data);
|
||||||
|
|
||||||
|
@ -462,22 +481,26 @@ private:
|
||||||
void paint(Painter &&p);
|
void paint(Painter &&p);
|
||||||
|
|
||||||
const not_null<QAction*> _dummyAction;
|
const not_null<QAction*> _dummyAction;
|
||||||
|
const CustomEmojiFactory _customEmojiFactory;
|
||||||
const style::Menu &_st;
|
const style::Menu &_st;
|
||||||
const int _height = 0;
|
const int _height = 0;
|
||||||
|
|
||||||
Text::String _text;
|
Text::String _text;
|
||||||
EmojiPtr _emoji = nullptr;
|
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
|
||||||
int _textWidth = 0;
|
|
||||||
QImage _userpic;
|
QImage _userpic;
|
||||||
|
int _textWidth = 0;
|
||||||
|
int _customSize = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
WhoReactedListMenu::EntryAction::EntryAction(
|
WhoReactedListMenu::EntryAction::EntryAction(
|
||||||
not_null<RpWidget*> parent,
|
not_null<RpWidget*> parent,
|
||||||
|
CustomEmojiFactory customEmojiFactory,
|
||||||
const style::Menu &st,
|
const style::Menu &st,
|
||||||
EntryData &&data)
|
EntryData &&data)
|
||||||
: ItemBase(parent, st)
|
: ItemBase(parent, st)
|
||||||
, _dummyAction(CreateChild<QAction>(parent.get()))
|
, _dummyAction(CreateChild<QAction>(parent.get()))
|
||||||
|
, _customEmojiFactory(std::move(customEmojiFactory))
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _height(st::defaultWhoRead.photoSkip * 2 + st::defaultWhoRead.photoSize) {
|
, _height(st::defaultWhoRead.photoSkip * 2 + st::defaultWhoRead.photoSize) {
|
||||||
setAcceptBoth(true);
|
setAcceptBoth(true);
|
||||||
|
@ -509,14 +532,14 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
|
||||||
setClickedCallback(std::move(data.callback));
|
setClickedCallback(std::move(data.callback));
|
||||||
_userpic = std::move(data.userpic);
|
_userpic = std::move(data.userpic);
|
||||||
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
|
_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 textWidth = _text.maxWidth();
|
||||||
const auto &padding = _st.itemPadding;
|
const auto &padding = _st.itemPadding;
|
||||||
const auto rightSkip = padding.right()
|
const auto rightSkip = padding.right()
|
||||||
+ (_emoji
|
+ (_custom ? (size + padding.right()) : 0);
|
||||||
? ((Emoji::GetSizeNormal() / style::DevicePixelRatio())
|
|
||||||
+ padding.right())
|
|
||||||
: 0);
|
|
||||||
const auto goodWidth = st::defaultWhoRead.nameLeft
|
const auto goodWidth = st::defaultWhoRead.nameLeft
|
||||||
+ textWidth
|
+ textWidth
|
||||||
+ rightSkip;
|
+ rightSkip;
|
||||||
|
@ -541,7 +564,7 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
||||||
const auto photoTop = (height() - photoSize) / 2;
|
const auto photoTop = (height() - photoSize) / 2;
|
||||||
if (!_userpic.isNull()) {
|
if (!_userpic.isNull()) {
|
||||||
p.drawImage(photoLeft, photoTop, _userpic);
|
p.drawImage(photoLeft, photoTop, _userpic);
|
||||||
} else if (!_emoji) {
|
} else if (!_custom) {
|
||||||
st::menuIconReactions.paintInCenter(
|
st::menuIconReactions.paintInCenter(
|
||||||
p,
|
p,
|
||||||
QRect(photoLeft, photoTop, photoSize, photoSize));
|
QRect(photoLeft, photoTop, photoSize, photoSize));
|
||||||
|
@ -559,16 +582,17 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
||||||
_textWidth,
|
_textWidth,
|
||||||
width());
|
width());
|
||||||
|
|
||||||
if (_emoji) {
|
if (_custom) {
|
||||||
// #TODO reactions
|
|
||||||
const auto size = Emoji::GetSizeNormal();
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
const auto ratio = style::DevicePixelRatio();
|
||||||
Emoji::Draw(
|
const auto size = Emoji::GetSizeNormal() / ratio;
|
||||||
p,
|
const auto skip = (size - _customSize) / 2;
|
||||||
_emoji,
|
_custom->paint(p, {
|
||||||
size,
|
.preview = _st.ripple.color->c,
|
||||||
width() - _st.itemPadding.right() - (size / ratio),
|
.now = crl::now(),
|
||||||
(height() - (size / ratio)) / 2);
|
.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(
|
base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
||||||
not_null<PopupMenu*> menu,
|
not_null<PopupMenu*> menu,
|
||||||
rpl::producer<WhoReadContent> content,
|
rpl::producer<WhoReadContent> content,
|
||||||
|
CustomEmojiFactory factory,
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen) {
|
Fn<void()> showAllChosen) {
|
||||||
return base::make_unique_q<Action>(
|
return base::make_unique_q<Action>(
|
||||||
menu,
|
menu,
|
||||||
std::move(content),
|
std::move(content),
|
||||||
|
std::move(factory),
|
||||||
std::move(participantChosen),
|
std::move(participantChosen),
|
||||||
std::move(showAllChosen));
|
std::move(showAllChosen));
|
||||||
}
|
}
|
||||||
|
|
||||||
WhoReactedListMenu::WhoReactedListMenu(
|
WhoReactedListMenu::WhoReactedListMenu(
|
||||||
|
CustomEmojiFactory factory,
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen)
|
Fn<void()> showAllChosen)
|
||||||
: _participantChosen(std::move(participantChosen))
|
: _customEmojiFactory(std::move(factory))
|
||||||
|
, _participantChosen(std::move(participantChosen))
|
||||||
, _showAllChosen(std::move(showAllChosen)) {
|
, _showAllChosen(std::move(showAllChosen)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,7 +639,7 @@ void WhoReactedListMenu::populate(
|
||||||
Fn<void()> refillTopActions) {
|
Fn<void()> refillTopActions) {
|
||||||
const auto reactions = ranges::count_if(
|
const auto reactions = ranges::count_if(
|
||||||
content.participants,
|
content.participants,
|
||||||
[](const auto &p) { return !p.reaction.isEmpty(); });
|
[](const auto &p) { return !p.customEntityData.isEmpty(); });
|
||||||
const auto addShowAll = (content.fullReactionsCount > reactions);
|
const auto addShowAll = (content.fullReactionsCount > reactions);
|
||||||
const auto actionsCount = int(content.participants.size())
|
const auto actionsCount = int(content.participants.size())
|
||||||
+ (addShowAll ? 1 : 0);
|
+ (addShowAll ? 1 : 0);
|
||||||
|
@ -629,6 +657,7 @@ void WhoReactedListMenu::populate(
|
||||||
} else {
|
} else {
|
||||||
auto item = base::make_unique_q<EntryAction>(
|
auto item = base::make_unique_q<EntryAction>(
|
||||||
menu->menu(),
|
menu->menu(),
|
||||||
|
_customEmojiFactory,
|
||||||
menu->menu()->st(),
|
menu->menu()->st(),
|
||||||
std::move(data));
|
std::move(data));
|
||||||
_actions.push_back(item.get());
|
_actions.push_back(item.get());
|
||||||
|
@ -642,7 +671,7 @@ void WhoReactedListMenu::populate(
|
||||||
};
|
};
|
||||||
append({
|
append({
|
||||||
.text = participant.name,
|
.text = participant.name,
|
||||||
.reaction = participant.reaction,
|
.customEntityData = participant.customEntityData,
|
||||||
.userpic = participant.userpicLarge,
|
.userpic = participant.userpicLarge,
|
||||||
.callback = chosen,
|
.callback = chosen,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
|
#include "ui/text/text_block.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Menu {
|
namespace Menu {
|
||||||
|
@ -18,7 +19,7 @@ class PopupMenu;
|
||||||
|
|
||||||
struct WhoReadParticipant {
|
struct WhoReadParticipant {
|
||||||
QString name;
|
QString name;
|
||||||
QString reaction;
|
QString customEntityData;
|
||||||
QImage userpicSmall;
|
QImage userpicSmall;
|
||||||
QImage userpicLarge;
|
QImage userpicLarge;
|
||||||
std::pair<uint64, uint64> userpicKey = {};
|
std::pair<uint64, uint64> userpicKey = {};
|
||||||
|
@ -40,7 +41,7 @@ enum class WhoReadType {
|
||||||
struct WhoReadContent {
|
struct WhoReadContent {
|
||||||
std::vector<WhoReadParticipant> participants;
|
std::vector<WhoReadParticipant> participants;
|
||||||
WhoReadType type = WhoReadType::Seen;
|
WhoReadType type = WhoReadType::Seen;
|
||||||
QString singleReaction;
|
QString singleCustomEntityData;
|
||||||
int fullReactionsCount = 0;
|
int fullReactionsCount = 0;
|
||||||
int fullReadCount = 0;
|
int fullReadCount = 0;
|
||||||
bool unknown = false;
|
bool unknown = false;
|
||||||
|
@ -49,12 +50,14 @@ struct WhoReadContent {
|
||||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
||||||
not_null<PopupMenu*> menu,
|
not_null<PopupMenu*> menu,
|
||||||
rpl::producer<WhoReadContent> content,
|
rpl::producer<WhoReadContent> content,
|
||||||
|
Text::CustomEmojiFactory factory,
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen);
|
Fn<void()> showAllChosen);
|
||||||
|
|
||||||
class WhoReactedListMenu final {
|
class WhoReactedListMenu final {
|
||||||
public:
|
public:
|
||||||
WhoReactedListMenu(
|
WhoReactedListMenu(
|
||||||
|
Text::CustomEmojiFactory factory,
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen);
|
Fn<void()> showAllChosen);
|
||||||
|
|
||||||
|
@ -67,6 +70,7 @@ public:
|
||||||
private:
|
private:
|
||||||
class EntryAction;
|
class EntryAction;
|
||||||
|
|
||||||
|
const Text::CustomEmojiFactory _customEmojiFactory;
|
||||||
const Fn<void(uint64)> _participantChosen;
|
const Fn<void(uint64)> _participantChosen;
|
||||||
const Fn<void()> _showAllChosen;
|
const Fn<void()> _showAllChosen;
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,7 @@ std::optional<Cache> Cache::FromSerialized(
|
||||||
const QByteArray &serialized,
|
const QByteArray &serialized,
|
||||||
int requestedSize) {
|
int requestedSize) {
|
||||||
Expects(requestedSize > 0 && requestedSize <= kMaxSize);
|
Expects(requestedSize > 0 && requestedSize <= kMaxSize);
|
||||||
|
|
||||||
if (serialized.size() <= sizeof(CacheHeader)) {
|
if (serialized.size() <= sizeof(CacheHeader)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -341,27 +342,30 @@ PaintFrameResult Cache::paintCurrentFrame(
|
||||||
if (!_frames) {
|
if (!_frames) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto now = context.paused ? 0 : context.now;
|
const auto first = context.firstFrameOnly;
|
||||||
const auto finishes = now ? currentFrameFinishes() : 0;
|
if (!first) {
|
||||||
if (finishes && now >= finishes) {
|
const auto now = context.paused ? 0 : context.now;
|
||||||
++_frame;
|
const auto finishes = now ? currentFrameFinishes() : 0;
|
||||||
if (_finished && _frame == _frames) {
|
if (finishes && now >= finishes) {
|
||||||
_frame = 0;
|
++_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 size = _size / style::DevicePixelRatio();
|
||||||
const auto rect = QRect(context.position, QSize(size, size));
|
const auto rect = QRect(context.position, QSize(size, size));
|
||||||
PaintScaledImage(p, rect, info, context);
|
PaintScaledImage(p, rect, info, context);
|
||||||
const auto next = currentFrameFinishes();
|
const auto next = first ? 0 : currentFrameFinishes();
|
||||||
const auto duration = next ? (next - _shown) : 0;
|
|
||||||
return {
|
return {
|
||||||
.painted = true,
|
.painted = true,
|
||||||
.next = currentFrameFinishes(),
|
.next = next,
|
||||||
.duration = duration,
|
.duration = next ? (next - _shown) : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/text/text_block.h"
|
#include "ui/text/text_custom_emoji.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
#include "base/bytes.h"
|
#include "base/bytes.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit fc2c55367099ca7bdeacd0d52ff6007f00a6ba72
|
Subproject commit f876d15eedbce39a445b020b92f45d137952ed2a
|
Loading…
Add table
Reference in a new issue