Support animated stickers in suggestions.

This commit is contained in:
John Preston 2019-07-02 16:29:26 +02:00
parent 848ea16eef
commit 0dd1b4eae6
8 changed files with 210 additions and 52 deletions

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "apiwrap.h"
#include "storage/localstorage.h"
#include "lottie/lottie_single_player.h"
#include "ui/widgets/scroll_area.h"
#include "ui/image/image.h"
#include "auth_session.h"
@ -23,11 +24,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_widgets.h"
#include "styles/style_chat_helpers.h"
FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent)
FieldAutocomplete::FieldAutocomplete(QWidget *parent)
: RpWidget(parent)
, _scroll(this, st::mentionScroll) {
_scroll->setGeometry(rect());
_inner = _scroll->setOwnedWidget(object_ptr<internal::FieldAutocompleteInner>(this, &_mrows, &_hrows, &_brows, &_srows));
_inner = _scroll->setOwnedWidget(
object_ptr<internal::FieldAutocompleteInner>(
this,
&_mrows,
&_hrows,
&_brows,
&_srows));
_inner->setGeometry(rect());
connect(_inner, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)));
@ -44,6 +52,8 @@ FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent)
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
}
FieldAutocomplete::~FieldAutocomplete() = default;
void FieldAutocomplete::paintEvent(QPaintEvent *e) {
Painter p(this);
@ -70,7 +80,12 @@ void FieldAutocomplete::showFiltered(
_channel = peer->asChannel();
if (query.isEmpty()) {
_type = Type::Mentions;
rowsUpdated(internal::MentionRows(), internal::HashtagRows(), internal::BotCommandRows(), _srows, false);
rowsUpdated(
internal::MentionRows(),
internal::HashtagRows(),
internal::BotCommandRows(),
base::take(_srows),
false);
return;
}
@ -108,7 +123,12 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
_emoji = emoji;
_type = Type::Stickers;
if (!emoji) {
rowsUpdated(_mrows, _hrows, _brows, internal::StickerRows(), false);
rowsUpdated(
base::take(_mrows),
base::take(_hrows),
base::take(_brows),
internal::StickerRows(),
false);
return;
}
@ -137,6 +157,31 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
}
}
internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto list = Stickers::GetListByEmoji(
_emoji,
_stickersSeed
);
auto result = ranges::view::all(
list
) | ranges::view::transform([](not_null<DocumentData*> sticker) {
return internal::StickerSuggestion{ sticker };
}) | ranges::to_vector;
for (auto &suggestion : _srows) {
if (!suggestion.animated) {
continue;
}
const auto i = ranges::find(
result,
suggestion.document,
&internal::StickerSuggestion::document);
if (i != end(result)) {
i->animated = std::move(suggestion.animated);
}
}
return result;
}
void FieldAutocomplete::updateFiltered(bool resetScroll) {
int32 now = unixtime(), recentInlineBots = 0;
internal::MentionRows mrows;
@ -144,7 +189,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
internal::BotCommandRows brows;
internal::StickerRows srows;
if (_emoji) {
srows = Stickers::GetListByEmoji(_emoji, _stickersSeed);
srows = getStickerSuggestions();
} else if (_type == Type::Mentions) {
int maxListSize = _addInlineBots ? cRecentInlineBots().size() : 0;
if (_chat) {
@ -326,11 +371,21 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
}
}
}
rowsUpdated(mrows, hrows, brows, srows, resetScroll);
rowsUpdated(
std::move(mrows),
std::move(hrows),
std::move(brows),
std::move(srows),
resetScroll);
_inner->setRecentInlineBotsInRows(recentInlineBots);
}
void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const internal::StickerRows &srows, bool resetScroll) {
void FieldAutocomplete::rowsUpdated(
internal::MentionRows &&mrows,
internal::HashtagRows &&hrows,
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.empty()) {
if (!isHidden()) {
hideAnimated();
@ -341,10 +396,10 @@ void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const in
_brows.clear();
_srows.clear();
} else {
_mrows = mrows;
_hrows = hrows;
_brows = brows;
_srows = srows;
_mrows = std::move(mrows);
_hrows = std::move(hrows);
_brows = std::move(brows);
_srows = std::move(srows);
bool hidden = _hiding || isHidden();
if (hidden) {
@ -358,6 +413,7 @@ void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const in
showAnimated();
}
}
_inner->rowsUpdated();
}
void FieldAutocomplete::setBoundings(QRect boundings) {
@ -505,12 +561,14 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
return QWidget::eventFilter(obj, e);
}
FieldAutocomplete::~FieldAutocomplete() {
}
namespace internal {
FieldAutocompleteInner::FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerRows *srows)
FieldAutocompleteInner::FieldAutocompleteInner(
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows)
: _parent(parent)
, _mrows(mrows)
, _hrows(hrows)
@ -551,9 +609,16 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
int32 index = row * _stickersPerRow + col;
if (index >= _srows->size()) break;
const auto document = _srows->at(index);
auto &sticker = (*_srows)[index];
const auto document = sticker.document;
if (!document->sticker()) continue;
if (document->sticker()->animated
&& !sticker.animated
&& document->loaded()) {
setupLottie(sticker);
}
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
if (_sel == index) {
QPoint tl(pos);
@ -562,14 +627,23 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
}
document->checkStickerSmall();
float64 coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(document->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 2) / float64(document->dimensions.height()));
if (coef > 1) coef = 1;
int32 w = qRound(coef * document->dimensions.width()), h = qRound(coef * document->dimensions.height());
if (w < 1) w = 1;
if (h < 1) h = 1;
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
if (const auto image = document->getStickerSmall()) {
if (sticker.animated && sticker.animated->ready()) {
const auto frame = sticker.animated->frame();
sticker.animated->markFrameShown();
const auto size = frame.size() / cIntRetinaFactor();
const auto ppos = pos + QPoint(
(st::stickerPanSize.width() - size.width()) / 2,
(st::stickerPanSize.height() - size.height()) / 2);
p.drawImage(
QRect(ppos, size),
frame);
} else if (const auto image = document->getStickerSmall()) {
float64 coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(document->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 2) / float64(document->dimensions.height()));
if (coef > 1) coef = 1;
int32 w = qRound(coef * document->dimensions.width()), h = qRound(coef * document->dimensions.height());
if (w < 1) w = 1;
if (h < 1) h = 1;
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
p.drawPixmapLeft(ppos, width(), image->pix(document->stickerSetOrigin(), w, h));
}
}
@ -742,7 +816,7 @@ bool FieldAutocompleteInner::moveSel(int key) {
bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod method) const {
if (!_srows->empty()) {
if (_sel >= 0 && _sel < _srows->size()) {
emit stickerChosen((*_srows)[_sel], method);
emit stickerChosen((*_srows)[_sel].document, method);
return true;
}
} else if (!_mrows->isEmpty()) {
@ -872,6 +946,58 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
}
}
void FieldAutocompleteInner::rowsUpdated() {
if (_srows->empty()) {
_stickersLifetime.destroy();
}
}
auto FieldAutocompleteInner::getLottieRenderer()
-> std::shared_ptr<Lottie::FrameRenderer> {
if (auto result = _lottieRenderer.lock()) {
return result;
}
auto result = Lottie::MakeFrameRenderer();
_lottieRenderer = result;
return result;
}
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
suggestion.animated = Stickers::LottiePlayerFromDocument(
document,
Stickers::LottieSize::InlineResults,
QSize(
st::stickerPanSize.width() - st::buttonRadius * 2,
st::stickerPanSize.height() - st::buttonRadius * 2
) * cIntRetinaFactor(),
getLottieRenderer());
suggestion.animated->updates(
) | rpl::start_with_next([=] {
repaintSticker(document);
}, _stickersLifetime);
}
void FieldAutocompleteInner::repaintSticker(
not_null<DocumentData*> document) {
const auto i = ranges::find(
*_srows,
document,
&StickerSuggestion::document);
if (i == end(*_srows)) {
return;
}
const auto index = (i - begin(*_srows));
const auto row = (index / _stickersPerRow);
const auto col = (index % _stickersPerRow);
update(
st::stickerPanPadding + col * st::stickerPanSize.width(),
st::stickerPanPadding + row * st::stickerPanSize.height(),
st::stickerPanSize.width(),
st::stickerPanSize.height());
}
void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;
@ -905,8 +1031,8 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
_down = _sel;
if (_down >= 0 && _down < _srows->size()) {
Ui::showMediaPreview(
(*_srows)[_down]->stickerSetOrigin(),
(*_srows)[_down]);
(*_srows)[_down].document->stickerSetOrigin(),
(*_srows)[_down].document);
}
}
}
@ -925,8 +1051,8 @@ void FieldAutocompleteInner::onParentGeometryChanged() {
void FieldAutocompleteInner::showPreview() {
if (_down >= 0 && _down < _srows->size()) {
Ui::showMediaPreview(
(*_srows)[_down]->stickerSetOrigin(),
(*_srows)[_down]);
(*_srows)[_down].document->stickerSetOrigin(),
(*_srows)[_down].document);
_previewShown = true;
}
}

View File

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/effects/animations.h"
#include "ui/twidget.h"
#include "ui/rp_widget.h"
#include "base/timer.h"
#include "chat_helpers/stickers.h"
@ -16,22 +16,33 @@ namespace Ui {
class ScrollArea;
} // namespace Ui
namespace Lottie {
class SinglePlayer;
class FrameRenderer;
} // namespace Lottie;
namespace internal {
struct StickerSuggestion {
not_null<DocumentData*> document;
std::unique_ptr<Lottie::SinglePlayer> animated;
};
using MentionRows = QList<UserData*>;
using HashtagRows = QList<QString>;
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
using StickerRows = std::vector<not_null<DocumentData*>>;
using StickerRows = std::vector<StickerSuggestion>;
class FieldAutocompleteInner;
} // namespace internal
class FieldAutocomplete final : public TWidget {
class FieldAutocomplete final : public Ui::RpWidget {
Q_OBJECT
public:
FieldAutocomplete(QWidget *parent);
~FieldAutocomplete();
bool clearFilteredBotCommands();
void showFiltered(
@ -70,8 +81,6 @@ public:
void hideFast();
~FieldAutocomplete();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
@ -93,6 +102,7 @@ private:
void updateFiltered(bool resetScroll = false);
void recount(bool resetScroll = false);
internal::StickerRows getStickerSuggestions();
QPixmap _cache;
internal::MentionRows _mrows;
@ -100,7 +110,12 @@ private:
internal::BotCommandRows _brows;
internal::StickerRows _srows;
void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const internal::StickerRows &srows, bool resetScroll);
void rowsUpdated(
internal::MentionRows &&mrows,
internal::HashtagRows &&hrows,
internal::BotCommandRows &&brows,
internal::StickerRows &&srows,
bool resetScroll);
object_ptr<Ui::ScrollArea> _scroll;
QPointer<internal::FieldAutocompleteInner> _inner;
@ -132,17 +147,25 @@ private:
namespace internal {
class FieldAutocompleteInner final : public TWidget, private base::Subscriber {
class FieldAutocompleteInner final
: public Ui::RpWidget
, private base::Subscriber {
Q_OBJECT
public:
FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerRows *srows);
FieldAutocompleteInner(
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
not_null<HashtagRows*> hrows,
not_null<BotCommandRows*> brows,
not_null<StickerRows*> srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
void setRecentInlineBotsInRows(int32 bots);
void rowsUpdated();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
@ -170,11 +193,17 @@ private:
void showPreview();
void selectByMouse(QPoint global);
FieldAutocomplete *_parent = nullptr;
MentionRows *_mrows = nullptr;
HashtagRows *_hrows = nullptr;
BotCommandRows *_brows = nullptr;
StickerRows *_srows = nullptr;
void setupLottie(StickerSuggestion &suggestion);
void repaintSticker(not_null<DocumentData*> document);
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
not_null<FieldAutocomplete*> _parent;
not_null<MentionRows*> _mrows;
not_null<HashtagRows*> _hrows;
not_null<BotCommandRows*> _brows;
not_null<StickerRows*> _srows;
rpl::lifetime _stickersLifetime;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
int _stickersPerRow = 1;
int _recentInlineBotsInRows = 0;
int _sel = -1;

View File

@ -1154,10 +1154,11 @@ auto LottieFromDocument(
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
LottieSize sizeTag,
QSize box) {
const auto method = [](auto &&...args) {
QSize box,
std::shared_ptr<Lottie::FrameRenderer> renderer) {
const auto method = [&](auto &&...args) {
return std::make_unique<Lottie::SinglePlayer>(
std::forward<decltype(args)>(args)...);
std::forward<decltype(args)>(args)..., std::move(renderer));
};
return LottieFromDocument(method, document, sizeTag, box);
}

View File

@ -132,7 +132,8 @@ enum class LottieSize : uchar {
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
not_null<DocumentData*> document,
LottieSize sizeTag,
QSize box);
QSize box,
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
[[nodiscard]] not_null<Lottie::Animation*> LottieAnimationFromDocument(
not_null<Lottie::MultiPlayer*> player,
not_null<DocumentData*> document,

View File

@ -129,6 +129,10 @@ std::unique_ptr<rlottie::Animation> CreateFromContent(
} // namespace details
std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
return FrameRenderer::CreateIndependent();
}
QImage ReadThumbnail(const QByteArray &content) {
return Init(content, FrameRequest()).match([](
const std::unique_ptr<SharedState> &state) {

View File

@ -22,6 +22,9 @@ namespace Lottie {
class Player;
class SharedState;
class FrameRenderer;
std::shared_ptr<FrameRenderer> MakeFrameRenderer();
QImage ReadThumbnail(const QByteArray &content);

View File

@ -15,10 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Lottie {
std::shared_ptr<FrameRenderer> MakeFrameRenderer() {
return FrameRenderer::CreateIndependent();
}
MultiPlayer::MultiPlayer(std::shared_ptr<FrameRenderer> renderer)
: _timer([=] { checkNextFrameRender(); })
, _renderer(renderer ? std::move(renderer) : FrameRenderer::Instance()) {

View File

@ -27,8 +27,6 @@ struct MultiUpdate {
// std::pair<Animation*, Error>> data;
};
std::shared_ptr<FrameRenderer> MakeFrameRenderer();
class MultiPlayer final : public Player {
public:
explicit MultiPlayer(std::shared_ptr<FrameRenderer> renderer = nullptr);