mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +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_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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() };
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -714,7 +714,8 @@ void Manager::paintButton(
|
|||
*q,
|
||||
size,
|
||||
expandRatio,
|
||||
radiusMin + expandRatio * (radiusMax - radiusMin),
|
||||
radiusMin,
|
||||
radiusMax,
|
||||
scale);
|
||||
layeredPainter.reset();
|
||||
p.drawImage(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Add table
Reference in a new issue