Support webm stickers in field autocomplete.

This commit is contained in:
John Preston 2022-01-25 12:53:08 +03:00
parent f45c47f3d5
commit d96a8d028a
2 changed files with 92 additions and 47 deletions

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/core_settings.h"
#include "lottie/lottie_single_player.h"
#include "media/clip/media_clip_reader.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h"
@ -102,8 +103,13 @@ private:
QSize stickerBoundingBox() const;
void setupLottie(StickerSuggestion &suggestion);
void setupWebm(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
void repaintStickerAtIndex(int index);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
void clipCallback(
Media::Clip::Notification notification,
not_null<DocumentData*> document);
const not_null<Window::SessionController*> _controller;
const not_null<FieldAutocomplete*> _parent;
@ -141,6 +147,13 @@ private:
};
struct FieldAutocomplete::StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> lottie;
Media::Clip::ReaderPointer webm;
};
FieldAutocomplete::FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller)
@ -340,7 +353,7 @@ FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
};
}) | ranges::to_vector;
for (auto &suggestion : _srows) {
if (!suggestion.animated) {
if (!suggestion.lottie && !suggestion.webm) {
continue;
}
const auto i = ranges::find(
@ -348,7 +361,8 @@ FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
suggestion.document,
&StickerSuggestion::document);
if (i != end(result)) {
i->animated = std::move(suggestion.animated);
i->lottie = std::move(suggestion.lottie);
i->webm = std::move(suggestion.webm);
}
}
return result;
@ -812,6 +826,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
width(),
std::min(st::msgMaxWidth / 2, width() / 2));
const auto now = crl::now();
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
@ -825,12 +840,15 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
auto &sticker = (*_srows)[index];
const auto document = sticker.document;
const auto &media = sticker.documentMedia;
if (!document->sticker()) continue;
const auto info = document->sticker();
if (!info) continue;
if (document->sticker()->isLottie()
&& !sticker.animated
&& media->loaded()) {
setupLottie(sticker);
if (media->loaded()) {
if (info->isLottie() && !sticker.lottie) {
setupLottie(sticker);
} else if (info->isWebm() && !sticker.webm) {
setupWebm(sticker);
}
}
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
@ -841,46 +859,34 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
}
media->checkStickerSmall();
auto w = 1;
auto h = 1;
if (sticker.animated && !document->dimensions.isEmpty()) {
const auto request = Lottie::FrameRequest{ stickerBoundingBox() * cIntRetinaFactor() };
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
w = std::max(size.width(), 1);
h = std::max(size.height(), 1);
} else {
const auto coef = std::min(
std::min(
(st::stickerPanSize.width() - st::roundRadiusSmall * 2) / float64(document->dimensions.width()),
(st::stickerPanSize.height() - st::roundRadiusSmall * 2) / float64(document->dimensions.height())),
1.);
w = std::max(qRound(coef * document->dimensions.width()), 1);
h = std::max(qRound(coef * document->dimensions.height()), 1);
}
if (sticker.animated && sticker.animated->ready()) {
const auto frame = sticker.animated->frame();
const auto size = frame.size() / cIntRetinaFactor();
const auto ppos = pos + QPoint(
(st::stickerPanSize.width() - size.width()) / 2,
(st::stickerPanSize.height() - size.height()) / 2);
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs);
const auto size = ChatHelpers::ComputeStickerSize(
document,
stickerBoundingBox());
const auto ppos = pos + QPoint(
(st::stickerPanSize.width() - size.width()) / 2,
(st::stickerPanSize.height() - size.height()) / 2);
if (sticker.lottie && sticker.lottie->ready()) {
const auto frame = sticker.lottie->frame();
p.drawImage(
QRect(ppos, size),
QRect(ppos, frame.size() / cIntRetinaFactor()),
frame);
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::SavedGifs);
if (!paused) {
sticker.animated->markFrameShown();
sticker.lottie->markFrameShown();
}
} else if (sticker.webm && sticker.webm->started()) {
p.drawPixmap(ppos, sticker.webm->current({
.frame = size,
.keepAlpha = true,
}, paused ? 0 : now));
} else if (const auto image = media->getStickerSmall()) {
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
p.drawPixmapLeft(ppos, width(), image->pix(w, h));
p.drawPixmapLeft(ppos, width(), image->pix(size));
} else {
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
ChatHelpers::PaintStickerThumbnailPath(
p,
media.get(),
QRect(ppos, QSize(w, h)),
QRect(ppos, size),
_pathGradient.get());
}
}
@ -1271,19 +1277,30 @@ auto FieldAutocomplete::Inner::getLottieRenderer()
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
suggestion.animated = ChatHelpers::LottiePlayerFromDocument(
suggestion.lottie = ChatHelpers::LottiePlayerFromDocument(
suggestion.documentMedia.get(),
ChatHelpers::StickerLottieSize::InlineResults,
stickerBoundingBox() * cIntRetinaFactor(),
Lottie::Quality::Default,
getLottieRenderer());
suggestion.animated->updates(
suggestion.lottie->updates(
) | rpl::start_with_next([=] {
repaintSticker(document);
}, _stickersLifetime);
}
void FieldAutocomplete::Inner::setupWebm(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
auto callback = [=](Media::Clip::Notification notification) {
clipCallback(notification, document);
};
suggestion.webm = Media::Clip::MakeReader(
suggestion.documentMedia->owner()->location(),
suggestion.documentMedia->bytes(),
std::move(callback));
}
QSize FieldAutocomplete::Inner::stickerBoundingBox() const {
return QSize(
st::stickerPanSize.width() - st::roundRadiusSmall * 2,
@ -1299,7 +1316,10 @@ void FieldAutocomplete::Inner::repaintSticker(
if (i == end(*_srows)) {
return;
}
const auto index = (i - begin(*_srows));
repaintStickerAtIndex(i - begin(*_srows));
}
void FieldAutocomplete::Inner::repaintStickerAtIndex(int index) {
const auto row = (index / _stickersPerRow);
const auto col = (index % _stickersPerRow);
update(
@ -1309,6 +1329,36 @@ void FieldAutocomplete::Inner::repaintSticker(
st::stickerPanSize.height());
}
void FieldAutocomplete::Inner::clipCallback(
Media::Clip::Notification notification,
not_null<DocumentData*> document) {
const auto i = ranges::find(
*_srows,
document,
&StickerSuggestion::document);
if (i == end(*_srows)) {
return;
}
using namespace Media::Clip;
switch (notification) {
case Notification::Reinit: {
if (!i->webm) {
break;
} else if (i->webm->state() == State::Error) {
i->webm.setBad();
} else if (i->webm->ready() && !i->webm->started()) {
const auto size = ChatHelpers::ComputeStickerSize(
i->document,
stickerBoundingBox());
i->webm->start({ .frame = size, .keepAlpha = true });
}
} break;
case Notification::Repaint: break;
}
repaintStickerAtIndex(i - begin(*_srows));
}
void FieldAutocomplete::Inner::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;

View file

@ -129,12 +129,7 @@ protected:
private:
class Inner;
friend class Inner;
struct StickerSuggestion {
not_null<DocumentData*> document;
std::shared_ptr<Data::DocumentMedia> documentMedia;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
struct StickerSuggestion;
struct MentionRow {
not_null<UserData*> user;