Add reaction animations to comments.

This commit is contained in:
John Preston 2022-01-11 18:33:14 +03:00
parent 34c36d77c3
commit 490e688a91
8 changed files with 101 additions and 56 deletions

View file

@ -86,6 +86,24 @@ void Reactions::preloadImageFor(const QString &emoji) {
}
}
void Reactions::preloadAnimationsFor(const QString &emoji) {
const auto i = ranges::find(_available, emoji, &Reaction::emoji);
if (i == end(_available)) {
return;
}
const auto preload = [&](DocumentData *document) {
const auto view = document
? document->activeMediaView()
: nullptr;
if (view) {
view->checkStickerLarge();
}
};
preload(i->centerIcon);
preload(i->aroundAnimation);
}
QImage Reactions::resolveImageFor(
const QString &emoji,
ImageSize size) {

View file

@ -54,6 +54,7 @@ public:
InlineList,
};
void preloadImageFor(const QString &emoji);
void preloadAnimationsFor(const QString &emoji);
[[nodiscard]] QImage resolveImageFor(
const QString &emoji,
ImageSize size);

View file

@ -554,11 +554,9 @@ void HistoryInner::repaintItem(const Element *view) {
if (top >= 0) {
const auto range = view->verticalRepaintRange();
update(0, top + range.top, width(), range.height);
if (!_reactionEffects.empty()) {
const auto i = _reactionEffects.find(view->data()->fullId());
if (i != end(_reactionEffects)) {
update(i->second);
}
const auto id = view->data()->fullId();
if (const auto area = _reactionsManager->lookupEffectArea(id)) {
update(*area);
}
}
}
@ -900,9 +898,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} else {
_emptyPainter = nullptr;
}
auto reactionEffects = base::flat_map<
not_null<Element*>,
Ui::ReactionEffectPainter>();
_reactionsManager->startEffectsCollection();
if (!noHistoryDisplayed) {
auto readMentions = base::flat_set<not_null<HistoryItem*>>();
@ -932,20 +929,17 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
context.translate(0, -top);
p.translate(0, top);
if (context.clip.y() < view->height()) while (top < drawToY) {
auto effect = Ui::ReactionEffectPainter();
context.reactionEffects = &effect;
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(
view,
selfromy - mtop,
seltoy - mtop);
view->draw(p, context);
if (effect.paint) {
effect.offset += QPoint(0, top);
reactionEffects.emplace(view, effect);
} else if (!_reactionEffects.empty()) {
_reactionEffects.remove(item->fullId());
}
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
const auto height = view->height();
const auto middle = top + height / 2;
@ -995,20 +989,17 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
while (top < drawToY) {
const auto height = view->height();
if (context.clip.y() < height && hdrawtop < top + height) {
auto effect = Ui::ReactionEffectPainter();
context.reactionEffects = &effect;
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(
view,
selfromy - htop,
seltoy - htop);
view->draw(p, context);
if (effect.paint) {
effect.offset += QPoint(0, top);
reactionEffects.emplace(view, effect);
} else if (!_reactionEffects.empty()) {
_reactionEffects.remove(item->fullId());
}
_reactionsManager->recordCurrentReactionEffect(
item->fullId(),
QPoint(0, top));
const auto middle = top + height / 2;
const auto bottom = top + height;
@ -1149,15 +1140,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
});
p.setOpacity(1.);
_reactionsManager->paintButtons(p, context);
for (const auto &[view, effect] : reactionEffects) {
const auto offset = effect.offset;
p.translate(offset);
_reactionEffects[view->data()->fullId()]
= effect.paint(p).translated(offset);
p.translate(-offset);
}
_reactionsManager->paint(p, context);
p.translate(0, _historyPaddingTop);
_emojiInteractions->paint(p);
@ -1648,7 +1631,6 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
return;
}
_reactionEffects.remove(item->fullId());
_animatedStickersPlayed.remove(item);
_reactionsManager->remove(item->fullId());

View file

@ -427,7 +427,6 @@ private:
std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache;
std::unique_ptr<HistoryView::Reactions::Manager> _reactionsManager;
base::flat_map<FullMsgId, QRect> _reactionEffects;
MouseAction _mouseAction = MouseAction::None;
TextSelectType _mouseSelectType = TextSelectType::Letters;

View file

@ -1731,6 +1731,8 @@ void ListWidget::paintEvent(QPaintEvent *e) {
});
if (from != end(_items)) {
_reactionsManager->startEffectsCollection();
auto top = itemTop(from->get());
auto context = controller()->preparePaintContext({
.theme = _delegate->listChatTheme(),
@ -1742,9 +1744,14 @@ void ListWidget::paintEvent(QPaintEvent *e) {
p.translate(0, top);
for (auto i = from; i != to; ++i) {
const auto view = *i;
context.reactionEffects
= _reactionsManager->currentReactionEffect();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view);
view->draw(p, context);
_reactionsManager->recordCurrentReactionEffect(
view->data()->fullId(),
QPoint(0, top));
const auto height = view->height();
top += height;
context.translate(0, -height);
@ -1829,7 +1836,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
return true;
});
_reactionsManager->paintButtons(p, context);
_reactionsManager->paint(p, context);
}
}
@ -2895,6 +2902,10 @@ void ListWidget::repaintItem(const Element *view) {
const auto top = itemTop(view);
const auto range = view->verticalRepaintRange();
update(0, top + range.top, width(), range.height);
const auto id = view->data()->fullId();
if (const auto area = _reactionsManager->lookupEffectArea(id)) {
update(*area);
}
}
void ListWidget::repaintItem(FullMsgId itemId) {

View file

@ -539,9 +539,7 @@ void Manager::applyList(const std::vector<Data::Reaction> &list) {
return std::tie(
obj.emoji,
obj.appearAnimation,
obj.selectAnimation,
obj.centerIcon,
obj.aroundAnimation);
obj.selectAnimation);
};
if (ranges::equal(_list, list, ranges::equal_to(), proj, proj)) {
return;
@ -555,8 +553,6 @@ void Manager::applyList(const std::vector<Data::Reaction> &list) {
.emoji = reaction.emoji,
.appearAnimation = reaction.appearAnimation,
.selectAnimation = reaction.selectAnimation,
.centerIcon = reaction.centerIcon,
.aroundAnimation = reaction.aroundAnimation,
});
}
applyListFilters();
@ -716,18 +712,10 @@ void Manager::loadIcons() {
all = false;
}
}
if (all) {
const auto preload = [&](DocumentData *document) {
const auto view = document
? document->activeMediaView()
: nullptr;
if (view) {
view->checkStickerLarge();
}
};
if (all && !_icons.empty()) {
auto &data = _icons.front()->appearAnimation->owner().reactions();
for (const auto &icon : _icons) {
preload(icon->centerIcon);
preload(icon->aroundAnimation);
data.preloadAnimationsFor(icon->emoji);
}
}
}
@ -751,7 +739,7 @@ void Manager::removeStaleButtons() {
end(_buttonHiding));
}
void Manager::paintButtons(Painter &p, const PaintContext &context) {
void Manager::paint(Painter &p, const PaintContext &context) {
removeStaleButtons();
for (const auto &button : _buttonHiding) {
paintButton(p, context, button.get());
@ -759,6 +747,14 @@ void Manager::paintButtons(Painter &p, const PaintContext &context) {
if (const auto current = _button.get()) {
paintButton(p, context, current);
}
for (const auto &[id, effect] : _collectedEffects) {
const auto offset = effect.offset;
p.translate(offset);
_activeEffectAreas[id] = effect.paint(p).translated(offset);
p.translate(-offset);
}
_collectedEffects.clear();
}
ClickHandlerPtr Manager::computeButtonLink(QPoint position) const {
@ -856,6 +852,7 @@ bool Manager::overCurrentButton(QPoint position) const {
}
void Manager::remove(FullMsgId context) {
_activeEffectAreas.remove(context);
if (_buttonContext == context) {
_buttonContext = {};
_button = nullptr;
@ -1507,6 +1504,31 @@ QRect Manager::validateFrame(
return result;
}
std::optional<QRect> Manager::lookupEffectArea(FullMsgId itemId) const {
const auto i = _activeEffectAreas.find(itemId);
return (i != end(_activeEffectAreas))
? i->second
: std::optional<QRect>();
}
void Manager::startEffectsCollection() {
_collectedEffects.clear();
_currentEffect = {};
}
not_null<Ui::ReactionEffectPainter*> Manager::currentReactionEffect() {
return &_currentEffect;
}
void Manager::recordCurrentReactionEffect(FullMsgId itemId, QPoint origin) {
if (_currentEffect.paint) {
_currentEffect.offset += origin;
_collectedEffects[itemId] = base::take(_currentEffect);
} else if (!_collectedEffects.empty()) {
_collectedEffects.remove(itemId);
}
}
void SetupManagerList(
not_null<Manager*> manager,
not_null<Main::Session*> session,

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/widgets/scroll_area.h"
#include "ui/chat/chat_style.h"
namespace Ui {
struct ChatPaintContext;
@ -145,7 +146,7 @@ public:
void updateUniqueLimit(not_null<HistoryItem*> item);
void updateButton(ButtonParameters parameters);
void paintButtons(Painter &p, const PaintContext &context);
void paint(Painter &p, const PaintContext &context);
[[nodiscard]] TextState buttonTextState(QPoint position) const;
void remove(FullMsgId context);
@ -165,6 +166,13 @@ public:
return _chosen.events();
}
[[nodiscard]] std::optional<QRect> lookupEffectArea(
FullMsgId itemId) const;
void startEffectsCollection();
[[nodiscard]] auto currentReactionEffect()
-> not_null<Ui::ReactionEffectPainter*>;
void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin);
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
@ -178,8 +186,6 @@ private:
QString emoji;
not_null<DocumentData*> appearAnimation;
not_null<DocumentData*> selectAnimation;
DocumentData *centerIcon = nullptr;
DocumentData *aroundAnimation = nullptr;
std::shared_ptr<Lottie::Icon> appear;
std::shared_ptr<Lottie::Icon> select;
mutable ClickHandlerPtr link;
@ -335,6 +341,11 @@ private:
mutable base::flat_map<QString, ClickHandlerPtr> _reactionsLinks;
Fn<Fn<void()>(QString)> _createChooseCallback;
base::flat_map<FullMsgId, QRect> _activeEffectAreas;
Ui::ReactionEffectPainter _currentEffect;
base::flat_map<FullMsgId, Ui::ReactionEffectPainter> _collectedEffects;
rpl::lifetime _lifetime;
};

View file

@ -275,6 +275,7 @@ bool InlineList::getState(
if (button.geometry.contains(point)) {
if (!button.link) {
button.link = _handlerFactory(button.emoji);
_owner->preloadAnimationsFor(button.emoji);
}
outResult->link = button.link;
return true;