diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index cc1d0386a..367ae97f8 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -746,7 +746,7 @@ bool HistoryItem::suggestDeleteAllReport() const { } bool HistoryItem::canReact() const { - return isRegular(); + return isRegular() && !isService(); } void HistoryItem::addReaction(const QString &reaction) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 5df978624..4c2937353 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -359,15 +359,23 @@ QSize Message::performCountOptimalSize() { } minHeight = hasVisibleText() ? item->_text.minHeight() : 0; if (reactionsInBubble) { - accumulate_max(maxWidth, std::min( - st::msgMaxWidth, - (st::msgPadding.left() - + _reactions->maxWidth() - + st::msgPadding.right()))); + const auto reactionsMaxWidth = st::msgPadding.left() + + _reactions->maxWidth() + + st::msgPadding.right(); + accumulate_max( + maxWidth, + std::min(st::msgMaxWidth, reactionsMaxWidth)); if (!mediaDisplayed) { minHeight += st::mediaInBubbleSkip; } - minHeight += _reactions->minHeight(); + if (maxWidth >= reactionsMaxWidth) { + minHeight += _reactions->minHeight(); + } else { + const auto widthForReactions = maxWidth + - st::msgPadding.left() + - st::msgPadding.right(); + minHeight += _reactions->resizeGetHeight(widthForReactions); + } } if (!mediaOnBottom) { minHeight += st::msgPadding.bottom(); @@ -457,13 +465,6 @@ QSize Message::performCountOptimalSize() { maxWidth = st::msgMinWidth; minHeight = 0; } - if (_reactions && !reactionsInBubble) { - // if we have a text bubble we can resize it to fit the keyboard - // but if we have only media we don't do that - if (hasVisibleText()) { - accumulate_max(maxWidth, _reactions->maxWidth()); - } - } if (const auto markup = item->inlineReplyMarkup()) { if (!markup->inlineKeyboard) { markup->inlineKeyboard = std::make_unique( @@ -601,8 +602,11 @@ void Message::draw(Painter &p, const PaintContext &context) const { if (_reactions && !reactionsInBubble) { const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height(); + const auto reactionsLeft = (!bubble && mediaDisplayed) + ? media->contentRectForReactionButton().x() + : 0; g.setHeight(g.height() - reactionsHeight); - const auto reactionsPosition = QPoint(g.left(), g.top() + g.height() + st::mediaInBubbleSkip); + 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)); p.translate(-reactionsPosition); @@ -1250,7 +1254,9 @@ TextState Message::textState( return result; } + const auto bubble = drawBubble(); const auto reactionsInBubble = _reactions && embedReactionsInBubble(); + const auto mediaDisplayed = media && media->isDisplayed(); auto keyboard = item->inlineReplyKeyboard(); auto keyboardHeight = 0; if (keyboard) { @@ -1260,17 +1266,19 @@ TextState Message::textState( if (_reactions && !reactionsInBubble) { const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height(); + const auto reactionsLeft = (!bubble && mediaDisplayed) + ? media->contentRectForReactionButton().x() + : 0; g.setHeight(g.height() - reactionsHeight); - const auto reactionsPosition = QPoint(g.left(), g.top() + g.height() + st::mediaInBubbleSkip); + const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip); if (_reactions->getState(point - reactionsPosition, &result)) { return result; } } - if (drawBubble()) { + if (bubble) { const auto inBubble = g.contains(point); auto entry = logEntryOriginal(); - auto mediaDisplayed = media && media->isDisplayed(); // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); @@ -1807,17 +1815,39 @@ TextSelection Message::adjustSelection( Reactions::ButtonParameters Message::reactionButtonParameters( QPoint position, const TextState &reactionState) const { - auto result = Reactions::ButtonParameters{ .context = data()->fullId() }; - const auto outbg = result.outbg = hasOutLayout(); + using namespace Reactions; + auto result = ButtonParameters{ .context = data()->fullId() }; + const auto outbg = hasOutLayout(); + result.style = (!_comments && !embedReactionsInBubble()) + ? ButtonStyle::Service + : outbg + ? ButtonStyle::Outgoing + : ButtonStyle::Incoming; const auto geometry = countGeometry(); result.pointer = position; const auto onTheLeft = (outbg && !delegate()->elementIsChatWide()); const auto leftAdd = onTheLeft ? 0 : geometry.width(); - result.center = geometry.topLeft() + (onTheLeft - ? (QPoint(0, geometry.height()) + QPoint( + + const auto keyboard = data()->inlineReplyKeyboard(); + const auto keyboardHeight = keyboard + ? (st::msgBotKbButton.margin + keyboard->naturalHeight()) + : 0; + const auto reactionsHeight = (_reactions && !embedReactionsInBubble()) + ? (st::mediaInBubbleSkip + _reactions->height()) + : 0; + const auto innerHeight = geometry.height() + - keyboardHeight + - reactionsHeight; + const auto contentRect = (result.style == ButtonStyle::Service + && !drawBubble()) + ? media()->contentRectForReactionButton().translated( + geometry.topLeft()) + : geometry; + result.center = contentRect.topLeft() + (onTheLeft + ? (QPoint(0, innerHeight) + QPoint( -st::reactionCornerCenter.x(), st::reactionCornerCenter.y())) - : (QPoint(geometry.width(), geometry.height()) + : (QPoint(contentRect.width(), innerHeight) + st::reactionCornerCenter)); if (reactionState.itemId != result.context) { const auto top = marginTop(); @@ -2739,7 +2769,11 @@ int Message::resizeContentGetHeight(int newWidth) { newHeight = 0; } if (_reactions && !reactionsInBubble) { - newHeight += st::mediaInBubbleSkip + _reactions->resizeGetHeight(contentWidth); + const auto reactionsWidth = (!bubble && mediaDisplayed) + ? media->contentRectForReactionButton().width() + : contentWidth; + newHeight += st::mediaInBubbleSkip + + _reactions->resizeGetHeight(reactionsWidth); } if (const auto keyboard = item->inlineReplyKeyboard()) { diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.cpp b/Telegram/SourceFiles/history/view/history_view_react_button.cpp index 944664260..8f5ad63ce 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_react_button.cpp @@ -25,6 +25,7 @@ constexpr auto kActivateDuration = crl::time(150); constexpr auto kExpandDuration = crl::time(150); constexpr auto kInCacheIndex = 0; constexpr auto kOutCacheIndex = 1; +constexpr auto kServiceCacheIndex = 2; constexpr auto kShadowCacheIndex = 0; constexpr auto kEmojiCacheIndex = 1; constexpr auto kMaskCacheIndex = 2; @@ -86,8 +87,8 @@ Button::Button( Button::~Button() = default; -bool Button::outbg() const { - return _outbg; +ButtonStyle Button::style() const { + return _style; } bool Button::isHidden() const { @@ -150,8 +151,8 @@ void Button::applyParameters( ? State::Active : State::Shown; applyState(state, update); - if (_outbg != parameters.outbg) { - _outbg = parameters.outbg; + if (_style != parameters.style) { + _style = parameters.style; if (update) { update(_geometry); } @@ -264,12 +265,12 @@ Manager::Manager( QRect({}, _outer).center() - _innerActive.center()); const auto ratio = style::DevicePixelRatio(); - _cacheInOut = QImage( - _outer.width() * 2 * ratio, + _cacheInOutService = QImage( + _outer.width() * 3 * ratio, _outer.height() * kFramesCount * ratio, QImage::Format_ARGB32_Premultiplied); - _cacheInOut.setDevicePixelRatio(ratio); - _cacheInOut.fill(Qt::transparent); + _cacheInOutService.setDevicePixelRatio(ratio); + _cacheInOutService.fill(Qt::transparent); _cacheParts = QImage( _outer.width() * kCacheColumsCount * ratio, _outer.height() * kFramesCount * ratio, @@ -529,8 +530,8 @@ void Manager::paintButton( const auto geometry = button->geometry(); const auto position = geometry.topLeft(); const auto size = geometry.size(); - const auto outbg = button->outbg(); - const auto patterned = outbg + const auto style = button->style(); + const auto patterned = (style == ButtonStyle::Outgoing) && context.bubblesPattern && !context.viewport.isEmpty() && !context.bubblesPattern->pixmap.size().isEmpty(); @@ -547,15 +548,19 @@ void Manager::paintButton( _cacheForPattern, QRect(QPoint(), geometry.size() * style::DevicePixelRatio())); } else { - const auto &stm = context.st->messageStyle(outbg, false); - const auto background = stm.msgBg->c; + const auto background = (style == ButtonStyle::Service) + ? context.st->msgServiceFg()->c + : context.st->messageStyle( + (style == ButtonStyle::Outgoing), + false + ).msgBg->c; const auto source = validateFrame( - outbg, + style, frameIndex, scale, - stm.msgBg->c, + background, shadow); - paintLongImage(p, geometry, _cacheInOut, source); + paintLongImage(p, geometry, _cacheInOutService, source); } const auto mainEmojiPosition = position + (button->expandUp() @@ -746,20 +751,32 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) { } QRect Manager::validateFrame( - bool outbg, + ButtonStyle style, int frameIndex, float64 scale, const QColor &background, const QColor &shadow) { applyPatternedShadow(shadow); - auto &valid = outbg ? _validOut : _validIn; - auto &color = outbg ? _backgroundOut : _backgroundIn; + auto &valid = (style == ButtonStyle::Service) + ? _validService + : (style == ButtonStyle::Outgoing) + ? _validOut + : _validIn; + auto &color = (style == ButtonStyle::Service) + ? _backgroundService + : (style == ButtonStyle::Outgoing) + ? _backgroundOut + : _backgroundIn; if (color != background) { color = background; ranges::fill(valid, false); } - const auto columnIndex = outbg ? kOutCacheIndex : kInCacheIndex; + const auto columnIndex = (style == ButtonStyle::Service) + ? kServiceCacheIndex + : (style == ButtonStyle::Outgoing) + ? kOutCacheIndex + : kInCacheIndex; const auto result = cacheRect(frameIndex, columnIndex); if (valid[frameIndex]) { return result; @@ -767,7 +784,7 @@ QRect Manager::validateFrame( const auto shadowSource = validateShadow(frameIndex, scale, shadow); const auto position = result.topLeft() / style::DevicePixelRatio(); - auto p = QPainter(&_cacheInOut); + auto p = QPainter(&_cacheInOutService); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawImage(position, _cacheParts, shadowSource); p.setCompositionMode(QPainter::CompositionMode_SourceOver); diff --git a/Telegram/SourceFiles/history/view/history_view_react_button.h b/Telegram/SourceFiles/history/view/history_view_react_button.h index 5ded1dd43..cf6615a5a 100644 --- a/Telegram/SourceFiles/history/view/history_view_react_button.h +++ b/Telegram/SourceFiles/history/view/history_view_react_button.h @@ -27,7 +27,9 @@ struct TextState; namespace HistoryView::Reactions { enum class ButtonStyle { - Bubble, + Incoming, + Outgoing, + Service, }; enum class ExpandDirection { @@ -46,11 +48,10 @@ struct ButtonParameters { FullMsgId context; QPoint center; QPoint pointer; - ButtonStyle style = ButtonStyle::Bubble; + ButtonStyle style = ButtonStyle::Incoming; int reactionsCount = 1; int visibleTop = 0; int visibleBottom = 0; - bool outbg = false; }; enum class ButtonState { @@ -70,7 +71,7 @@ public: using State = ButtonState; void applyState(State state); - [[nodiscard]] bool outbg() const; + [[nodiscard]] ButtonStyle style() const; [[nodiscard]] bool expandUp() const; [[nodiscard]] bool isHidden() const; [[nodiscard]] QRect geometry() const; @@ -101,7 +102,7 @@ private: int _finalHeight = 0; int _scroll = 0; ExpandDirection _expandDirection = ExpandDirection::Up; - bool _outbg = false; + ButtonStyle _style = ButtonStyle::Incoming; }; @@ -171,7 +172,7 @@ private: const QColor &shadow); QRect validateEmoji(int frameIndex, float64 scale); QRect validateFrame( - bool outbg, + ButtonStyle style, int frameIndex, float64 scale, const QColor &background, @@ -198,17 +199,19 @@ private: QSize _outer; QRectF _inner; QRect _innerActive; - QImage _cacheInOut; + QImage _cacheInOutService; QImage _cacheParts; QImage _cacheForPattern; QImage _shadowBuffer; std::array _validIn = { { false } }; std::array _validOut = { { false } }; + std::array _validService = { { false } }; std::array _validShadow = { { false } }; std::array _validEmoji = { { false } }; std::array _validMask = { { false } }; QColor _backgroundIn; QColor _backgroundOut; + QColor _backgroundService; QColor _shadow; std::shared_ptr _mainReactionMedia; diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/history_view_reactions.cpp index 500d7470f..c8c5d788a 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reactions.cpp @@ -22,6 +22,11 @@ namespace { constexpr auto kInNonChosenOpacity = 0.12; constexpr auto kOutNonChosenOpacity = 0.18; +[[nodiscard]] QColor AdaptChosenServiceFg(QColor serviceBg) { + serviceBg.setAlpha(std::max(serviceBg.alpha(), 192)); + return serviceBg; +} + } // namespace InlineList::InlineList( @@ -146,6 +151,14 @@ QSize InlineList::countCurrentSize(int newWidth) { return { newWidth, height + add }; } +int InlineList::placeAndResizeGetHeight(QRect available) { + const auto result = resizeGetHeight(available.width()); + for (auto &button : _buttons) { + button.geometry.translate(available.x(), 0); + } + return result; +} + void InlineList::paint( Painter &p, const PaintContext &context, @@ -173,9 +186,7 @@ void InlineList::paint( } p.setBrush(stm->msgFileBg); } else { - p.setBrush(chosen - ? st->msgServiceBgSelected() - : st->msgServiceBg()); + p.setBrush(chosen ? st->msgServiceFg() : st->msgServiceBg()); } const auto radius = geometry.height() / 2.; p.drawRoundedRect(geometry, radius, radius); @@ -192,7 +203,9 @@ void InlineList::paint( p.drawImage(inner.topLeft(), button.image); } p.setPen(!inbubble - ? st->msgServiceFg() + ? (chosen + ? QPen(AdaptChosenServiceFg(st->msgServiceBg()->c)) + : st->msgServiceFg()) : !chosen ? stm->msgServiceFg : context.outbg diff --git a/Telegram/SourceFiles/history/view/history_view_reactions.h b/Telegram/SourceFiles/history/view/history_view_reactions.h index 7bdfdd5b4..ef9c2b60c 100644 --- a/Telegram/SourceFiles/history/view/history_view_reactions.h +++ b/Telegram/SourceFiles/history/view/history_view_reactions.h @@ -48,6 +48,7 @@ public: void update(Data &&data, int availableWidth); QSize countCurrentSize(int newWidth) override; + [[nodiscard]] int placeAndResizeGetHeight(QRect available); void updateSkipBlock(int width, int height); void removeSkipBlock(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 6b65422e4..64f46c419 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1170,6 +1170,27 @@ bool Gif::needsBubble() const { return false; } +QRect Gif::contentRectForReactionButton() const { + if (isSeparateRoundVideo()) { + return QRect(0, 0, width(), height()); + } + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + auto usex = 0, usew = paintw; + const auto outbg = _parent->hasOutLayout(); + const auto item = _parent->data(); + const auto via = item->Get(); + const auto reply = _parent->displayedReply(); + const auto forwarded = item->Get(); + if (via || reply || forwarded) { + usew = maxWidth() - additionalWidth(via, reply, forwarded); + if (outbg) { + usex = width() - usew; + } + } + if (rtl()) usex = width() - usex - usew; + return style::rtlrect(usex + paintx, painty, usew, painth, width()); +} + int Gif::additionalWidth() const { const auto item = _parent->data(); return additionalWidth( diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 492fbfa21..6161662a5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -96,6 +96,7 @@ public: bool customInfoLayout() const override { return _caption.isEmpty(); } + QRect contentRectForReactionButton() const override; QString additionalInfoString() const override; bool skipBubbleTail() const override { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index cc80266ec..446a7aa3e 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -345,6 +345,10 @@ bool Location::needsBubble() const { || _parent->displayFromName(); } +QRect Location::contentRectForReactionButton() const { + return QRect(0, 0, width(), height()); +} + int Location::fullWidth() const { return st::locationSize.width(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.h b/Telegram/SourceFiles/history/view/media/history_view_location.h index 7c182bb99..ab7882e75 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.h +++ b/Telegram/SourceFiles/history/view/media/history_view_location.h @@ -53,6 +53,7 @@ public: bool customInfoLayout() const override { return true; } + QRect contentRectForReactionButton() const override; bool skipBubbleTail() const override { return isRoundedInBubbleBottom(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 139e26f0a..5dbe00a65 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -205,6 +205,9 @@ public: } [[nodiscard]] virtual bool needsBubble() const = 0; [[nodiscard]] virtual bool customInfoLayout() const = 0; + [[nodiscard]] virtual QRect contentRectForReactionButton() const { + Unexpected("Media::contentRectForReactionButton"); + } [[nodiscard]] virtual QMargins bubbleMargins() const { return QMargins(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 87ce0df1a..a1b22471c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -730,6 +730,10 @@ bool GroupedMedia::needsBubble() const { return _needBubble; } +QRect GroupedMedia::contentRectForReactionButton() const { + return QRect(0, 0, width(), height()); +} + bool GroupedMedia::computeNeedBubble() const { if (!_caption.isEmpty() || _mode == Mode::Column) { return true; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index f31b69971..4680758aa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -84,6 +84,7 @@ public: bool customInfoLayout() const override { return _caption.isEmpty() && (_mode != Mode::Column); } + QRect contentRectForReactionButton() const override; bool allowsFastShare() const override { return true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index b2980977f..3bce0244d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -401,6 +401,37 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { return result; } +QRect UnwrappedMedia::contentRectForReactionButton() const { + const auto inWebPage = (_parent->media() != this); + if (inWebPage) { + return QRect(0, 0, width(), height()); + } + const auto rightAligned = _parent->hasOutLayout() + && !_parent->delegate()->elementIsChatWide(); + const auto item = _parent->data(); + const auto via = item->Get(); + const auto reply = _parent->displayedReply(); + const auto forwarded = getDisplayedForwardedInfo(); + auto usex = 0; + auto usew = maxWidth(); + if (!inWebPage) { + usew -= additionalWidth(via, reply, forwarded); + if (rightAligned) { + usex = width() - usew; + } + } + if (rtl()) { + usex = width() - usex - usew; + } + const auto usey = rightAligned ? 0 : (height() - _contentSize.height()); + const auto useh = rightAligned + ? std::max( + _contentSize.height(), + height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height) + : _contentSize.height(); + return QRect(usex, usey, usew, useh); +} + std::unique_ptr UnwrappedMedia::stickerTakeLottie( not_null data, const Lottie::ColorReplacements *replacements) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h index c72f7e518..9c8b0cf58 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -81,6 +81,7 @@ public: bool customInfoLayout() const override { return true; } + QRect contentRectForReactionButton() const override; void stickerClearLoopPlayed() override { _content->stickerClearLoopPlayed(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 0d0b308e5..1949d82e9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -831,6 +831,10 @@ bool Photo::needsBubble() const { || _parent->displayFromName()); } +QRect Photo::contentRectForReactionButton() const { + return QRect(0, 0, width(), height()); +} + bool Photo::isReadyForOpen() const { ensureDataMediaCreated(); return _dataMedia->loaded(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 3c60661ec..4c4ea720d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -82,6 +82,7 @@ public: bool customInfoLayout() const override { return _caption.isEmpty(); } + QRect contentRectForReactionButton() const override; bool skipBubbleTail() const override { return isRoundedInBubbleBottom() && _caption.isEmpty(); }