diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 6cf6f800a..2cd2518bd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -554,6 +554,12 @@ 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); + } + } } } @@ -894,6 +900,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } else { _emptyPainter = nullptr; } + auto reactionEffects = base::flat_map< + not_null, + Ui::ReactionEffectPainter>(); if (!noHistoryDisplayed) { auto readMentions = base::flat_set>(); @@ -923,12 +932,20 @@ 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.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()); + } const auto height = view->height(); const auto middle = top + height / 2; @@ -978,14 +995,21 @@ 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.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()); + } - const auto item = view->data(); const auto middle = top + height / 2; const auto bottom = top + height; if (_visibleAreaBottom >= bottom) { @@ -1127,6 +1151,14 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _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); + } + p.translate(0, _historyPaddingTop); _emojiInteractions->paint(p); } @@ -1616,6 +1648,7 @@ 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 5645236a4..60870de1f 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -427,6 +427,7 @@ 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_bottom_info.cpp b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp index f0f6d0326..dc73121a8 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.cpp @@ -225,7 +225,7 @@ void BottomInfo::paint( left += width() - available; top += st::msgDateFont->height; } - paintReactions(p, position, left, top, available); + paintReactions(p, position, left, top, available, context); } } @@ -234,11 +234,12 @@ void BottomInfo::paintReactions( QPoint origin, int left, int top, - int availableWidth) const { + int availableWidth, + const PaintContext &context) const { auto x = left; auto y = top; auto widthLeft = availableWidth; - const auto animated = _reactionAnimation + const auto animated = (_reactionAnimation && context.reactionEffects) ? _reactionAnimation->playingAroundEmoji() : QString(); if (_reactionAnimation && animated.isEmpty()) { @@ -274,7 +275,9 @@ void BottomInfo::paintReactions( p.drawImage(image.topLeft(), reaction.image); } if (animating) { - _reactionAnimation->paint(p, origin, image); + context.reactionEffects->paint = [=](QPainter &p) { + return _reactionAnimation->paintGetArea(p, origin, image); + }; } if (reaction.countTextWidth > 0) { p.drawText( diff --git a/Telegram/SourceFiles/history/view/history_view_bottom_info.h b/Telegram/SourceFiles/history/view/history_view_bottom_info.h index 09e2ebcb3..73041f844 100644 --- a/Telegram/SourceFiles/history/view/history_view_bottom_info.h +++ b/Telegram/SourceFiles/history/view/history_view_bottom_info.h @@ -102,7 +102,8 @@ private: QPoint origin, int left, int top, - int availableWidth) const; + int availableWidth, + const PaintContext &context) const; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 11064e845..0d515ad15 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -729,6 +729,9 @@ void Message::draw(Painter &p, const PaintContext &context) const { const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); p.translate(reactionsPosition); _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition)); + if (context.reactionEffects && context.reactionEffects->paint) { + context.reactionEffects->offset += reactionsPosition; + } p.translate(-reactionsPosition); } @@ -781,6 +784,9 @@ void Message::draw(Painter &p, const PaintContext &context) const { const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop); p.translate(reactionsPosition); _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition)); + if (context.reactionEffects && context.reactionEffects->paint) { + context.reactionEffects->offset += reactionsPosition; + } p.translate(-reactionsPosition); } @@ -828,15 +834,20 @@ void Message::draw(Painter &p, const PaintContext &context) const { paintText(p, trect, context); if (mediaDisplayed) { auto mediaHeight = media->height(); - auto mediaLeft = inner.left(); - auto mediaTop = (trect.y() + trect.height() - mediaHeight); + auto mediaPosition = QPoint( + inner.left(), + trect.y() + trect.height() - mediaHeight); - p.translate(mediaLeft, mediaTop); + p.translate(mediaPosition); media->draw(p, context.translated( - -mediaLeft, - -mediaTop + -mediaPosition ).withSelection(skipTextSelection(context.selection))); - p.translate(-mediaLeft, -mediaTop); + if (context.reactionEffects + && context.reactionEffects->paint + && !_reactions) { + context.reactionEffects->offset += mediaPosition; + } + p.translate(-mediaPosition); } if (entry) { auto entryLeft = inner.left(); @@ -890,6 +901,9 @@ void Message::draw(Painter &p, const PaintContext &context) const { media->draw(p, context.translated( -g.topLeft() ).withSelection(skipTextSelection(context.selection))); + if (context.reactionEffects && context.reactionEffects->paint && !_reactions) { + context.reactionEffects->offset += g.topLeft(); + } p.translate(-g.topLeft()); } diff --git a/Telegram/SourceFiles/history/view/history_view_react_animation.cpp b/Telegram/SourceFiles/history/view/history_view_react_animation.cpp index 6ce30cee9..c715d5ddf 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_animation.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_animation.cpp @@ -67,36 +67,38 @@ SendAnimation::SendAnimation( SendAnimation::~SendAnimation() = default; -void SendAnimation::paint(QPainter &p, QPoint origin, QRect target) const { - if (_flyIcon) { - const auto from = _flyFrom.translated(origin); - const auto lshift = target.width() / 4; - const auto rshift = target.width() / 2 - lshift; - const auto margins = QMargins{ lshift, lshift, rshift, rshift }; - target = target.marginsRemoved(margins); - const auto progress = _fly.value(1.); - const auto rect = QRect( - anim::interpolate(from.x(), target.x(), progress), - anim::interpolate(from.y(), target.y(), progress), - anim::interpolate(from.width(), target.width(), progress), - anim::interpolate(from.height(), target.height(), progress)); - auto hq = PainterHighQualityEnabler(p); - if (progress < 1.) { - p.setOpacity(1. - progress); - p.drawImage(rect, _flyIcon->frame()); - } - if (progress > 0.) { - p.setOpacity(progress); - p.drawImage(rect.marginsAdded(margins), _center->frame()); - } - p.setOpacity(1.); - } else { +QRect SendAnimation::paintGetArea(QPainter &p, QPoint origin, QRect target) const { + if (!_flyIcon) { p.drawImage(target, _center->frame()); - p.drawImage(QRect( + const auto wide = QRect( target.topLeft() - QPoint(target.width(), target.height()) / 2, - target.size() * 2 - ), _effect->frame()); + target.size() * 2); + p.drawImage(wide, _effect->frame()); + return wide; } + const auto from = _flyFrom.translated(origin); + const auto lshift = target.width() / 4; + const auto rshift = target.width() / 2 - lshift; + const auto margins = QMargins{ lshift, lshift, rshift, rshift }; + target = target.marginsRemoved(margins); + const auto progress = _fly.value(1.); + const auto rect = QRect( + anim::interpolate(from.x(), target.x(), progress), + anim::interpolate(from.y(), target.y(), progress), + anim::interpolate(from.width(), target.width(), progress), + anim::interpolate(from.height(), target.height(), progress)); + const auto wide = rect.marginsAdded(margins); + auto hq = PainterHighQualityEnabler(p); + if (progress < 1.) { + p.setOpacity(1. - progress); + p.drawImage(rect, _flyIcon->frame()); + } + if (progress > 0.) { + p.setOpacity(progress); + p.drawImage(wide, _center->frame()); + } + p.setOpacity(1.); + return wide; } void SendAnimation::startAnimations() { diff --git a/Telegram/SourceFiles/history/view/history_view_react_animation.h b/Telegram/SourceFiles/history/view/history_view_react_animation.h index 35e1c2542..c11864121 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_animation.h +++ b/Telegram/SourceFiles/history/view/history_view_react_animation.h @@ -33,7 +33,7 @@ public: ~SendAnimation(); void setRepaintCallback(Fn repaint); - void paint(QPainter &p, QPoint origin, QRect target) const; + QRect paintGetArea(QPainter &p, QPoint origin, QRect target) const; [[nodiscard]] QString playingAroundEmoji() const; [[nodiscard]] bool flying() const; diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 771df791e..7a9bdd965 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -810,7 +810,7 @@ void Manager::setSelectedIcon(int index) const { if (select && !icon->selectAnimated) { icon->selectAnimated = true; select->animate( - [=] { updateCurrentButton(); }, + crl::guard(this, [=] { updateCurrentButton(); }), 0, select->framesCount() - 1); } @@ -1301,9 +1301,9 @@ void Manager::paintAllEmoji( const auto shift = QPoint(0, oneHeight * (expandUp ? -1 : 1)); auto emojiPosition = mainEmojiPosition + QPoint(0, scroll * (expandUp ? 1 : -1)); - const auto update = [=] { + const auto update = crl::guard(this, [=] { updateCurrentButton(); - }; + }); for (const auto &icon : _icons) { const auto target = countTarget(*icon).translated(emojiPosition); emojiPosition += shift; diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/history_view_reactions.cpp index 9495ef2a7..ef584cdcd 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reactions.cpp @@ -190,7 +190,7 @@ void InlineList::paint( const auto size = st::reactionBottomSize; const auto skip = (size - st::reactionBottomImage) / 2; const auto inbubble = (_data.flags & InlineListData::Flag::InBubble); - const auto animated = _animation + const auto animated = (_animation && context.reactionEffects) ? _animation->playingAroundEmoji() : QString(); if (_animation && animated.isEmpty()) { @@ -236,7 +236,9 @@ void InlineList::paint( p.drawImage(image.topLeft(), button.image); } if (animating) { - _animation->paint(p, QPoint(), image); + context.reactionEffects->paint = [=](QPainter &p) { + return _animation->paintGetArea(p, QPoint(), image); + }; } p.setPen(!inbubble ? (chosen diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 5c57bf1b9..07755c250 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -90,9 +90,15 @@ struct MessageImageStyle { style::icon historyVideoMessageMute = { Qt::Uninitialized }; }; +struct ReactionEffectPainter { + QPoint offset; + Fn paint; +}; + struct ChatPaintContext { not_null st; const BubblePattern *bubblesPattern = nullptr; + ReactionEffectPainter *reactionEffects = nullptr; QRect viewport; QRect clip; TextSelection selection;