From 490e688a91153c866584778f27845b8f45b6bdcd Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 11 Jan 2022 18:33:14 +0300 Subject: [PATCH] Add reaction animations to comments. --- .../data/data_message_reactions.cpp | 18 ++++++ .../SourceFiles/data/data_message_reactions.h | 1 + .../history/history_inner_widget.cpp | 50 ++++++----------- .../history/history_inner_widget.h | 1 - .../history/view/history_view_list_widget.cpp | 13 ++++- .../view/history_view_react_button.cpp | 56 +++++++++++++------ .../history/view/history_view_react_button.h | 17 +++++- .../history/view/history_view_reactions.cpp | 1 + 8 files changed, 101 insertions(+), 56 deletions(-) diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 578723337f..a80413a6b8 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -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) { diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 31a833071e..9b0a020bd8 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -54,6 +54,7 @@ public: InlineList, }; void preloadImageFor(const QString &emoji); + void preloadAnimationsFor(const QString &emoji); [[nodiscard]] QImage resolveImageFor( const QString &emoji, ImageSize size); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2cd2518bdb..37b3109b31 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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, - Ui::ReactionEffectPainter>(); + + _reactionsManager->startEffectsCollection(); if (!noHistoryDisplayed) { auto readMentions = base::flat_set>(); @@ -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 item) { return; } - _reactionEffects.remove(item->fullId()); _animatedStickersPlayed.remove(item); _reactionsManager->remove(item->fullId()); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 60870de1f9..5645236a43 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -427,7 +427,6 @@ private: std::shared_ptr> _userpics, _userpicsCache; std::unique_ptr _reactionsManager; - base::flat_map _reactionEffects; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 65cef54eba..c96fe4857c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -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) { diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 7a9bdd965d..d285a5d1bf 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -539,9 +539,7 @@ void Manager::applyList(const std::vector &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 &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 Manager::lookupEffectArea(FullMsgId itemId) const { + const auto i = _activeEffectAreas.find(itemId); + return (i != end(_activeEffectAreas)) + ? i->second + : std::optional(); +} + +void Manager::startEffectsCollection() { + _collectedEffects.clear(); + _currentEffect = {}; +} + +not_null 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, not_null session, diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index f875dc87e5..fe6e610b94 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -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 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 lookupEffectArea( + FullMsgId itemId) const; + void startEffectsCollection(); + [[nodiscard]] auto currentReactionEffect() + -> not_null; + void recordCurrentReactionEffect(FullMsgId itemId, QPoint origin); + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -178,8 +186,6 @@ private: QString emoji; not_null appearAnimation; not_null selectAnimation; - DocumentData *centerIcon = nullptr; - DocumentData *aroundAnimation = nullptr; std::shared_ptr appear; std::shared_ptr select; mutable ClickHandlerPtr link; @@ -335,6 +341,11 @@ private: mutable base::flat_map _reactionsLinks; Fn(QString)> _createChooseCallback; + base::flat_map _activeEffectAreas; + + Ui::ReactionEffectPainter _currentEffect; + base::flat_map _collectedEffects; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/history_view_reactions.cpp index ef584cdcd0..c770a5d7b1 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reactions.cpp @@ -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;