From d4ba01bad06872fe9c3b7ce3ef1df62b0b9a95f6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 14 Sep 2023 18:47:22 +0400 Subject: [PATCH] Suggested reaction effect around the widget. --- Telegram/SourceFiles/data/data_story.cpp | 3 +- .../stories/media_stories_controller.cpp | 15 ++- .../media/stories/media_stories_reactions.cpp | 124 +++++++++++++++++- .../media/stories/media_stories_reactions.h | 1 + .../media/view/media_view_overlay_widget.cpp | 7 +- .../ui/effects/reaction_fly_animation.cpp | 17 ++- .../ui/effects/reaction_fly_animation.h | 4 +- 7 files changed, 159 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index f51d7a390..e3acfe06b 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -567,7 +567,8 @@ void Story::applyFields( } } auto total = 0; - if (const auto list = data.vreactions()) { + if (const auto list = data.vreactions() + ; list && _peer->isChannel()) { reactionsCounts.reserve(list->v.size()); for (const auto &reaction : list->v) { const auto &data = reaction.data(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 14bf29857..4a0fbd0eb 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -1143,14 +1143,23 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { } for (const auto &suggestedReaction : _suggestedReactions) { const auto id = suggestedReaction.reaction; + auto widget = _reactions->makeSuggestedReactionWidget( + suggestedReaction); + const auto raw = widget.get(); _areas.push_back({ .original = suggestedReaction.area.geometry, .rotation = suggestedReaction.area.rotation, .handler = std::make_shared([=] { - _reactions->applyLike(id); + raw->playEffect(); + if (const auto now = story()) { + if (now->sentReactionId() != id) { + now->owner().stories().sendReaction( + now->fullId(), + id); + } + } }), - .reaction = _reactions->makeSuggestedReactionWidget( - suggestedReaction), + .reaction = std::move(widget), }); } rebuildActiveAreas(*layout); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 1ba4014ce..82daa7b89 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -57,6 +57,7 @@ constexpr auto kSuggestedTailBigRotation = -42.29; constexpr auto kSuggestedTailSmallRotation = -40.87; constexpr auto kSuggestedReactionSize = 0.7; constexpr auto kSuggestedWithCountSize = 0.55; +constexpr auto kStoppingFadeDuration = crl::time(150); class ReactionView final : public Ui::RpWidget @@ -70,9 +71,16 @@ public: void setAreaGeometry(QRect geometry) override; void updateCount(int count) override; + void playEffect() override; private: using Element = HistoryView::Element; + + struct Stopping { + std::unique_ptr effect; + Ui::Animations::Simple animation; + }; + not_null delegate(); HistoryView::Context elementContext() override; bool elementAnimationsPaused() override; @@ -81,7 +89,15 @@ private: void paintEvent(QPaintEvent *e) override; + void setupCustomChatStylePalette(); void cacheBackground(); + void paintEffectFrame( + QPainter &p, + not_null effect, + crl::time now); + void updateEffectGeometry(); + void createEffectCanvas(); + void stopEffect(); Data::SuggestedReaction _data; std::unique_ptr _chatStyle; @@ -102,6 +118,11 @@ private: float64 _smallOffset = 0; float64 _smallSize = 0; + std::unique_ptr _effectCanvas; + std::unique_ptr _effect; + std::vector _effectStopping; + QRect _effectTarget; + }; [[nodiscard]] AdminLog::OwnedItem GenerateFakeItem( @@ -190,11 +211,17 @@ ReactionView::ReactionView( _data.count = 0; updateCount(reaction.count); - + setupCustomChatStylePalette(); setAttribute(Qt::WA_TransparentForMouseEvents); show(); } +void ReactionView::setupCustomChatStylePalette() { + const auto color = uchar(_data.dark ? 255 : 0); + _chatStyle->historyTextInFg().set(color, color, color, 255); + _chatStyle->applyCustomPalette(_chatStyle.get()); +} + void ReactionView::setAreaGeometry(QRect geometry) { _size = std::min(geometry.width(), geometry.height()); const auto scaled = [&](float64 scale) { @@ -208,6 +235,10 @@ void ReactionView::setAreaGeometry(QRect geometry) { const auto add = int(base::SafeRound(_smallOffset + _smallSize)) - (_size / 2); setGeometry(geometry.marginsAdded({ add, add, add, add })); + const auto sub = int(base::SafeRound( + (1. - kSuggestedReactionSize) * _size / 2)); + _effectTarget = geometry.marginsRemoved({ sub, sub, sub, sub }); + updateEffectGeometry(); } void ReactionView::updateCount(int count) { @@ -228,6 +259,97 @@ void ReactionView::updateCount(int count) { update(); } +void ReactionView::playEffect() { + const auto exists = (_effectCanvas != nullptr); + if (exists) { + stopEffect(); + } else { + createEffectCanvas(); + } + const auto reactions = &_fake->history()->owner().reactions(); + const auto scaleDown = _bubbleGeometry.width() / float64(_mediaWidth); + auto args = Ui::ReactionFlyAnimationArgs{ + .id = _data.reaction, + .miniCopyMultiplier = std::min(1., scaleDown), + .effectOnly = true, + }; + _effect = std::make_unique( + reactions, + std::move(args), + [=] { _effectCanvas->update(); }, + _size / 2, + Data::CustomEmojiSizeTag::Isolated); + if (exists) { + _effectStopping.back().animation.start([=] { + _effectCanvas->update(); + }, 1., 0., kStoppingFadeDuration); + } +} + +void ReactionView::paintEffectFrame( + QPainter &p, + not_null effect, + crl::time now) { + effect->paintGetArea( + p, + QPoint(), + _effectTarget.translated(-_effectCanvas->pos()), + _data.dark ? Qt::white : Qt::black, + QRect(), + now); +} + +void ReactionView::createEffectCanvas() { + _effectCanvas = std::make_unique(parentWidget()); + const auto raw = _effectCanvas.get(); + raw->setAttribute(Qt::WA_TransparentForMouseEvents); + raw->show(); + raw->paintRequest() | rpl::start_with_next([=] { + if (!_effect || _effect->finished()) { + crl::on_main(_effectCanvas.get(), [=] { + _effect = nullptr; + _effectStopping.clear(); + _effectCanvas = nullptr; + }); + return; + } + const auto now = crl::now(); + auto p = QPainter(raw); + auto hq = PainterHighQualityEnabler(p); + _effectStopping.erase(ranges::remove_if(_effectStopping, [&]( + const Stopping &stopping) { + if (!stopping.animation.animating() + || stopping.effect->finished()) { + return true; + } + p.setOpacity(stopping.animation.value(0.)); + paintEffectFrame(p, stopping.effect.get(), now); + return false; + }), end(_effectStopping)); + paintEffectFrame(p, _effect.get(), now); + }, raw->lifetime()); + updateEffectGeometry(); +} + +void ReactionView::stopEffect() { + _effectStopping.push_back({ .effect = std::move(_effect) }); + _effectStopping.back().animation.start([=] { + _effectCanvas->update(); + }, 1., 0., kStoppingFadeDuration); +} + +void ReactionView::updateEffectGeometry() { + if (!_effectCanvas) { + return; + } + const auto center = geometry().center(); + _effectCanvas->setGeometry( + center.x() - _size, + center.y() - _size, + _size * 2, + _size * 3); +} + not_null ReactionView::delegate() { return static_cast(this); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index 01c679e2e..d27761f46 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -47,6 +47,7 @@ public: virtual void setAreaGeometry(QRect geometry) = 0; virtual void updateCount(int count) = 0; + virtual void playEffect() = 0; }; class Reactions final { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index fb153ed80..8b92b92ab 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -5537,10 +5537,15 @@ bool OverlayWidget::handleDoubleClick( Qt::MouseButton button) { updateOver(position); - if (_over != Over::Video || !_streamed || button != Qt::LeftButton) { + if (_over != Over::Video || button != Qt::LeftButton) { return false; } else if (_stories) { + if (ClickHandler::getActive()) { + return false; + } toggleFullScreen(_windowed); + } else if (!_streamed) { + return false; } else { playbackToggleFullScreen(); playbackPauseResume(); diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp index 8bf354c17..8a271233d 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.cpp @@ -114,9 +114,9 @@ ReactionFlyAnimation::ReactionFlyAnimation( }); return true; }; - generateMiniCopies(size + size / 2); + generateMiniCopies(size + size / 2, args.miniCopyMultiplier); if (args.effectOnly) { - _custom = nullptr; + _effectOnly = true; } else if (!_custom && !resolve(_center, centerIcon, size)) { return; } @@ -227,6 +227,9 @@ void ReactionFlyAnimation::paintCenterFrame( QRect target, const QColor &colored, crl::time now) const { + if (_effectOnly) { + return; + } const auto size = QSize( int(base::SafeRound(target.width() * _centerSizeMultiplier)), int(base::SafeRound(target.height() * _centerSizeMultiplier))); @@ -298,7 +301,9 @@ void ReactionFlyAnimation::paintMiniCopies( } } -void ReactionFlyAnimation::generateMiniCopies(int size) { +void ReactionFlyAnimation::generateMiniCopies( + int size, + float64 miniCopyMultiplier) { if (!_custom) { return; } @@ -313,17 +318,19 @@ void ReactionFlyAnimation::generateMiniCopies(int size) { }; _miniCopies.reserve(kMiniCopies); for (auto i = 0; i != kMiniCopies; ++i) { - const auto maxScale = kMiniCopiesMaxScaleMin + const auto scale = kMiniCopiesMaxScaleMin + (kMiniCopiesMaxScaleMax - kMiniCopiesMaxScaleMin) * random(); + const auto maxScale = scale * miniCopyMultiplier; const auto duration = between( kMiniCopiesDurationMin, kMiniCopiesDurationMax); const auto maxSize = int(std::ceil(maxScale * _customSize)); const auto maxHalf = (maxSize + 1) / 2; + const auto flyUpTill = std::max(size - maxHalf, size / 4 + 1); _miniCopies.push_back({ .maxScale = maxScale, .duration = duration / float64(kMiniCopiesDurationMax), - .flyUp = between(size / 4, size - maxHalf), + .flyUp = between(size / 4, flyUpTill), .finalX = between(-size, size), .finalY = between(size - (size / 4), size), }); diff --git a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h index 340deffa0..50aae6dc8 100644 --- a/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/reaction_fly_animation.h @@ -29,6 +29,7 @@ struct ReactionFlyAnimationArgs { QRect flyFrom; crl::time scaleOutDuration = 0; float64 scaleOutTarget = 0.; + float64 miniCopyMultiplier = 1.; bool effectOnly = false; [[nodiscard]] ReactionFlyAnimationArgs translated(QPoint point) const; @@ -102,7 +103,7 @@ private: QPoint center, const QColor &colored, crl::time now) const; - void generateMiniCopies(int size); + void generateMiniCopies(int size, float64 miniCopyMultiplier); const not_null<::Data::Reactions*> _owner; Fn _repaint; @@ -120,6 +121,7 @@ private: crl::time _scaleOutDuration = 0; float64 _scaleOutTarget = 0.; bool _noEffectScaleStarted = false; + bool _effectOnly = false; bool _valid = false; mutable Parabolic _cached;