mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Start emoji interactions playback.
This commit is contained in:
parent
b6fafdd8f7
commit
15f83892a1
9 changed files with 263 additions and 14 deletions
|
@ -100,7 +100,7 @@ void EmojiInteractions::start(not_null<const HistoryView::Element*> view) {
|
|||
auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
||||
auto nearest = kTimeNever;
|
||||
auto waitingForDownload = false;
|
||||
for (auto &[id, animations] : _animations) {
|
||||
for (auto &[item, animations] : _animations) {
|
||||
auto lastStartedAt = crl::time();
|
||||
for (auto &animation : animations) {
|
||||
if (animation.startedAt) {
|
||||
|
@ -111,11 +111,7 @@ auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
|||
break;
|
||||
} else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) {
|
||||
animation.startedAt = now;
|
||||
|
||||
// #TODO interactions
|
||||
//const auto sticker = std::make_unique<HistoryView::Sticker>(
|
||||
// view,
|
||||
// document);
|
||||
_playRequests.fire({ item, animation.media });
|
||||
break;
|
||||
} else {
|
||||
nearest = std::min(nearest, lastStartedAt + kMinDelay);
|
||||
|
@ -174,8 +170,8 @@ void EmojiInteractions::sendAccumulated(
|
|||
auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
|
||||
auto nearest = kTimeNever;
|
||||
for (auto i = begin(_animations); i != end(_animations);) {
|
||||
auto &[id, animations] = *i;
|
||||
sendAccumulated(now, id, animations);
|
||||
auto &[item, animations] = *i;
|
||||
sendAccumulated(now, item, animations);
|
||||
if (animations.empty()) {
|
||||
i = _animations.erase(i);
|
||||
continue;
|
||||
|
|
|
@ -26,12 +26,22 @@ class Element;
|
|||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct EmojiInteractionPlayRequest {
|
||||
not_null<HistoryItem*> item;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
};
|
||||
|
||||
class EmojiInteractions final {
|
||||
public:
|
||||
explicit EmojiInteractions(not_null<Main::Session*> session);
|
||||
~EmojiInteractions();
|
||||
|
||||
using PlayRequest = EmojiInteractionPlayRequest;
|
||||
|
||||
void start(not_null<const HistoryView::Element*> view);
|
||||
[[nodiscard]] rpl::producer<PlayRequest> playRequests() const {
|
||||
return _playRequests.events();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Animation {
|
||||
|
@ -63,6 +73,7 @@ private:
|
|||
not_null<HistoryItem*>,
|
||||
std::vector<Animation>> _animations;
|
||||
base::Timer _checkTimer;
|
||||
rpl::event_stream<PlayRequest> _playRequests;
|
||||
|
||||
bool _waitingForDownload = false;
|
||||
rpl::lifetime _downloadCheckLifetime;
|
||||
|
|
|
@ -60,11 +60,12 @@ auto LottieFromDocument(
|
|||
Method &&method,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
uint8 keyShift,
|
||||
QSize box) {
|
||||
QSize box,
|
||||
int cacheAreaLimit) {
|
||||
const auto document = media->owner();
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
if (box.width() * box.height() > kDontCacheLottieAfterArea) {
|
||||
if (box.width() * box.height() > cacheAreaLimit) {
|
||||
// Don't use frame caching for large stickers.
|
||||
return method(
|
||||
Lottie::ReadContent(data, filepath),
|
||||
|
@ -113,9 +114,12 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
|||
replacements,
|
||||
std::move(renderer));
|
||||
};
|
||||
const auto limit = (sizeTag == StickerLottieSize::EmojiInteraction)
|
||||
? (3 * kDontCacheLottieAfterArea)
|
||||
: kDontCacheLottieAfterArea;
|
||||
const auto tag = replacements ? replacements->tag : uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box, limit);
|
||||
}
|
||||
|
||||
not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
|
@ -126,7 +130,8 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
|||
const auto method = [&](auto &&...args) {
|
||||
return player->append(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box);
|
||||
const auto limit = kDontCacheLottieAfterArea;
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box, limit);
|
||||
}
|
||||
|
||||
bool HasLottieThumbnail(
|
||||
|
|
|
@ -44,6 +44,7 @@ enum class StickerLottieSize : uchar {
|
|||
StickersFooter,
|
||||
SetsListThumbnail,
|
||||
InlineResults,
|
||||
EmojiInteraction,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_service_message.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
|
@ -160,6 +161,8 @@ HistoryInner::HistoryInner(
|
|||
, _controller(controller)
|
||||
, _peer(history->peer)
|
||||
, _history(history)
|
||||
, _emojiInteractions(std::make_unique<HistoryView::EmojiInteractions>(
|
||||
&controller->session()))
|
||||
, _migrated(history->migrateFrom())
|
||||
, _pathGradient(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
|
@ -195,6 +198,21 @@ HistoryInner::HistoryInner(
|
|||
update();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
using PlayRequest = ChatHelpers::EmojiInteractionPlayRequest;
|
||||
_controller->emojiInteractions().playRequests(
|
||||
) | rpl::filter([=](const PlayRequest &request) {
|
||||
return (request.item->history() == _history);
|
||||
}) | rpl::start_with_next([=](const PlayRequest &request) {
|
||||
if (const auto view = request.item->mainView()) {
|
||||
_emojiInteractions->play(request, view);
|
||||
}
|
||||
}, lifetime());
|
||||
_emojiInteractions->updateRequests(
|
||||
) | rpl::start_with_next([=](QRect rect) {
|
||||
update(rect.translated(0, _historyPaddingTop));
|
||||
}, lifetime());
|
||||
|
||||
session().data().itemRemoved(
|
||||
) | rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
|
@ -834,6 +852,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
p.setOpacity(1.);
|
||||
p.translate(0, _historyPaddingTop);
|
||||
_emojiInteractions->paint(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2357,6 +2378,10 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
|
|||
const auto till = _visibleAreaBottom + pages * visibleAreaHeight;
|
||||
session().data().unloadHeavyViewParts(ElementDelegate(), from, till);
|
||||
checkHistoryActivation();
|
||||
|
||||
_emojiInteractions->visibleAreaUpdated(
|
||||
_visibleAreaTop - _historyPaddingTop,
|
||||
_visibleAreaBottom - _historyPaddingTop);
|
||||
}
|
||||
|
||||
bool HistoryInner::displayScrollDate() const {
|
||||
|
@ -2454,7 +2479,12 @@ void HistoryInner::updateSize() {
|
|||
_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
|
||||
}
|
||||
|
||||
_historyPaddingTop = newHistoryPaddingTop;
|
||||
if (_historyPaddingTop != newHistoryPaddingTop) {
|
||||
_historyPaddingTop = newHistoryPaddingTop;
|
||||
_emojiInteractions->visibleAreaUpdated(
|
||||
_visibleAreaTop - _historyPaddingTop,
|
||||
_visibleAreaBottom - _historyPaddingTop);
|
||||
}
|
||||
|
||||
int newHeight = _historyPaddingTop + itemsHeight + st::historyPaddingBottom;
|
||||
if (width() != _scroll->width() || height() != newHeight) {
|
||||
|
|
|
@ -21,6 +21,7 @@ class CloudImageView;
|
|||
|
||||
namespace HistoryView {
|
||||
class ElementDelegate;
|
||||
class EmojiInteractions;
|
||||
struct TextState;
|
||||
struct StateRequest;
|
||||
enum class CursorState : char;
|
||||
|
@ -354,6 +355,7 @@ private:
|
|||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<History*> _history;
|
||||
const std::unique_ptr<HistoryView::EmojiInteractions> _emojiInteractions;
|
||||
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||
|
||||
History *_migrated = nullptr;
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/history.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "base/random.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSizeMultiplier = 3;
|
||||
|
||||
[[nodiscard]] QPoint GenerateRandomShift(QSize emoji) {
|
||||
// Random shift in [-0.08 ... 0.08] of animated emoji size.
|
||||
const auto maxShift = emoji * 2 / 25;
|
||||
return {
|
||||
base::RandomIndex(maxShift.width() * 2 + 1) - maxShift.width(),
|
||||
base::RandomIndex(maxShift.height() * 2 + 1) - maxShift.height(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
_session->data().viewRemoved(
|
||||
) | rpl::filter([=] {
|
||||
return !_plays.empty();
|
||||
}) | rpl::start_with_next([=](not_null<const Element*> view) {
|
||||
_plays.erase(ranges::remove(_plays, view, &Play::view), end(_plays));
|
||||
}, _lifetime);
|
||||
|
||||
_emojiSize = Sticker::EmojiSize();
|
||||
}
|
||||
|
||||
EmojiInteractions::~EmojiInteractions() = default;
|
||||
|
||||
void EmojiInteractions::play(
|
||||
ChatHelpers::EmojiInteractionPlayRequest request,
|
||||
not_null<Element*> view) {
|
||||
const auto top = view->block()->y() + view->y();
|
||||
const auto bottom = top + view->height();
|
||||
if (_visibleTop >= bottom
|
||||
|| _visibleBottom <= top
|
||||
|| _visibleTop == _visibleBottom) {
|
||||
return;
|
||||
}
|
||||
auto lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
request.media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::EmojiInteraction,
|
||||
_emojiSize * kSizeMultiplier * style::DevicePixelRatio(),
|
||||
Lottie::Quality::High);
|
||||
const auto shift = GenerateRandomShift(_emojiSize);
|
||||
lottie->updates(
|
||||
) | rpl::start_with_next([=](Lottie::Update update) {
|
||||
v::match(update.data, [&](const Lottie::Information &information) {
|
||||
}, [&](const Lottie::DisplayFrameRequest &request) {
|
||||
const auto rect = computeRect(view).translated(shift);
|
||||
if (rect.y() + rect.height() >= _visibleTop
|
||||
&& rect.y() <= _visibleBottom) {
|
||||
_updateRequests.fire_copy(rect);
|
||||
}
|
||||
});
|
||||
}, lottie->lifetime());
|
||||
_plays.push_back({
|
||||
.view = view,
|
||||
.lottie = std::move(lottie),
|
||||
.shift = shift,
|
||||
});
|
||||
}
|
||||
|
||||
void EmojiInteractions::visibleAreaUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
}
|
||||
|
||||
QRect EmojiInteractions::computeRect(not_null<Element*> view) const {
|
||||
const auto fullWidth = view->width();
|
||||
const auto shift = (_emojiSize.width() * kSizeMultiplier) / 40;
|
||||
const auto skip = (view->hasFromPhoto() ? st::msgPhotoSkip : 0)
|
||||
+ st::msgMargin.left();
|
||||
const auto rightAligned = view->hasOutLayout()
|
||||
&& !view->delegate()->elementIsChatWide();
|
||||
const auto left = rightAligned
|
||||
? (fullWidth - skip + shift - _emojiSize.width() * kSizeMultiplier)
|
||||
: (skip - shift);
|
||||
const auto viewTop = view->block()->y() + view->y() + view->marginTop();
|
||||
const auto top = viewTop - _emojiSize.height();
|
||||
return QRect(QPoint(left, top), _emojiSize * kSizeMultiplier);
|
||||
}
|
||||
|
||||
void EmojiInteractions::paint(QPainter &p) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
for (auto &play : _plays) {
|
||||
if (!play.lottie->ready()) {
|
||||
continue;
|
||||
}
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _emojiSize * kSizeMultiplier * factor;
|
||||
const auto rightAligned = play.view->hasOutLayout()
|
||||
&& !play.view->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
// #TODO interactions mirror
|
||||
request.colored = st::msgStickerOverlay->c;
|
||||
}
|
||||
const auto frame = play.lottie->frameInfo(request);
|
||||
if (frame.index + 1 == play.lottie->information().framesCount) {
|
||||
play.finished = true;
|
||||
}
|
||||
const auto rect = computeRect(play.view);
|
||||
p.drawImage(
|
||||
QRect(rect.topLeft() + play.shift, frame.image.size() / factor),
|
||||
frame.image);
|
||||
play.lottie->markFrameShown();
|
||||
}
|
||||
_plays.erase(ranges::remove(_plays, true, &Play::finished), end(_plays));
|
||||
}
|
||||
|
||||
rpl::producer<QRect> EmojiInteractions::updateRequests() const {
|
||||
return _updateRequests.events();
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ChatHelpers {
|
||||
struct EmojiInteractionPlayRequest;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Lottie {
|
||||
class SinglePlayer;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class Element;
|
||||
|
||||
class EmojiInteractions final {
|
||||
public:
|
||||
explicit EmojiInteractions(not_null<Main::Session*> session);
|
||||
~EmojiInteractions();
|
||||
|
||||
void play(
|
||||
ChatHelpers::EmojiInteractionPlayRequest request,
|
||||
not_null<Element*> view);
|
||||
void visibleAreaUpdated(int visibleTop, int visibleBottom);
|
||||
|
||||
void paint(QPainter &p);
|
||||
[[nodiscard]] rpl::producer<QRect> updateRequests() const;
|
||||
|
||||
private:
|
||||
struct Play {
|
||||
not_null<Element*> view;
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
QPoint shift;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] QRect computeRect(not_null<Element*> view) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
QSize _emojiSize;
|
||||
|
||||
std::vector<Play> _plays;
|
||||
rpl::event_stream<QRect> _updateRequests;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
|
@ -1 +1 @@
|
|||
Subproject commit 25294ef8da339f23e87d3cf0dbe8f29491712cfc
|
||||
Subproject commit a8ffe6cb920ca400de03127cbf5bc5b5ed32aff4
|
Loading…
Add table
Reference in a new issue