Show effect animation with correct geometry.

This commit is contained in:
John Preston 2024-05-07 22:15:34 +04:00
parent a19e71324b
commit 92133e7f50
15 changed files with 197 additions and 43 deletions

View File

@ -286,7 +286,7 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
document,
media->videoThumbnailContent(),
QString(),
true);
Stickers::EffectType::PremiumSticker);
const auto update = [=] {
if (!state->readyInvoked

View File

@ -269,10 +269,10 @@ std::unique_ptr<Lottie::SinglePlayer> EmojiPack::effectPlayer(
not_null<DocumentData*> document,
QByteArray data,
QString filepath,
bool premium) {
EffectType type) {
// Shortened copy from stickers_lottie module.
const auto baseKey = document->bigFileBaseCacheKey();
const auto tag = uint8(0);
const auto tag = uint8(type);
const auto keyShift = ((tag << 4) & 0xF0)
| (uint8(ChatHelpers::StickerLottieSize::EmojiInteraction) & 0x0F);
const auto key = Storage::Cache::Key{
@ -292,19 +292,24 @@ std::unique_ptr<Lottie::SinglePlayer> EmojiPack::effectPlayer(
std::move(data));
});
};
const auto size = premium
const auto size = (type == EffectType::PremiumSticker)
? HistoryView::Sticker::PremiumEffectSize(document)
: HistoryView::Sticker::EmojiEffectSize();
: (type == EffectType::EmojiInteraction)
? HistoryView::Sticker::EmojiEffectSize()
: HistoryView::Sticker::MessageEffectSize();
const auto request = Lottie::FrameRequest{
size * style::DevicePixelRatio(),
};
auto &weakProvider = _sharedProviders[document];
auto &weakProvider = _sharedProviders[{ document, type }];
auto shared = [&] {
if (const auto result = weakProvider.lock()) {
return result;
}
const auto count = (type == EffectType::PremiumSticker)
? kPremiumCachesCount
: kEmojiCachesCount;
const auto result = Lottie::SinglePlayer::SharedProvider(
premium ? kPremiumCachesCount : kEmojiCachesCount,
count,
get,
put,
Lottie::ReadContent(data, filepath),

View File

@ -50,6 +50,12 @@ struct LargeEmojiImage {
[[nodiscard]] static QSize Size();
};
enum class EffectType : uint8 {
EmojiInteraction,
PremiumSticker,
MessageEffect,
};
class EmojiPack final {
public:
using ViewElement = HistoryView::Element;
@ -95,11 +101,23 @@ public:
not_null<DocumentData*> document,
QByteArray data,
QString filepath,
bool premium);
EffectType type);
private:
class ImageLoader;
struct ProviderKey {
not_null<DocumentData*> document;
Stickers::EffectType type = {};
friend inline auto operator<=>(
const ProviderKey &,
const ProviderKey &) = default;
friend inline bool operator==(
const ProviderKey &,
const ProviderKey &) = default;
};
void refresh();
void refreshDelayed();
void refreshAnimations();
@ -135,7 +153,7 @@ private:
mtpRequestId _animationsRequestId = 0;
base::flat_map<
not_null<DocumentData*>,
ProviderKey,
std::weak_ptr<Lottie::FrameProvider>> _sharedProviders;
rpl::event_stream<> _refreshed;

View File

@ -737,6 +737,9 @@ void Reactions::resolveEffectImages() {
LOG(("API Error: Effect '%1' not found!"
).arg(ReactionIdToLog(id)));
}
if (i != end(_effects)) {
preloadEffect(*i);
}
}
}

View File

@ -173,20 +173,10 @@ ClickHandlerPtr BottomInfo::replayEffectLink(
left += width() - available;
top += st::msgDateFont->height;
}
auto x = left;
auto y = top;
auto widthLeft = available;
if (_effect) {
const auto add = st::reactionInfoBetween;
const auto width = st::reactionInfoSize;
if (x > left && widthLeft < width) {
x = left;
y += st::msgDateFont->height;
widthLeft = available;
}
const auto image = QRect(
x,
y,
left,
top,
st::reactionInfoSize,
st::msgDateFont->height);
if (image.contains(position)) {
@ -195,8 +185,6 @@ ClickHandlerPtr BottomInfo::replayEffectLink(
}
return _replayLink;
}
x += width + add;
widthLeft -= width + add;
}
return nullptr;
}
@ -544,6 +532,25 @@ void BottomInfo::continueEffectAnimation(
}
}
QRect BottomInfo::effectIconGeometry() const {
if (!_effect) {
return {};
}
auto left = 0;
auto top = 0;
auto available = width();
if (height() != minHeight()) {
available = std::min(available, _effectMaxWidth);
left += width() - available;
top += st::msgDateFont->height;
}
return QRect(
left + (st::reactionInfoSize - st::effectInfoImage) / 2,
top + (st::msgDateFont->height - st::effectInfoImage) / 2,
st::effectInfoImage,
st::effectInfoImage);
}
BottomInfo::Data BottomInfoDataFromMessage(not_null<Message*> message) {
using Flag = BottomInfo::Data::Flag;
const auto item = message->data();

View File

@ -82,6 +82,8 @@ public:
void continueEffectAnimation(
std::unique_ptr<Ui::ReactionFlyAnimation> animation);
QRect effectIconGeometry() const;
private:
struct Effect;

View File

@ -1786,6 +1786,10 @@ auto Element::takeEffectAnimation()
return nullptr;
}
QRect Element::effectIconGeometry() const {
return QRect();
}
Element::~Element() {
// Delete media while owner still exists.
clearSpecialOnlyEmoji();

View File

@ -523,6 +523,7 @@ public:
void previousInBlocksChanged();
void nextInBlocksRemoved();
[[nodiscard]] virtual QRect effectIconGeometry() const;
[[nodiscard]] virtual QRect innerGeometry() const = 0;
void customEmojiRepaint();

View File

@ -119,7 +119,7 @@ bool EmojiInteractions::playPremiumEffect(
document->createMediaView()->videoThumbnailContent(),
QString(),
false,
true);
Stickers::EffectType::PremiumSticker);
}
}
}
@ -147,7 +147,7 @@ void EmojiInteractions::play(
media->bytes(),
media->owner()->filepath(),
incoming,
false);
Stickers::EffectType::EmojiInteraction);
}
void EmojiInteractions::playEffectOnRead(not_null<const Element*> view) {
@ -214,7 +214,7 @@ void EmojiInteractions::playEffect(
resolved.content,
resolved.filepath,
false,
false);
Stickers::EffectType::MessageEffect);
}
void EmojiInteractions::addPendingEffect(not_null<const Element*> view) {
@ -272,7 +272,7 @@ void EmojiInteractions::play(
QByteArray data,
QString filepath,
bool incoming,
bool premium) {
Stickers::EffectType type) {
const auto top = _itemTop(view);
const auto bottom = top + view->height();
if (_visibleTop >= bottom
@ -286,12 +286,14 @@ void EmojiInteractions::play(
document,
data,
filepath,
premium);
type);
const auto inner = premium
const auto inner = (type == Stickers::EffectType::PremiumSticker)
? HistoryView::Sticker::Size(document)
: HistoryView::Sticker::EmojiSize();
const auto shift = premium ? QPoint() : GenerateRandomShift(inner);
const auto shift = (type == Stickers::EffectType::EmojiInteraction)
? GenerateRandomShift(inner)
: QPoint();
const auto raw = lottie.get();
lottie->updates(
) | rpl::start_with_next([=](Lottie::Update update) {
@ -312,16 +314,18 @@ void EmojiInteractions::play(
.lottie = std::move(lottie),
.shift = shift,
.inner = inner,
.outer = (premium
.outer = ((type == Stickers::EffectType::PremiumSticker)
? HistoryView::Sticker::PremiumEffectSize(document)
: HistoryView::Sticker::EmojiEffectSize()),
.premium = premium,
: (type == Stickers::EffectType::EmojiInteraction)
? HistoryView::Sticker::EmojiEffectSize()
: HistoryView::Sticker::MessageEffectSize()),
.type = type,
});
if (incoming) {
_playStarted.fire(std::move(emoticon));
}
if (const auto media = view->media()) {
if (!premium) {
if (type == Stickers::EffectType::EmojiInteraction) {
media->stickerClearLoopPlayed();
}
}
@ -336,9 +340,28 @@ void EmojiInteractions::visibleAreaUpdated(
QRect EmojiInteractions::computeRect(const Play &play) const {
const auto view = play.view;
const auto viewTop = _itemTop(view);
if (viewTop < 0) {
return QRect();
}
if (play.type == Stickers::EffectType::MessageEffect) {
const auto icon = view->effectIconGeometry();
if (icon.isEmpty()) {
return QRect();
}
const auto size = play.outer;
const auto shift = view->hasRightLayout()
? (-size.width() / 3)
: (size.width() / 3);
return QRect(
shift + icon.x() + (icon.width() - size.width()) / 2,
viewTop + icon.y() + (icon.height() - size.height()) / 2,
size.width(),
size.height());
}
const auto sticker = play.inner;
const auto size = play.outer;
const auto shift = play.premium
const auto shift = (play.type == Stickers::EffectType::PremiumSticker)
? int(sticker.width() * kPremiumShift)
: (size.width() / 40);
const auto inner = view->innerGeometry();
@ -346,11 +369,9 @@ QRect EmojiInteractions::computeRect(const Play &play) const {
const auto left = rightAligned
? (inner.x() + inner.width() + shift - size.width())
: (inner.x() - shift);
const auto viewTop = _itemTop(view) + inner.y();
if (viewTop < 0) {
return QRect();
}
const auto top = viewTop + (sticker.height() - size.height()) / 2;
const auto top = viewTop
+ inner.y()
+ (sticker.height() - size.height()) / 2;
return QRect(QPoint(left, top), size).translated(play.shift);
}

View File

@ -23,6 +23,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Stickers {
enum class EffectType : uint8;
} // namespace Stickers
namespace HistoryView {
class Element;
@ -60,7 +64,7 @@ private:
int frame = 0;
int framesCount = 0;
int frameRate = 0;
bool premium = false;
Stickers::EffectType type = {};
bool started = false;
bool finished = false;
};
@ -96,7 +100,7 @@ private:
QByteArray data,
QString filepath,
bool incoming,
bool premium);
Stickers::EffectType type);
void checkDelayed();
void addPendingEffect(not_null<const Element*> view);

View File

@ -707,6 +707,88 @@ auto Message::takeEffectAnimation()
return _bottomInfo.takeEffectAnimation();
}
QRect Message::effectIconGeometry() const {
const auto item = data();
const auto media = this->media();
auto g = countGeometry();
if (g.width() < 1 || isHidden()) {
return {};
}
const auto bubble = drawBubble();
const auto reactionsInBubble = _reactions && embedReactionsInBubble();
const auto mediaDisplayed = media && media->isDisplayed();
const auto keyboard = item->inlineReplyKeyboard();
auto keyboardHeight = 0;
if (keyboard) {
keyboardHeight = keyboard->naturalHeight();
g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
}
const auto fromBottomInfo = [&](QPoint bottomRight) {
const auto size = _bottomInfo.currentSize();
return _bottomInfo.effectIconGeometry().translated(
bottomRight - QPoint(size.width(), size.height()));
};
if (bubble) {
auto entry = logEntryOriginal();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto inner = g;
if (_comments) {
inner.setHeight(inner.height() - st::historyCommentsButtonHeight);
}
auto trect = inner.marginsRemoved(st::msgPadding);
const auto reactionsTop = (reactionsInBubble && !_viewButton)
? st::mediaInBubbleSkip
: 0;
const auto reactionsHeight = reactionsInBubble
? (reactionsTop + _reactions->height())
: 0;
if (_viewButton) {
const auto belowInfo = _viewButton->belowMessageInfo();
const auto infoHeight = reactionsInBubble
? (reactionsHeight + 2 * st::mediaInBubbleSkip)
: _bottomInfo.height();
const auto heightMargins = QMargins(0, 0, 0, infoHeight);
if (belowInfo) {
inner -= heightMargins;
}
trect.setHeight(trect.height() - _viewButton->height());
if (reactionsInBubble) {
trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom());
} else if (mediaDisplayed) {
trect.setHeight(trect.height() - st::mediaInBubbleSkip);
}
}
if (mediaOnBottom) {
trect.setHeight(trect.height()
+ st::msgPadding.bottom()
- viewButtonHeight());
}
if (mediaOnTop) {
trect.setY(trect.y() - st::msgPadding.top());
}
if (mediaDisplayed && mediaOnBottom && media->customInfoLayout()) {
auto mediaHeight = media->height();
auto mediaLeft = trect.x() - st::msgPadding.left();
auto mediaTop = (trect.y() + trect.height() - mediaHeight);
return fromBottomInfo(QPoint(mediaLeft, mediaTop) + media->resolveCustomInfoRightBottom());
} else {
return fromBottomInfo({
inner.left() + inner.width() - (st::msgPadding.right() - st::msgDateDelta.x()),
inner.top() + inner.height() - (st::msgPadding.bottom() - st::msgDateDelta.y()),
});
}
} else if (mediaDisplayed) {
return fromBottomInfo(g.topLeft() + media->resolveCustomInfoRightBottom());
}
return {};
}
QSize Message::performCountOptimalSize() {
const auto item = data();

View File

@ -162,6 +162,7 @@ public:
auto takeEffectAnimation()
-> std::unique_ptr<Ui::ReactionFlyAnimation> override;
QRect effectIconGeometry() const override;
QRect innerGeometry() const override;
[[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const;

View File

@ -41,6 +41,7 @@ constexpr auto kMaxSizeFixed = 512;
constexpr auto kMaxEmojiSizeFixed = 256;
constexpr auto kPremiumMultiplier = (1 + 0.245 * 2);
constexpr auto kEmojiMultiplier = 3;
constexpr auto kMessageEffectMultiplier = 2;
[[nodiscard]] QImage CacheDiceImage(
const QString &emoji,
@ -208,6 +209,10 @@ QSize Sticker::EmojiEffectSize() {
return EmojiSize() * kEmojiMultiplier;
}
QSize Sticker::MessageEffectSize() {
return EmojiSize() * kMessageEffectMultiplier;
}
QSize Sticker::EmojiSize() {
const auto side = std::min(st::maxAnimatedEmojiSize, kMaxEmojiSizeFixed);
return { side, side };

View File

@ -91,6 +91,7 @@ public:
not_null<DocumentData*> document);
[[nodiscard]] static QSize UsualPremiumEffectSize();
[[nodiscard]] static QSize EmojiEffectSize();
[[nodiscard]] static QSize MessageEffectSize();
[[nodiscard]] static QSize EmojiSize();
[[nodiscard]] static ClickHandlerPtr ShowSetHandler(
not_null<DocumentData*> document);

View File

@ -339,7 +339,7 @@ void MediaPreviewWidget::setupLottie() {
_document,
_documentMedia->videoThumbnailContent(),
QString(),
true);
Stickers::EffectType::PremiumSticker);
} else {
const auto size = currentDimensions();
_lottie = std::make_unique<Lottie::SinglePlayer>(