2021-09-15 14:49:06 +00:00
|
|
|
/*
|
|
|
|
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"
|
2022-05-19 15:05:07 +00:00
|
|
|
#include "chat_helpers/stickers_emoji_pack.h"
|
2021-09-15 14:49:06 +00:00
|
|
|
#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 {
|
|
|
|
|
2022-06-08 15:00:48 +00:00
|
|
|
constexpr auto kPremiumShift = 21. / 240;
|
2021-09-15 15:30:29 +00:00
|
|
|
constexpr auto kMaxPlays = 5;
|
|
|
|
constexpr auto kMaxPlaysWithSmallDelay = 3;
|
|
|
|
constexpr auto kSmallDelay = crl::time(200);
|
|
|
|
constexpr auto kDropDelayedAfterDelay = crl::time(2000);
|
|
|
|
|
2021-09-15 14:49:06 +00:00
|
|
|
[[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
|
|
|
|
|
2022-04-26 13:48:38 +00:00
|
|
|
EmojiInteractions::EmojiInteractions(
|
|
|
|
not_null<Main::Session*> session,
|
|
|
|
Fn<int(not_null<const Element*>)> itemTop)
|
2022-09-09 16:09:51 +00:00
|
|
|
: _session(session)
|
|
|
|
, _itemTop(std::move(itemTop)) {
|
2021-09-15 14:49:06 +00:00
|
|
|
_session->data().viewRemoved(
|
|
|
|
) | rpl::filter([=] {
|
2021-10-12 06:32:32 +00:00
|
|
|
return !_plays.empty() || !_delayed.empty();
|
2021-09-15 14:49:06 +00:00
|
|
|
}) | rpl::start_with_next([=](not_null<const Element*> view) {
|
|
|
|
_plays.erase(ranges::remove(_plays, view, &Play::view), end(_plays));
|
2021-10-12 06:32:32 +00:00
|
|
|
_delayed.erase(
|
|
|
|
ranges::remove(_delayed, view, &Delayed::view),
|
|
|
|
end(_delayed));
|
2021-09-15 14:49:06 +00:00
|
|
|
}, _lifetime);
|
|
|
|
}
|
|
|
|
|
2022-09-09 16:09:51 +00:00
|
|
|
EmojiInteractions::~EmojiInteractions() = default;
|
2021-09-15 14:49:06 +00:00
|
|
|
|
|
|
|
void EmojiInteractions::play(
|
|
|
|
ChatHelpers::EmojiInteractionPlayRequest request,
|
|
|
|
not_null<Element*> view) {
|
2021-09-19 12:12:20 +00:00
|
|
|
if (!view->media()) {
|
|
|
|
// Large emoji may be disabled.
|
|
|
|
return;
|
|
|
|
} else if (_plays.empty()) {
|
2021-09-16 16:30:41 +00:00
|
|
|
play(
|
2021-09-19 07:31:41 +00:00
|
|
|
std::move(request.emoticon),
|
2021-09-16 16:30:41 +00:00
|
|
|
view,
|
|
|
|
std::move(request.media),
|
|
|
|
request.incoming);
|
2021-09-15 15:30:29 +00:00
|
|
|
} else {
|
2021-09-16 16:30:41 +00:00
|
|
|
const auto now = crl::now();
|
|
|
|
_delayed.push_back({
|
2021-09-19 07:31:41 +00:00
|
|
|
request.emoticon,
|
2021-09-16 16:30:41 +00:00
|
|
|
view,
|
|
|
|
std::move(request.media),
|
|
|
|
now,
|
|
|
|
request.incoming,
|
|
|
|
});
|
2021-09-15 15:30:29 +00:00
|
|
|
checkDelayed();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-26 08:30:50 +00:00
|
|
|
bool EmojiInteractions::playPremiumEffect(
|
2022-04-26 16:31:10 +00:00
|
|
|
not_null<const Element*> view,
|
|
|
|
Element *replacing) {
|
2022-05-20 14:57:01 +00:00
|
|
|
const auto already = ranges::contains(_plays, view, &Play::view);
|
2022-04-26 16:31:10 +00:00
|
|
|
if (replacing) {
|
|
|
|
const auto i = ranges::find(_plays, replacing, &Play::view);
|
|
|
|
if (i != end(_plays)) {
|
2022-05-20 14:57:01 +00:00
|
|
|
if (already) {
|
|
|
|
_plays.erase(i);
|
|
|
|
} else {
|
|
|
|
i->view = view;
|
|
|
|
}
|
2022-05-26 08:30:50 +00:00
|
|
|
return true;
|
2022-04-26 16:31:10 +00:00
|
|
|
}
|
2022-05-20 14:57:01 +00:00
|
|
|
} else if (already) {
|
2022-05-26 08:30:50 +00:00
|
|
|
return false;
|
2022-04-26 16:31:10 +00:00
|
|
|
}
|
2022-04-22 16:23:47 +00:00
|
|
|
if (const auto media = view->media()) {
|
|
|
|
if (const auto document = media->getDocument()) {
|
|
|
|
if (document->isPremiumSticker()) {
|
|
|
|
play(
|
|
|
|
QString(),
|
|
|
|
view,
|
|
|
|
document,
|
|
|
|
document->createMediaView()->videoThumbnailContent(),
|
|
|
|
QString(),
|
|
|
|
false,
|
|
|
|
true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-26 08:30:50 +00:00
|
|
|
return true;
|
2022-04-22 16:23:47 +00:00
|
|
|
}
|
|
|
|
|
2022-04-26 16:31:10 +00:00
|
|
|
void EmojiInteractions::cancelPremiumEffect(not_null<const Element*> view) {
|
|
|
|
_plays.erase(ranges::remove_if(_plays, [&](const Play &play) {
|
|
|
|
if (play.view != view) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}), end(_plays));
|
|
|
|
}
|
|
|
|
|
2021-09-15 15:30:29 +00:00
|
|
|
void EmojiInteractions::play(
|
2021-09-19 07:31:41 +00:00
|
|
|
QString emoticon,
|
2022-04-22 16:23:47 +00:00
|
|
|
not_null<const Element*> view,
|
2021-09-16 16:30:41 +00:00
|
|
|
std::shared_ptr<Data::DocumentMedia> media,
|
|
|
|
bool incoming) {
|
2022-04-22 16:23:47 +00:00
|
|
|
play(
|
|
|
|
std::move(emoticon),
|
|
|
|
view,
|
|
|
|
media->owner(),
|
|
|
|
media->bytes(),
|
|
|
|
media->owner()->filepath(),
|
|
|
|
incoming,
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmojiInteractions::play(
|
|
|
|
QString emoticon,
|
|
|
|
not_null<const Element*> view,
|
|
|
|
not_null<DocumentData*> document,
|
|
|
|
QByteArray data,
|
|
|
|
QString filepath,
|
|
|
|
bool incoming,
|
|
|
|
bool premium) {
|
2022-05-02 09:58:12 +00:00
|
|
|
const auto top = _itemTop(view);
|
2021-09-15 14:49:06 +00:00
|
|
|
const auto bottom = top + view->height();
|
|
|
|
if (_visibleTop >= bottom
|
|
|
|
|| _visibleBottom <= top
|
2022-04-22 16:23:47 +00:00
|
|
|
|| _visibleTop == _visibleBottom
|
|
|
|
|| (data.isEmpty() && filepath.isEmpty())) {
|
2021-09-15 14:49:06 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-09-17 16:23:52 +00:00
|
|
|
|
2022-05-19 15:05:07 +00:00
|
|
|
auto lottie = document->session().emojiStickersPack().effectPlayer(
|
|
|
|
document,
|
|
|
|
data,
|
|
|
|
filepath,
|
|
|
|
premium);
|
2021-09-17 16:23:52 +00:00
|
|
|
|
2022-05-19 15:05:07 +00:00
|
|
|
const auto inner = premium
|
|
|
|
? HistoryView::Sticker::Size(document)
|
|
|
|
: HistoryView::Sticker::EmojiSize();
|
|
|
|
const auto shift = premium ? QPoint() : GenerateRandomShift(inner);
|
2022-04-26 16:31:10 +00:00
|
|
|
const auto raw = lottie.get();
|
2021-09-15 14:49:06 +00:00
|
|
|
lottie->updates(
|
|
|
|
) | rpl::start_with_next([=](Lottie::Update update) {
|
|
|
|
v::match(update.data, [&](const Lottie::Information &information) {
|
|
|
|
}, [&](const Lottie::DisplayFrameRequest &request) {
|
2022-04-26 16:31:10 +00:00
|
|
|
const auto i = ranges::find(_plays, raw, [](const Play &p) {
|
|
|
|
return p.lottie.get();
|
|
|
|
});
|
2022-05-19 15:05:07 +00:00
|
|
|
const auto rect = computeRect(*i).translated(shift);
|
2021-09-15 14:49:06 +00:00
|
|
|
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,
|
2022-05-19 15:05:07 +00:00
|
|
|
.inner = inner,
|
|
|
|
.outer = (premium
|
|
|
|
? HistoryView::Sticker::PremiumEffectSize(document)
|
|
|
|
: HistoryView::Sticker::EmojiEffectSize()),
|
2022-04-22 16:23:47 +00:00
|
|
|
.premium = premium,
|
2021-09-15 14:49:06 +00:00
|
|
|
});
|
2021-09-16 16:30:41 +00:00
|
|
|
if (incoming) {
|
2021-09-19 07:31:41 +00:00
|
|
|
_playStarted.fire(std::move(emoticon));
|
2021-09-16 16:30:41 +00:00
|
|
|
}
|
2021-09-16 11:20:25 +00:00
|
|
|
if (const auto media = view->media()) {
|
2022-04-22 16:23:47 +00:00
|
|
|
if (!premium) {
|
|
|
|
media->stickerClearLoopPlayed();
|
|
|
|
}
|
2021-09-16 11:20:25 +00:00
|
|
|
}
|
2021-09-15 14:49:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void EmojiInteractions::visibleAreaUpdated(
|
|
|
|
int visibleTop,
|
|
|
|
int visibleBottom) {
|
|
|
|
_visibleTop = visibleTop;
|
|
|
|
_visibleBottom = visibleBottom;
|
|
|
|
}
|
|
|
|
|
2022-05-19 15:05:07 +00:00
|
|
|
QRect EmojiInteractions::computeRect(const Play &play) const {
|
|
|
|
const auto view = play.view;
|
|
|
|
const auto sticker = play.inner;
|
|
|
|
const auto size = play.outer;
|
|
|
|
const auto shift = play.premium
|
|
|
|
? int(sticker.width() * kPremiumShift)
|
2022-05-18 10:18:17 +00:00
|
|
|
: (size.width() / 40);
|
2022-05-02 09:58:12 +00:00
|
|
|
const auto inner = view->innerGeometry();
|
2024-01-01 19:33:19 +00:00
|
|
|
const auto rightAligned = view->hasRightLayout();
|
2021-09-15 14:49:06 +00:00
|
|
|
const auto left = rightAligned
|
2022-05-02 09:58:12 +00:00
|
|
|
? (inner.x() + inner.width() + shift - size.width())
|
|
|
|
: (inner.x() - shift);
|
|
|
|
const auto viewTop = _itemTop(view) + inner.y();
|
2022-04-26 13:48:38 +00:00
|
|
|
if (viewTop < 0) {
|
|
|
|
return QRect();
|
|
|
|
}
|
2022-04-22 16:23:47 +00:00
|
|
|
const auto top = viewTop + (sticker.height() - size.height()) / 2;
|
2022-05-19 15:05:07 +00:00
|
|
|
return QRect(QPoint(left, top), size).translated(play.shift);
|
2021-09-15 14:49:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void EmojiInteractions::paint(QPainter &p) {
|
|
|
|
const auto factor = style::DevicePixelRatio();
|
|
|
|
for (auto &play : _plays) {
|
|
|
|
if (!play.lottie->ready()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto request = Lottie::FrameRequest();
|
2022-05-19 15:05:07 +00:00
|
|
|
request.box = play.outer * factor;
|
2024-01-01 19:33:19 +00:00
|
|
|
const auto rightAligned = play.view->hasRightLayout();
|
2021-09-15 14:49:06 +00:00
|
|
|
if (!rightAligned) {
|
2021-09-16 09:48:30 +00:00
|
|
|
request.mirrorHorizontal = true;
|
2021-09-15 14:49:06 +00:00
|
|
|
}
|
|
|
|
const auto frame = play.lottie->frameInfo(request);
|
2021-09-15 15:30:29 +00:00
|
|
|
play.frame = frame.index;
|
|
|
|
if (!play.framesCount) {
|
|
|
|
const auto &information = play.lottie->information();
|
|
|
|
play.framesCount = information.framesCount;
|
|
|
|
play.frameRate = information.frameRate;
|
|
|
|
}
|
2022-05-19 15:05:07 +00:00
|
|
|
const auto rect = computeRect(play);
|
2022-04-26 12:49:39 +00:00
|
|
|
if (play.started && !play.frame) {
|
2021-09-15 14:49:06 +00:00
|
|
|
play.finished = true;
|
2022-04-26 12:49:39 +00:00
|
|
|
_updateRequests.fire_copy(rect);
|
|
|
|
continue;
|
|
|
|
} else if (play.frame > 0) {
|
|
|
|
play.started = true;
|
2021-09-15 14:49:06 +00:00
|
|
|
}
|
|
|
|
p.drawImage(
|
2022-04-26 12:49:39 +00:00
|
|
|
QRect(rect.topLeft(), frame.image.size() / factor),
|
2021-09-15 14:49:06 +00:00
|
|
|
frame.image);
|
2022-09-09 16:09:51 +00:00
|
|
|
play.lottie->markFrameShown();
|
2021-09-15 14:49:06 +00:00
|
|
|
}
|
2022-04-26 16:31:10 +00:00
|
|
|
_plays.erase(ranges::remove_if(_plays, [](const Play &play) {
|
|
|
|
if (!play.finished) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}), end(_plays));
|
2021-09-15 15:30:29 +00:00
|
|
|
checkDelayed();
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmojiInteractions::checkDelayed() {
|
|
|
|
if (_delayed.empty() || _plays.size() >= kMaxPlays) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto withTooLittleDelay = false;
|
|
|
|
auto withHalfPlayed = false;
|
|
|
|
for (const auto &play : _plays) {
|
|
|
|
if (!play.framesCount
|
|
|
|
|| !play.frameRate
|
|
|
|
|| !play.frame
|
|
|
|
|| (play.frame * crl::time(1000)
|
|
|
|
< kSmallDelay * play.frameRate)) {
|
|
|
|
withTooLittleDelay = true;
|
|
|
|
break;
|
|
|
|
} else if (play.frame * 2 > play.framesCount) {
|
|
|
|
withHalfPlayed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (withTooLittleDelay) {
|
|
|
|
return;
|
|
|
|
} else if (_plays.size() >= kMaxPlaysWithSmallDelay && !withHalfPlayed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto now = crl::now();
|
|
|
|
const auto i = ranges::find_if(_delayed, [&](const Delayed &delayed) {
|
|
|
|
return (delayed.shouldHaveStartedAt + kDropDelayedAfterDelay > now);
|
|
|
|
});
|
|
|
|
if (i == end(_delayed)) {
|
|
|
|
_delayed.clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto good = std::move(*i);
|
|
|
|
_delayed.erase(begin(_delayed), i + 1);
|
2021-09-19 07:31:41 +00:00
|
|
|
play(
|
|
|
|
std::move(good.emoticon),
|
|
|
|
good.view,
|
|
|
|
std::move(good.media),
|
|
|
|
good.incoming);
|
2021-09-15 14:49:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<QRect> EmojiInteractions::updateRequests() const {
|
|
|
|
return _updateRequests.events();
|
|
|
|
}
|
|
|
|
|
2021-09-16 16:30:41 +00:00
|
|
|
rpl::producer<QString> EmojiInteractions::playStarted() const {
|
|
|
|
return _playStarted.events();
|
|
|
|
}
|
|
|
|
|
2021-09-15 14:49:06 +00:00
|
|
|
} // namespace HistoryView
|