Support webm stickers in StickerSetBox.

This commit is contained in:
John Preston 2022-01-25 14:25:12 +03:00
parent d96a8d028a
commit b4eb25de58
2 changed files with 138 additions and 41 deletions

View File

@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_multi_player.h" #include "lottie/lottie_multi_player.h"
#include "lottie/lottie_animation.h" #include "lottie/lottie_animation.h"
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "media/clip/media_clip_reader.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -51,6 +52,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kStickersPanelPerRow = 5; constexpr auto kStickersPanelPerRow = 5;
constexpr auto kMinRepaintDelay = crl::time(33);
constexpr auto kMinAfterScrollDelay = crl::time(33);
using Data::StickersSet; using Data::StickersSet;
using Data::StickersPack; using Data::StickersPack;
@ -99,7 +102,8 @@ private:
struct Element { struct Element {
not_null<DocumentData*> document; not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia; std::shared_ptr<Data::DocumentMedia> documentMedia;
Lottie::Animation *animated = nullptr; Lottie::Animation *lottie = nullptr;
Media::Clip::ReaderPointer webm;
Ui::Animations::Simple overAnimation; Ui::Animations::Simple overAnimation;
}; };
@ -107,8 +111,18 @@ private:
QSize boundingBoxSize() const; QSize boundingBoxSize() const;
void paintSticker(Painter &p, int index, QPoint position) const; void paintSticker(
Painter &p,
int index,
QPoint position,
bool paused,
crl::time now) const;
void setupLottie(int index); void setupLottie(int index);
void setupWebm(int index);
void clipCallback(
Media::Clip::Notification notification,
not_null<DocumentData*> document,
int index);
void updateSelected(); void updateSelected();
void setSelected(int selected); void setSelected(int selected);
@ -123,6 +137,8 @@ private:
not_null<Lottie::MultiPlayer*> getLottiePlayer(); not_null<Lottie::MultiPlayer*> getLottiePlayer();
void showPreview(); void showPreview();
void updateItems();
void repaintItems(crl::time now = 0);
not_null<Window::SessionController*> _controller; not_null<Window::SessionController*> _controller;
MTP::Sender _api; MTP::Sender _api;
@ -142,6 +158,12 @@ private:
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
int _visibleTop = 0;
int _visibleBottom = 0;
crl::time _lastScrolledAt = 0;
crl::time _lastUpdatedAt = 0;
base::Timer _updateItemsTimer;
StickerSetIdentifier _input; StickerSetIdentifier _input;
mtpRequestId _installRequest = 0; mtpRequestId _installRequest = 0;
@ -370,9 +392,12 @@ StickerSetBox::Inner::Inner(
, _pathGradient(std::make_unique<Ui::PathShiftGradient>( , _pathGradient(std::make_unique<Ui::PathShiftGradient>(
st::windowBgRipple, st::windowBgRipple,
st::windowBgOver, st::windowBgOver,
[=] { update(); })) [=] { repaintItems(); }))
, _updateItemsTimer([=] { updateItems(); })
, _input(set) , _input(set)
, _previewTimer([=] { showPreview(); }) { , _previewTimer([=] { showPreview(); }) {
setAttribute(Qt::WA_OpaquePaintEvent);
_api.request(MTPmessages_GetStickerSet( _api.request(MTPmessages_GetStickerSet(
Data::InputStickerSet(_input), Data::InputStickerSet(_input),
MTP_int(0) // hash MTP_int(0) // hash
@ -387,7 +412,7 @@ StickerSetBox::Inner::Inner(
_controller->session().downloaderTaskFinished( _controller->session().downloaderTaskFinished(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
update(); updateItems();
}, lifetime()); }, lifetime());
setMouseTracking(true); setMouseTracking(true);
@ -735,7 +760,7 @@ not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
Lottie::MakeFrameRenderer()); Lottie::MakeFrameRenderer());
_lottiePlayer->updates( _lottiePlayer->updates(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
update(); updateItems();
}, lifetime()); }, lifetime());
} }
return _lottiePlayer.get(); return _lottiePlayer.get();
@ -756,6 +781,7 @@ int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
void StickerSetBox::Inner::paintEvent(QPaintEvent *e) { void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
p.fillRect(e->rect(), st::boxBg);
if (_elements.empty()) { if (_elements.empty()) {
return; return;
} }
@ -764,6 +790,9 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
_pathGradient->startFrame(0, width(), width() / 2); _pathGradient->startFrame(0, width(), width() / 2);
const auto now = crl::now();
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
for (int32 i = from; i < to; ++i) { for (int32 i = from; i < to; ++i) {
for (int32 j = 0; j < kStickersPanelPerRow; ++j) { for (int32 j = 0; j < kStickersPanelPerRow; ++j) {
int32 index = i * kStickersPanelPerRow + j; int32 index = i * kStickersPanelPerRow + j;
@ -771,16 +800,12 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
break; break;
} }
const auto pos = QPoint(st::stickersPadding.left() + j * st::stickersSize.width(), st::stickersPadding.top() + i * st::stickersSize.height()); const auto pos = QPoint(st::stickersPadding.left() + j * st::stickersSize.width(), st::stickersPadding.top() + i * st::stickersSize.height());
paintSticker(p, index, pos); paintSticker(p, index, pos, paused, now);
} }
} }
if (_lottiePlayer) { if (_lottiePlayer && !paused) {
const auto paused = _controller->isGifPausedAtLeastFor( _lottiePlayer->markFrameShown();
Window::GifPauseReason::Layer);
if (!paused) {
_lottiePlayer->markFrameShown();
}
} }
} }
@ -793,6 +818,12 @@ QSize StickerSetBox::Inner::boundingBoxSize() const {
void StickerSetBox::Inner::visibleTopBottomUpdated( void StickerSetBox::Inner::visibleTopBottomUpdated(
int visibleTop, int visibleTop,
int visibleBottom) { int visibleBottom) {
if (_visibleTop != visibleTop || _visibleBottom != visibleBottom) {
_visibleTop = visibleTop;
_visibleBottom = visibleBottom;
_lastScrolledAt = crl::now();
update();
}
const auto pauseInRows = [&](int fromRow, int tillRow) { const auto pauseInRows = [&](int fromRow, int tillRow) {
Expects(fromRow <= tillRow); Expects(fromRow <= tillRow);
@ -802,8 +833,10 @@ void StickerSetBox::Inner::visibleTopBottomUpdated(
if (index >= _elements.size()) { if (index >= _elements.size()) {
break; break;
} }
if (const auto animated = _elements[index].animated) { if (const auto lottie = _elements[index].lottie) {
_lottiePlayer->pause(animated); _lottiePlayer->pause(lottie);
} else if (auto &webm = _elements[index].webm) {
webm = nullptr;
} }
} }
} }
@ -834,17 +867,63 @@ void StickerSetBox::Inner::visibleTopBottomUpdated(
void StickerSetBox::Inner::setupLottie(int index) { void StickerSetBox::Inner::setupLottie(int index) {
auto &element = _elements[index]; auto &element = _elements[index];
element.animated = ChatHelpers::LottieAnimationFromDocument( element.lottie = ChatHelpers::LottieAnimationFromDocument(
getLottiePlayer(), getLottiePlayer(),
element.documentMedia.get(), element.documentMedia.get(),
ChatHelpers::StickerLottieSize::StickerSet, ChatHelpers::StickerLottieSize::StickerSet,
boundingBoxSize() * cIntRetinaFactor()); boundingBoxSize() * cIntRetinaFactor());
} }
void StickerSetBox::Inner::setupWebm(int index) {
auto &element = _elements[index];
const auto document = element.document;
auto callback = [=](Media::Clip::Notification notification) {
clipCallback(notification, document, index);
};
element.webm = Media::Clip::MakeReader(
element.documentMedia->owner()->location(),
element.documentMedia->bytes(),
std::move(callback));
}
void StickerSetBox::Inner::clipCallback(
Media::Clip::Notification notification,
not_null<DocumentData*> document,
int index) {
const auto i = (index < _elements.size()
&& _elements[index].document == document)
? (_elements.begin() + index)
: ranges::find(_elements, document, &Element::document);
if (i == end(_elements)) {
return;
}
using namespace Media::Clip;
switch (notification) {
case Notification::Reinit: {
auto &webm = i->webm;
if (webm->state() == State::Error) {
webm.setBad();
} else if (webm->ready() && !webm->started()) {
const auto size = ChatHelpers::ComputeStickerSize(
i->document,
boundingBoxSize());
webm->start({ .frame = size, .keepAlpha = true });
}
} break;
case Notification::Repaint: break;
}
updateItems();
}
void StickerSetBox::Inner::paintSticker( void StickerSetBox::Inner::paintSticker(
Painter &p, Painter &p,
int index, int index,
QPoint position) const { QPoint position,
bool paused,
crl::time now) const {
if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) { if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) {
p.setOpacity(over); p.setOpacity(over);
auto tl = position; auto tl = position;
@ -856,47 +935,46 @@ void StickerSetBox::Inner::paintSticker(
const auto &element = _elements[index]; const auto &element = _elements[index];
const auto document = element.document; const auto document = element.document;
const auto &media = element.documentMedia; const auto &media = element.documentMedia;
const auto sticker = document->sticker();
media->checkStickerSmall(); media->checkStickerSmall();
const auto isLottie = document->sticker()->isLottie(); if (media->loaded()) {
if (isLottie if (sticker->isLottie() && !element.lottie) {
&& !element.animated const_cast<Inner*>(this)->setupLottie(index);
&& media->loaded()) { } else if (sticker->isWebm() && !element.webm) {
const_cast<Inner*>(this)->setupLottie(index); const_cast<Inner*>(this)->setupWebm(index);
}
} }
auto w = 1; const auto size = ChatHelpers::ComputeStickerSize(
auto h = 1; document,
if (isLottie && !document->dimensions.isEmpty()) { boundingBoxSize());
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() }; const auto ppos = position + QPoint(
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor(); (st::stickersSize.width() - size.width()) / 2,
w = std::max(size.width(), 1); (st::stickersSize.height() - size.height()) / 2);
h = std::max(size.height(), 1);
} else {
auto coef = qMin((st::stickersSize.width() - st::roundRadiusSmall * 2) / float64(document->dimensions.width()), (st::stickersSize.height() - st::roundRadiusSmall * 2) / float64(document->dimensions.height()));
if (coef > 1) coef = 1;
w = std::max(qRound(coef * document->dimensions.width()), 1);
h = std::max(qRound(coef * document->dimensions.height()), 1);
}
QPoint ppos = position + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2);
if (element.animated && element.animated->ready()) { if (element.lottie && element.lottie->ready()) {
const auto frame = element.animated->frame(); const auto frame = element.lottie->frame();
p.drawImage( p.drawImage(
QRect(ppos, frame.size() / cIntRetinaFactor()), QRect(ppos, frame.size() / cIntRetinaFactor()),
frame); frame);
_lottiePlayer->unpause(element.animated); _lottiePlayer->unpause(element.lottie);
} else if (element.webm && element.webm->started()) {
p.drawPixmap(ppos, element.webm->current({
.frame = size,
.keepAlpha = true,
}, paused ? 0 : now));
} else if (const auto image = media->getStickerSmall()) { } else if (const auto image = media->getStickerSmall()) {
p.drawPixmapLeft( p.drawPixmapLeft(
ppos, ppos,
width(), width(),
image->pix(w, h)); image->pix(size));
} else { } else {
ChatHelpers::PaintStickerThumbnailPath( ChatHelpers::PaintStickerThumbnailPath(
p, p,
media.get(), media.get(),
QRect(ppos, QSize(w, h)), QRect(ppos, size),
_pathGradient.get()); _pathGradient.get());
} }
} }
@ -965,4 +1043,23 @@ void StickerSetBox::Inner::archiveStickers() {
}).send(); }).send();
} }
void StickerSetBox::Inner::updateItems() {
const auto now = crl::now();
const auto delay = std::max(
_lastScrolledAt + kMinAfterScrollDelay - now,
_lastUpdatedAt + kMinRepaintDelay - now);
if (delay <= 0) {
repaintItems(now);
} else if (!_updateItemsTimer.isActive()
|| _updateItemsTimer.remainingTime() > kMinRepaintDelay) {
_updateItemsTimer.callOnce(std::max(delay, kMinRepaintDelay));
}
}
void StickerSetBox::Inner::repaintItems(crl::time now) {
_lastUpdatedAt = now ? now : crl::now();
update();
}
StickerSetBox::Inner::~Inner() = default; StickerSetBox::Inner::~Inner() = default;

View File

@ -936,7 +936,7 @@ void GifsListWidget::updateInlineItems() {
_lastScrolledAt + kMinAfterScrollDelay - now, _lastScrolledAt + kMinAfterScrollDelay - now,
_lastUpdatedAt + kMinRepaintDelay - now); _lastUpdatedAt + kMinRepaintDelay - now);
if (delay <= 0) { if (delay <= 0) {
repaintItems(); repaintItems(now);
} else if (!_updateInlineItems.isActive() } else if (!_updateInlineItems.isActive()
|| _updateInlineItems.remainingTime() > kMinRepaintDelay) { || _updateInlineItems.remainingTime() > kMinRepaintDelay) {
_updateInlineItems.callOnce(std::max(delay, kMinRepaintDelay)); _updateInlineItems.callOnce(std::max(delay, kMinRepaintDelay));