From 4e0490494e90c508a2a7a52a0d9c8c89312459d6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Nov 2023 13:25:11 +0400 Subject: [PATCH] Extract reply view to a separate component. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/history/history_item.cpp | 6 +- .../history/history_item_components.cpp | 713 +--------------- .../history/history_item_components.h | 74 +- .../history/view/history_view_element.cpp | 11 +- .../history/view/history_view_element.h | 3 +- .../history/view/history_view_message.cpp | 80 +- .../history/view/history_view_message.h | 4 +- .../history/view/history_view_reply.cpp | 804 ++++++++++++++++++ .../history/view/history_view_reply.h | 116 +++ .../media/history_view_extended_preview.cpp | 2 +- .../history/view/media/history_view_gif.cpp | 41 +- .../history/view/media/history_view_gif.h | 3 +- .../view/media/history_view_location.cpp | 2 +- .../view/media/history_view_media_grouped.cpp | 2 +- .../media/history_view_media_unwrapped.cpp | 45 +- .../view/media/history_view_media_unwrapped.h | 7 +- .../history/view/media/history_view_photo.cpp | 2 +- 18 files changed, 1037 insertions(+), 880 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_reply.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_reply.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 00f597231..3ad594344 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -770,6 +770,8 @@ PRIVATE history/view/history_view_quick_action.h history/view/history_view_replies_section.cpp history/view/history_view_replies_section.h + history/view/history_view_reply.cpp + history/view/history_view_reply.h history/view/history_view_requests_bar.cpp history/view/history_view_requests_bar.h history/view/history_view_schedule_box.cpp diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index b4949c93f..f233b08d0 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -723,7 +723,7 @@ HistoryItem::HistoryItem( HistoryItem::~HistoryItem() { _media = nullptr; clearSavedMedia(); - if (auto reply = Get()) { + if (const auto reply = Get()) { reply->clearData(this); } clearDependencyMessage(); @@ -1674,7 +1674,6 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setForwardsCount(data.vforwards().value_or(-1)); if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - // #TODO replies const auto replyToPeer = data.vreply_to_peer_id() ? peerFromMTP(*data.vreply_to_peer_id()) : PeerId(); @@ -1980,9 +1979,6 @@ void HistoryItem::setRealId(MsgId newId) { _history->owner().requestItemResize(this); if (const auto reply = Get()) { - if (reply->link()) { - reply->setLinkFrom(this); - } incrementReplyToTopCounter(); } } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 5ae8652af..c92f9f855 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -55,130 +55,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -constexpr auto kNonExpandedLinesLimit = 5; const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; -void ValidateBackgroundEmoji( - DocumentId backgroundEmojiId, - not_null data, - not_null cache, - not_null quote, - not_null holder) { - if (data->firstFrameMask.isNull()) { - if (!cache->frames[0].isNull()) { - for (auto &frame : cache->frames) { - frame = QImage(); - } - } - const auto tag = Data::CustomEmojiSizeTag::Isolated; - if (!data->emoji) { - const auto owner = &holder->history()->owner(); - const auto repaint = crl::guard(holder, [=] { - holder->history()->owner().requestViewRepaint(holder); - }); - data->emoji = owner->customEmojiManager().create( - backgroundEmojiId, - repaint, - tag); - } - if (!data->emoji->ready()) { - return; - } - const auto size = Data::FrameSizeFromTag(tag); - data->firstFrameMask = QImage( - QSize(size, size), - QImage::Format_ARGB32_Premultiplied); - data->firstFrameMask.fill(Qt::transparent); - data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio()); - auto p = Painter(&data->firstFrameMask); - data->emoji->paint(p, { - .textColor = QColor(255, 255, 255), - .position = QPoint(0, 0), - .internal = { - .forceFirstFrame = true, - }, - }); - p.end(); - - data->emoji = nullptr; - } - if (!cache->frames[0].isNull() && cache->color == quote->icon) { - return; - } - cache->color = quote->icon; - const auto ratio = style::DevicePixelRatio(); - auto colorized = QImage( - data->firstFrameMask.size(), - QImage::Format_ARGB32_Premultiplied); - colorized.setDevicePixelRatio(ratio); - style::colorizeImage( - data->firstFrameMask, - cache->color, - &colorized, - QRect(), // src - QPoint(), // dst - true); // use alpha - const auto make = [&](int size) { - size = style::ConvertScale(size) * ratio; - auto result = colorized.scaled( - size, - size, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - result.setDevicePixelRatio(ratio); - return result; - }; - - constexpr auto kSize1 = 12; - constexpr auto kSize2 = 16; - constexpr auto kSize3 = 20; - cache->frames[0] = make(kSize1); - cache->frames[1] = make(kSize2); - cache->frames[2] = make(kSize3); -} - -void FillBackgroundEmoji( - Painter &p, - const QRect &rect, - bool quote, - const Ui::BackgroundEmojiCache &cache) { - p.setClipRect(rect); - - const auto &frames = cache.frames; - const auto right = rect.x() + rect.width(); - const auto paint = [&](int x, int y, int index, float64 opacity) { - y = style::ConvertScale(y); - if (y >= rect.height()) { - return; - } - p.setOpacity(opacity); - p.drawImage( - right - style::ConvertScale(x + (quote ? 12 : 0)), - rect.y() + y, - frames[index]); - }; - - paint(28, 4, 2, 0.32); - paint(51, 15, 1, 0.32); - paint(64, -2, 0, 0.28); - paint(87, 11, 1, 0.24); - paint(125, -2, 2, 0.16); - - paint(28, 31, 1, 0.24); - paint(72, 33, 2, 0.2); - - paint(46, 52, 1, 0.24); - paint(24, 55, 2, 0.18); - - if (quote) { - paint(4, 23, 1, 0.28); - paint(0, 48, 0, 0.24); - } - - p.setClipping(false); - p.setOpacity(1.); -} - } // namespace void HistoryMessageVia::create( @@ -471,10 +349,7 @@ FullReplyTo ReplyToFromMTP( }); } -HistoryMessageReply::HistoryMessageReply() -: _name(st::maxSignatureSize / 2) -, _text(st::maxSignatureSize / 2) { -} +HistoryMessageReply::HistoryMessageReply() = default; HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply &&other) = default; @@ -527,11 +402,6 @@ bool HistoryMessageReply::updateData( } } - const auto repaint = [=] { holder->customEmojiRepaint(); }; - const auto context = Core::MarkedTextContext{ - .session = &holder->history()->session(), - .customEmojiRepaint = repaint, - }; const auto external = this->external(); _multiline = !_fields.storyId && (external || !_fields.quote.empty()); @@ -545,38 +415,10 @@ bool HistoryMessageReply::updateData( && ((!_fields.storyId && !_fields.messageId) || force); _unavailable = unavailable ? 1 : 0; - const auto text = !_fields.quote.empty() - ? _fields.quote - : resolvedMessage - ? resolvedMessage->inReplyText() - : resolvedStory - ? resolvedStory->inReplyText() - : TextWithEntities{ u"..."_q }; - _text.setMarkedText( - st::defaultTextStyle, - text, - Ui::DialogTextOptions(), - context); - - updateName(holder); - - if (_displaying) { - setLinkFrom(holder); - const auto media = resolvedMessage - ? resolvedMessage->media() - : nullptr; - if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) { - spoiler = nullptr; - } else if (!spoiler) { - spoiler = std::make_unique(repaint); - } - } else if (force) { - if (_fields.messageId || _fields.storyId) { + if (force) { + if (!_displaying && (_fields.messageId || _fields.storyId)) { _unavailable = 1; } - spoiler = nullptr; - } - if (force) { holder->history()->owner().requestItemResize(holder); } return resolvedMessage @@ -611,74 +453,6 @@ void HistoryMessageReply::updateFields( } } -bool HistoryMessageReply::expand() { - if (!_expandable || _expanded) { - return false; - } - _expanded = true; - return true; -} - -void HistoryMessageReply::setLinkFrom( - not_null holder) { - const auto externalChannelId = peerToChannel(_fields.externalPeerId); - const auto messageId = _fields.messageId; - const auto quote = _fields.manualQuote - ? _fields.quote - : TextWithEntities(); - const auto returnToId = holder->fullId(); - const auto externalLink = [=](ClickContext context) { - const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - auto error = QString(); - const auto owner = &controller->session().data(); - if (const auto item = owner->message(returnToId)) { - if (const auto reply = item->Get()) { - if (reply->expand()) { - owner->requestItemResize(item); - return; - } - } - } - if (externalChannelId) { - const auto channel = owner->channel(externalChannelId); - if (!channel->isForbidden()) { - if (messageId) { - JumpToMessageClickHandler( - channel, - messageId, - returnToId, - quote - )->onClick(context); - } else { - controller->showPeerInfo(channel); - } - } else if (channel->isBroadcast()) { - error = tr::lng_channel_not_accessible(tr::now); - } else { - error = tr::lng_group_not_accessible(tr::now); - } - } else { - error = tr::lng_reply_from_private_chat(tr::now); - } - if (!error.isEmpty()) { - controller->showToast(error); - } - } - }; - _link = resolvedMessage - ? JumpToMessageClickHandler( - resolvedMessage.get(), - returnToId, - quote) - : resolvedStory - ? JumpToStoryClickHandler(resolvedStory.get()) - : (external() - && (!_fields.messageId || (_unavailable && externalChannelId))) - ? std::make_shared(externalLink) - : nullptr; -} - void HistoryMessageReply::setTopMessageId(MsgId topMessageId) { _fields.topMessageId = topMessageId; } @@ -696,11 +470,8 @@ void HistoryMessageReply::clearData(not_null holder) { resolvedStory.get()); resolvedStory = nullptr; } - _name.clear(); - _text.clear(); _unavailable = 1; _displaying = 0; - _expandable = 0; if (_multiline) { holder->history()->owner().requestItemResize(holder); _multiline = 0; @@ -714,236 +485,6 @@ bool HistoryMessageReply::external() const { || !_fields.externalSenderName.isEmpty(); } -PeerData *HistoryMessageReply::sender(not_null holder) const { - if (resolvedStory) { - return resolvedStory->peer(); - } else if (!resolvedMessage) { - if (!_externalSender && _fields.externalSenderId) { - _externalSender = holder->history()->owner().peer( - _fields.externalSenderId); - } - return _externalSender; - } else if (holder->Has()) { - // Forward of a reply. Show reply-to original sender. - const auto forwarded - = resolvedMessage->Get(); - if (forwarded) { - return forwarded->originalSender; - } - } - if (const auto from = resolvedMessage->displayFrom()) { - return from; - } - return resolvedMessage->author().get(); -} - -QString HistoryMessageReply::senderName( - not_null holder, - bool shorten) const { - if (const auto peer = sender(holder)) { - return senderName(peer, shorten); - } else if (!resolvedMessage) { - return _fields.externalSenderName; - } else if (holder->Has()) { - // Forward of a reply. Show reply-to original sender. - const auto forwarded - = resolvedMessage->Get(); - if (forwarded) { - Assert(forwarded->hiddenSenderInfo != nullptr); - return forwarded->hiddenSenderInfo->name; - } - } - return QString(); -} - -QString HistoryMessageReply::senderName( - not_null peer, - bool shorten) const { - const auto user = shorten ? peer->asUser() : nullptr; - return user ? user->firstName : peer->name(); -} - -bool HistoryMessageReply::isNameUpdated( - not_null holder) const { - if (const auto from = sender(holder)) { - if (_nameVersion < from->nameVersion()) { - updateName(holder, from); - return true; - } - } - return false; -} - -void HistoryMessageReply::updateName( - not_null holder, - std::optional resolvedSender) const { - auto viaBotUsername = QString(); - if (resolvedMessage - && !resolvedMessage->Has()) { - if (const auto bot = resolvedMessage->viaBot()) { - viaBotUsername = bot->username(); - } - } - const auto sender = resolvedSender.value_or(this->sender(holder)); - const auto externalPeer = _fields.externalPeerId - ? holder->history()->owner().peer(_fields.externalPeerId).get() - : nullptr; - const auto groupNameAdded = (externalPeer && externalPeer != sender); - const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; - const auto name = sender - ? senderName(sender, shorten) - : senderName(holder, shorten); - const auto hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - const auto previewSkip = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left()) - : 0; - const auto peerIcon = [](PeerData *peer) { - return !peer - ? &st::historyReplyUser - : peer->isBroadcast() - ? &st::historyReplyChannel - : (peer->isChannel() || peer->isChat()) - ? &st::historyReplyGroup - : &st::historyReplyUser; - }; - const auto peerEmoji = [&](PeerData *peer) { - const auto owner = &holder->history()->owner(); - return Ui::Text::SingleCustomEmoji( - owner->customEmojiManager().registerInternalEmoji( - *peerIcon(peer))); - }; - auto nameFull = TextWithEntities(); - if (!groupNameAdded && external() && !_fields.storyId) { - nameFull.append(peerEmoji(sender)); - } - nameFull.append(name); - if (groupNameAdded) { - nameFull.append(peerEmoji(externalPeer)); - nameFull.append(externalPeer->name()); - } - if (!viaBotUsername.isEmpty()) { - nameFull.append(u" @"_q).append(viaBotUsername); - } - const auto context = Core::MarkedTextContext{ - .session = &holder->history()->session(), - .customEmojiRepaint = [] {}, - .customEmojiLoopLimit = 1, - }; - _name.setMarkedText( - st::fwdTextStyle, - nameFull, - Ui::NameTextOptions(), - context); - if (sender) { - _nameVersion = sender->nameVersion(); - } - const auto nameMaxWidth = previewSkip - + _name.maxWidth() - + (hasQuoteIcon() - ? st::messageTextStyle.blockquote.icon.width() - : 0); - const auto storySkip = _fields.storyId - ? (st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width()) - : 0; - const auto optimalTextSize = _multiline - ? countMultilineOptimalSize(previewSkip) - : QSize( - (previewSkip - + storySkip - + std::min(_text.maxWidth(), st::maxSignatureSize)), - st::normalFont->height); - _maxWidth = std::max(nameMaxWidth, optimalTextSize.width()); - if (!_displaying) { - const auto phraseWidth = st::msgDateFont->width(statePhrase()); - _maxWidth = _unavailable - ? phraseWidth - : std::max(_maxWidth, phraseWidth); - } - _maxWidth = st::historyReplyPadding.left() - + _maxWidth - + st::historyReplyPadding.right(); - _minHeight = st::historyReplyPadding.top() - + st::msgServiceNameFont->height - + optimalTextSize.height() - + st::historyReplyPadding.bottom(); -} - -int HistoryMessageReply::resizeToWidth(int width) const { - const auto hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - const auto previewSkip = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left()) - : 0; - if (width >= _maxWidth || !_multiline) { - _nameTwoLines = 0; - _expandable = 0; - _height = _minHeight; - return height(); - } - const auto innerw = width - - st::historyReplyPadding.left() - - st::historyReplyPadding.right(); - const auto namew = innerw - previewSkip; - const auto desiredNameHeight = _name.countHeight(namew); - _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; - const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; - const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; - auto lineCounter = 0; - auto elided = false; - const auto texth = _text.countDimensions( - textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; - _expandable = (_multiline && elided) ? 1 : 0; - _height = st::historyReplyPadding.top() - + nameh - + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) - + st::historyReplyPadding.bottom(); - return height(); -} - -Ui::Text::GeometryDescriptor HistoryMessageReply::textGeometry( - int available, - int firstLineSkip, - not_null line, - not_null outElided) const { - return { .layout = [=](Ui::Text::LineGeometry in) { - const auto skip = (*line ? 0 : firstLineSkip); - ++*line; - *outElided = *outElided - || !_multiline - || (!_expanded - && (*line == kNonExpandedLinesLimit) - && in.width > available - skip); - in.width = available - skip; - in.left += skip; - in.elided = *outElided; - return in; - } }; -} - -int HistoryMessageReply::height() const { - return _height + st::historyReplyTop + st::historyReplyBottom; -} - -QMargins HistoryMessageReply::margins() const { - return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom); -} - void HistoryMessageReply::itemRemoved( not_null holder, not_null removed) { @@ -962,254 +503,6 @@ void HistoryMessageReply::storyRemoved( } } -bool HistoryMessageReply::hasQuoteIcon() const { - return _fields.manualQuote && !_fields.quote.empty(); -} - -QSize HistoryMessageReply::countMultilineOptimalSize( - int previewSkip) const { - auto elided = false; - auto lineCounter = 0; - const auto max = previewSkip + _text.maxWidth(); - const auto result = _text.countDimensions( - textGeometry(max, previewSkip, &lineCounter, &elided)); - return { result.width, result.height }; -} - -void HistoryMessageReply::paint( - Painter &p, - not_null holder, - const Ui::ChatPaintContext &context, - int x, - int y, - int w, - bool inBubble) const { - const auto st = context.st; - const auto stm = context.messageStyle(); - - y += st::historyReplyTop; - const auto rect = QRect(x, y, w, _height); - const auto hasQuote = hasQuoteIcon(); - const auto selected = context.selected(); - const auto colorPeer = resolvedMessage - ? resolvedMessage->displayFrom() - : resolvedStory - ? resolvedStory->peer().get() - : _externalSender - ? _externalSender - : nullptr; - const auto backgroundEmojiId = colorPeer - ? colorPeer->backgroundEmojiId() - : DocumentId(); - const auto colorIndexPlusOne = colorPeer - ? (colorPeer->colorIndex() + 1) - : resolvedMessage - ? (resolvedMessage->hiddenSenderInfo()->colorIndex + 1) - : 0; - const auto useColorIndex = colorIndexPlusOne && !context.outbg; - const auto colorPattern = colorIndexPlusOne - ? st->colorPatternIndex(colorIndexPlusOne - 1) - : 0; - const auto cache = !inBubble - ? (hasQuote - ? st->serviceQuoteCache(colorPattern) - : st->serviceReplyCache(colorPattern)).get() - : useColorIndex - ? (hasQuote - ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) - : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() - : (hasQuote - ? stm->quoteCache[colorPattern] - : stm->replyCache[colorPattern]).get(); - const auto "eSt = hasQuote - ? st::messageTextStyle.blockquote - : st::messageQuoteStyle; - const auto backgroundEmoji = backgroundEmojiId - ? st->backgroundEmojiData(backgroundEmojiId).get() - : nullptr; - const auto backgroundEmojiCache = backgroundEmoji - ? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex( - selected, - context.outbg, - inBubble, - colorIndexPlusOne)] - : nullptr; - const auto rippleColor = cache->bg; - if (!inBubble) { - cache->bg = QColor(0, 0, 0, 0); - } - Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); - Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); - if (backgroundEmoji) { - ValidateBackgroundEmoji( - backgroundEmojiId, - backgroundEmoji, - backgroundEmojiCache, - cache, - holder); - if (!backgroundEmojiCache->frames[0].isNull()) { - FillBackgroundEmoji(p, rect, hasQuote, *backgroundEmojiCache); - } - } - if (!inBubble) { - cache->bg = rippleColor; - } - - if (ripple.animation) { - ripple.animation->paint(p, x, y, w, &rippleColor); - if (ripple.animation->empty()) { - ripple.animation.reset(); - } - } - - auto hasPreview = (resolvedStory - && resolvedStory->hasReplyPreview()) - || (resolvedMessage - && resolvedMessage->media() - && resolvedMessage->media()->hasReplyPreview()); - auto previewSkip = hasPreview - ? (st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left()) - : 0; - if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) { - hasPreview = false; - previewSkip = 0; - } - - const auto pausedSpoiler = context.paused - || On(PowerSaving::kChatSpoiler); - auto textLeft = x + st::historyReplyPadding.left(); - auto textTop = y - + st::historyReplyPadding.top() - + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); - if (w > st::historyReplyPadding.left()) { - if (_displaying) { - const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; - if (hasPreview) { - const auto image = media - ? media->replyPreview() - : resolvedStory->replyPreview(); - if (image) { - auto to = style::rtlrect( - x + st::historyReplyPreviewMargin.left(), - y + st::historyReplyPreviewMargin.top(), - st::historyReplyPreview, - st::historyReplyPreview, - w + 2 * x); - const auto preview = image->pixSingle( - image->size() / style::DevicePixelRatio(), - { - .colored = (context.selected() - ? &st->msgStickerOverlay() - : nullptr), - .options = Images::Option::RoundSmall, - .outer = to.size(), - }); - p.drawPixmap(to.x(), to.y(), preview); - if (spoiler) { - holder->clearCustomEmojiRepaint(); - Ui::FillSpoilerRect( - p, - to, - Ui::DefaultImageSpoiler().frame( - spoiler->index( - context.now, - pausedSpoiler))); - } - } - } - const auto textw = w - - st::historyReplyPadding.left() - - st::historyReplyPadding.right(); - const auto namew = textw - previewSkip; - auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; - if (namew > 0) { - p.setPen(!inBubble - ? st->msgImgReplyBarColor()->c - : useColorIndex - ? FromNameFg(context, colorIndexPlusOne - 1) - : stm->msgServiceFg->c); - _name.drawLeftElided( - p, - x + st::historyReplyPadding.left() + previewSkip, - y + st::historyReplyPadding.top(), - namew, - w + 2 * x, - _nameTwoLines ? 2 : 1); - - p.setPen(inBubble - ? stm->historyTextFg - : st->msgImgReplyBarColor()); - holder->prepareCustomEmojiPaint(p, context, _text); - auto replyToTextPalette = &(!inBubble - ? st->imgReplyTextPalette() - : useColorIndex - ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) - : stm->replyTextPalette); - if (_fields.storyId) { - st::dialogsMiniReplyStory.icon.icon.paint( - p, - textLeft + firstLineSkip, - textTop, - w + 2 * x, - replyToTextPalette->linkFg->c); - firstLineSkip += st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width(); - } - auto owned = std::optional(); - auto copy = std::optional(); - if (inBubble && colorIndexPlusOne) { - copy.emplace(*replyToTextPalette); - owned.emplace(cache->icon); - copy->linkFg = owned->color(); - replyToTextPalette = &*copy; - } - auto l = 0; - auto e = false; - _text.draw(p, { - .position = { textLeft, textTop }, - .geometry = textGeometry(textw, firstLineSkip, &l, &e), - .palette = replyToTextPalette, - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = (context.paused - || On(PowerSaving::kEmojiChat)), - .pausedSpoiler = pausedSpoiler, - .elisionOneLine = true, - }); - p.setTextPalette(stm->textPalette); - } - } else { - p.setFont(st::msgDateFont); - p.setPen(cache->icon); - p.drawTextLeft( - textLeft, - (y - + st::historyReplyPadding.top() - + (st::msgDateFont->height / 2)), - w + 2 * x, - st::msgDateFont->elided( - statePhrase(), - x + w - textLeft - st::historyReplyPadding.right())); - } - } -} - -void HistoryMessageReply::unloadPersistentAnimation() { - _text.unloadPersistentAnimation(); -} - -QString HistoryMessageReply::statePhrase() const { - return ((_fields.messageId || _fields.storyId) && !_unavailable) - ? tr::lng_profile_loading(tr::now) - : _fields.storyId - ? tr::lng_deleted_story(tr::now) - : tr::lng_deleted_message(tr::now); -} - void HistoryMessageReply::refreshReplyToMedia() { replyToDocumentId = 0; replyToWebPageId = 0; diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index e4159106f..b23b509b0 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -22,7 +22,6 @@ namespace Ui { struct ChatPaintContext; class ChatStyle; struct PeerUserpicView; -class SpoilerAnimation; } // namespace Ui namespace Ui::Text { @@ -264,8 +263,6 @@ struct HistoryMessageReply HistoryMessageReply &operator=(HistoryMessageReply &&other); ~HistoryMessageReply(); - static constexpr auto kBarAlpha = 230. / 255.; - void set(ReplyFields fields); void updateFields( @@ -279,20 +276,6 @@ struct HistoryMessageReply void clearData(not_null holder); [[nodiscard]] bool external() const; - [[nodiscard]] PeerData *sender(not_null holder) const; - [[nodiscard]] QString senderName( - not_null holder, - bool shorten) const; - [[nodiscard]] QString senderName( - not_null peer, - bool shorten) const; - [[nodiscard]] bool isNameUpdated(not_null holder) const; - void updateName( - not_null holder, - std::optional resolvedSender = std::nullopt) const; - [[nodiscard]] int resizeToWidth(int width) const; - [[nodiscard]] int height() const; - [[nodiscard]] QMargins margins() const; void itemRemoved( not_null holder, not_null removed); @@ -300,19 +283,7 @@ struct HistoryMessageReply not_null holder, not_null removed); - bool expand(); - - void paint( - Painter &p, - not_null holder, - const Ui::ChatPaintContext &context, - int x, - int y, - int w, - bool inBubble) const; - void unloadPersistentAnimation(); - - [[nodiscard]] ReplyFields fields() const { + [[nodiscard]] const ReplyFields &fields() const { return _fields; } [[nodiscard]] PeerId externalPeerId() const { @@ -327,21 +298,22 @@ struct HistoryMessageReply [[nodiscard]] MsgId topMessageId() const { return _fields.topMessageId; } - [[nodiscard]] int maxWidth() const { - return _maxWidth; - } - [[nodiscard]] ClickHandlerPtr link() const { - return _link; - } [[nodiscard]] bool topicPost() const { return _fields.topicPost; } [[nodiscard]] bool manualQuote() const { return _fields.manualQuote; } - [[nodiscard]] QString statePhrase() const; + [[nodiscard]] bool unavailable() const { + return _unavailable; + } + [[nodiscard]] bool displaying() const { + return _displaying; + } + [[nodiscard]] bool multiline() const { + return _multiline; + } - void setLinkFrom(not_null holder); void setTopMessageId(MsgId topMessageId); void refreshReplyToMedia(); @@ -350,38 +322,12 @@ struct HistoryMessageReply WebPageId replyToWebPageId = 0; ReplyToMessagePointer resolvedMessage; ReplyToStoryPointer resolvedStory; - std::unique_ptr spoiler; - - struct { - mutable std::unique_ptr animation; - QPoint lastPoint; - } ripple; private: - [[nodiscard]] bool hasQuoteIcon() const; - [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( - int available, - int firstLineSkip, - not_null line, - not_null outElided) const; - [[nodiscard]] QSize countMultilineOptimalSize( - int firstLineSkip) const; - ReplyFields _fields; - ClickHandlerPtr _link; - mutable Ui::Text::String _name; - mutable Ui::Text::String _text; - mutable PeerData *_externalSender = nullptr; - mutable int _maxWidth = 0; - mutable int _minHeight = 0; - mutable int _height = 0; - mutable int _nameVersion = 0; uint8 _unavailable : 1 = 0; uint8 _displaying : 1 = 0; uint8 _multiline : 1 = 0; - mutable uint8 _expandable : 1 = 0; - uint8 _expanded : 1 = 0; - mutable uint8 _nameTwoLines : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index eeec9d86b..329ed41f3 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/reactions/history_view_reactions.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_reply.h" #include "history/view/history_view_spoiler_click_handler.h" #include "history/history.h" #include "history/history_item.h" @@ -1362,6 +1363,10 @@ bool Element::hasFromName() const { return false; } +bool Element::displayReply() const { + return Has(); +} + bool Element::displayFromName() const { return false; } @@ -1419,10 +1424,6 @@ TimeId Element::displayedEditDate() const { return TimeId(0); } -HistoryMessageReply *Element::displayedReply() const { - return nullptr; -} - bool Element::toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const { return false; @@ -1482,7 +1483,7 @@ void Element::unloadHeavyPart() { if (_flags & Flag::HeavyCustomEmoji) { _flags &= ~Flag::HeavyCustomEmoji; _text.unloadPersistentAnimation(); - if (const auto reply = data()->Get()) { + if (const auto reply = Get()) { reply->unloadPersistentAnimation(); } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index db3933b98..4da9f4b9c 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -49,6 +49,7 @@ enum class InfoDisplayType : char; struct StateRequest; struct TextState; class Media; +class Reply; enum class Context : char { History, @@ -433,6 +434,7 @@ public: [[nodiscard]] virtual bool hasFromPhoto() const; [[nodiscard]] virtual bool displayFromPhoto() const; [[nodiscard]] virtual bool hasFromName() const; + [[nodiscard]] bool displayReply() const; [[nodiscard]] virtual bool displayFromName() const; [[nodiscard]] virtual TopicButton *displayedTopicButton() const; [[nodiscard]] virtual bool displayForwardedFrom() const; @@ -456,7 +458,6 @@ public: std::optional pressPoint) const; [[nodiscard]] virtual TimeId displayedEditDate() const; [[nodiscard]] virtual bool hasVisibleText() const; - [[nodiscard]] virtual HistoryMessageReply *displayedReply() const; virtual void applyGroupAdminChanges( const base::flat_set &changes) { } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 833702101..115b2e76c 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/reactions/history_view_reactions.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_group_call_bar.h" // UserpicInRow. +#include "history/view/history_view_reply.h" #include "history/view/history_view_view_button.h" // ViewButton. #include "history/history.h" #include "boxes/share_box.h" @@ -406,6 +407,7 @@ Message::Message( Element *replacing) : Element(delegate, data, replacing, Flag(0)) , _invertMedia(data->invertMedia() && !data->emptyText()) +, _hideReply(delegate->elementHideReply(this)) , _bottomInfo( &data->history()->owner().reactions(), BottomInfoDataFromMessage(this)) { @@ -597,6 +599,14 @@ auto Message::takeReactionAnimations() QSize Message::performCountOptimalSize() { const auto item = data(); + + const auto replyData = item->Get(); + if (replyData) { + AddComponents(Reply::Bit()); + } else { + RemoveComponents(Reply::Bit()); + } + const auto markup = item->inlineReplyMarkup(); const auto reactionsKey = [&] { return embedReactionsInBottomInfo() @@ -633,17 +643,19 @@ QSize Message::performCountOptimalSize() { if (_reactions) { _reactions->initDimensions(); } + + const auto reply = Get(); + if (reply) { + reply->update(this, replyData); + } + if (drawBubble()) { const auto forwarded = item->Get(); - const auto reply = displayedReply(); const auto via = item->Get(); const auto entry = logEntryOriginal(); if (forwarded) { forwarded->create(via); } - if (reply) { - reply->updateName(item); - } auto mediaDisplayed = false; if (media) { @@ -1217,9 +1229,11 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.restore(); } - if (const auto reply = displayedReply()) { - if (reply->isNameUpdated(data())) { - const_cast(this)->setPendingResize(); + if (const auto reply = Get()) { + if (const auto replyData = item->Get()) { + if (reply->isNameUpdated(this, replyData)) { + const_cast(this)->setPendingResize(); + } } } } @@ -1598,8 +1612,15 @@ void Message::paintReplyInfo( Painter &p, QRect &trect, const PaintContext &context) const { - if (const auto reply = displayedReply()) { - reply->paint(p, this, context, trect.x(), trect.y(), trect.width(), true); + if (const auto reply = Get()) { + reply->paint( + p, + this, + context, + trect.x(), + trect.y(), + trect.width(), + true); trect.setY(trect.y() + reply->height()); } } @@ -1753,7 +1774,7 @@ void Message::clickHandlerPressedChanged( toggleTopicButtonRipple(pressed); } else if (_viewButton) { _viewButton->checkLink(handler, pressed); - } else if (const auto reply = displayedReply() + } else if (const auto reply = Get() ; reply && (handler == reply->link())) { toggleReplyRipple(pressed); } @@ -1796,13 +1817,13 @@ void Message::toggleRightActionRipple(bool pressed) { } void Message::toggleReplyRipple(bool pressed) { - const auto reply = displayedReply(); + const auto reply = Get(); if (!reply) { return; } if (pressed) { - if (!reply->ripple.animation && !unwrapped()) { + if (!unwrapped()) { const auto &padding = st::msgPadding; const auto geometry = countGeometry(); const auto item = data(); @@ -1810,18 +1831,11 @@ void Message::toggleReplyRipple(bool pressed) { const auto size = QSize( geometry.width() - padding.left() - padding.right(), reply->height() - margins.top() - margins.bottom()); - reply->ripple.animation = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - size, - st::messageQuoteStyle.radius), - [=] { item->history()->owner().requestItemRepaint(item); }); + reply->createRippleAnimation(this, size); } - if (reply->ripple.animation) { - reply->ripple.animation->add(reply->ripple.lastPoint); - } - } else if (reply->ripple.animation) { - reply->ripple.animation->lastStop(); + reply->addRipple(); + } else { + reply->stopLastRipple(); } } @@ -2482,7 +2496,7 @@ bool Message::getStateReplyInfo( QPoint point, QRect &trect, not_null outResult) const { - if (const auto reply = displayedReply()) { + if (const auto reply = Get()) { const auto margins = reply->margins(); const auto height = reply->height(); if (point.y() >= trect.top() && point.y() < trect.top() + height) { @@ -2494,7 +2508,7 @@ bool Message::getStateReplyInfo( if (g.contains(point)) { if (const auto link = reply->link()) { outResult->link = reply->link(); - reply->ripple.lastPoint = point - g.topLeft(); + reply->saveRipplePoint(point - g.topLeft()); } } return true; @@ -2577,7 +2591,7 @@ void Message::updatePressed(QPoint point) { auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; trect.setTop(trect.top() + fwdheight); } - if (const auto reply = item->Get()) { + if (const auto reply = Get()) { trect.setTop(trect.top() + reply->height()); } if (const auto via = item->Get()) { @@ -3123,13 +3137,6 @@ WebPage *Message::logEntryOriginal() const { return nullptr; } -HistoryMessageReply *Message::displayedReply() const { - if (const auto reply = data()->Get()) { - return delegate()->elementHideReply(this) ? nullptr : reply; - } - return nullptr; -} - bool Message::toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const { if (_comments && _comments->link == handler) { @@ -3580,7 +3587,7 @@ void Message::updateMediaInBubbleState() { return displayFromName() || displayedTopicButton() || displayForwardedFrom() - || displayedReply() + || Has() || item->Has(); }; auto entry = logEntryOriginal(); @@ -3705,7 +3712,7 @@ QRect Message::innerGeometry() const { + st::topicButtonSkip); } // Skip displayForwardedFrom() until there are no animations for it. - if (const auto reply = displayedReply()) { + if (const auto reply = Get()) { // See paintReplyInfo(). result.translate(0, reply->height()); } @@ -3872,7 +3879,7 @@ int Message::resizeContentGetHeight(int newWidth) { textWidth - 2 * st::msgDateDelta.x())); if (bubble) { - auto reply = displayedReply(); + auto reply = Get(); auto via = item->Get(); auto entry = logEntryOriginal(); @@ -3962,7 +3969,6 @@ int Message::resizeContentGetHeight(int newWidth) { newHeight += reply->resizeToWidth(contentWidth - st::msgPadding.left() - st::msgPadding.right()); - reply->ripple.animation = nullptr; } if (needInfoDisplay()) { newHeight += (bottomInfoHeight - st::msgDateFont->height); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index aeba7c1aa..2b9112c1c 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -137,7 +137,6 @@ public: [[nodiscard]] ClickHandlerPtr rightActionLink( std::optional pressPoint) const override; [[nodiscard]] TimeId displayedEditDate() const override; - [[nodiscard]] HistoryMessageReply *displayedReply() const override; [[nodiscard]] bool toggleSelectionByHandlerClick( const ClickHandlerPtr &handler) const override; [[nodiscard]] bool allowTextSelectionByHandler( @@ -308,8 +307,9 @@ private: mutable std::unique_ptr _fromNameStatus; Ui::Text::String _rightBadge; mutable int _fromNameVersion = 0; - uint32 _bubbleWidthLimit : 31 = 0; + uint32 _bubbleWidthLimit : 30 = 0; uint32 _invertMedia : 1 = 0; + uint32 _hideReply : 1 = 0; BottomInfo _bottomInfo; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp new file mode 100644 index 000000000..5d9722231 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -0,0 +1,804 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_reply.h" + +#include "core/click_handler_types.h" +#include "core/ui_integration.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_channel.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_story.h" +#include "data/data_user.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/history_item_helpers.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/chat/chat_style.h" +#include "ui/effects/ripple_animation.h" +#include "ui/effects/spoiler_mess.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "ui/painter.h" +#include "ui/power_saving.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_dialogs.h" + +namespace HistoryView { +namespace { + +constexpr auto kNonExpandedLinesLimit = 5; + +void ValidateBackgroundEmoji( + DocumentId backgroundEmojiId, + not_null data, + not_null cache, + not_null quote, + not_null view) { + if (data->firstFrameMask.isNull()) { + if (!cache->frames[0].isNull()) { + for (auto &frame : cache->frames) { + frame = QImage(); + } + } + const auto tag = Data::CustomEmojiSizeTag::Isolated; + if (!data->emoji) { + const auto owner = &view->history()->owner(); + const auto repaint = crl::guard(view, [=] { + view->history()->owner().requestViewRepaint(view); + }); + data->emoji = owner->customEmojiManager().create( + backgroundEmojiId, + repaint, + tag); + } + if (!data->emoji->ready()) { + return; + } + const auto size = Data::FrameSizeFromTag(tag); + data->firstFrameMask = QImage( + QSize(size, size), + QImage::Format_ARGB32_Premultiplied); + data->firstFrameMask.fill(Qt::transparent); + data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio()); + auto p = Painter(&data->firstFrameMask); + data->emoji->paint(p, { + .textColor = QColor(255, 255, 255), + .position = QPoint(0, 0), + .internal = { + .forceFirstFrame = true, + }, + }); + p.end(); + + data->emoji = nullptr; + } + if (!cache->frames[0].isNull() && cache->color == quote->icon) { + return; + } + cache->color = quote->icon; + const auto ratio = style::DevicePixelRatio(); + auto colorized = QImage( + data->firstFrameMask.size(), + QImage::Format_ARGB32_Premultiplied); + colorized.setDevicePixelRatio(ratio); + style::colorizeImage( + data->firstFrameMask, + cache->color, + &colorized, + QRect(), // src + QPoint(), // dst + true); // use alpha + const auto make = [&](int size) { + size = style::ConvertScale(size) * ratio; + auto result = colorized.scaled( + size, + size, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + result.setDevicePixelRatio(ratio); + return result; + }; + + constexpr auto kSize1 = 12; + constexpr auto kSize2 = 16; + constexpr auto kSize3 = 20; + cache->frames[0] = make(kSize1); + cache->frames[1] = make(kSize2); + cache->frames[2] = make(kSize3); +} + +void FillBackgroundEmoji( + Painter &p, + const QRect &rect, + bool quote, + const Ui::BackgroundEmojiCache &cache) { + p.setClipRect(rect); + + const auto &frames = cache.frames; + const auto right = rect.x() + rect.width(); + const auto paint = [&](int x, int y, int index, float64 opacity) { + y = style::ConvertScale(y); + if (y >= rect.height()) { + return; + } + p.setOpacity(opacity); + p.drawImage( + right - style::ConvertScale(x + (quote ? 12 : 0)), + rect.y() + y, + frames[index]); + }; + + paint(28, 4, 2, 0.32); + paint(51, 15, 1, 0.32); + paint(64, -2, 0, 0.28); + paint(87, 11, 1, 0.24); + paint(125, -2, 2, 0.16); + + paint(28, 31, 1, 0.24); + paint(72, 33, 2, 0.2); + + paint(46, 52, 1, 0.24); + paint(24, 55, 2, 0.18); + + if (quote) { + paint(4, 23, 1, 0.28); + paint(0, 48, 0, 0.24); + } + + p.setClipping(false); + p.setOpacity(1.); +} + +} // namespace + +Reply::Reply() +: _name(st::maxSignatureSize / 2) +, _text(st::maxSignatureSize / 2) { +} + +Reply &Reply::operator=(Reply &&other) = default; + +Reply::~Reply() = default; + +void Reply::update( + not_null view, + not_null data) { + const auto item = view->data(); + const auto &fields = data->fields(); + const auto message = data->resolvedMessage.get(); + const auto story = data->resolvedStory.get(); + if (!_externalSender) { + if (const auto id = fields.externalSenderId) { + _externalSender = view->history()->owner().peer(id); + } + } + _colorPeer = message + ? message->displayFrom() + : story + ? story->peer().get() + : _externalSender + ? _externalSender + : nullptr; + _hiddenSenderColorIndexPlusOne = (!_colorPeer && message) + ? (message->hiddenSenderInfo()->colorIndex + 1) + : 0; + + const auto hasPreview = (story && story->hasReplyPreview()) + || (message + && message->media() + && message->media()->hasReplyPreview()); + _hasPreview = hasPreview ? 1 : 0; + _displaying = data->displaying() ? 1 : 0; + _multiline = data->multiline() ? 1 : 0; + _replyToStory = (fields.storyId != 0); + const auto hasQuoteIcon = _displaying + && fields.manualQuote + && !fields.quote.empty(); + _hasQuoteIcon = hasQuoteIcon ? 1 : 0; + + const auto text = (!_displaying && data->unavailable()) + ? TextWithEntities() + : !fields.quote.empty() + ? fields.quote + : message + ? message->inReplyText() + : story + ? story->inReplyText() + : TextWithEntities(); + const auto repaint = [=] { item->customEmojiRepaint(); }; + const auto context = Core::MarkedTextContext{ + .session = &view->history()->session(), + .customEmojiRepaint = repaint, + }; + _text.setMarkedText( + st::defaultTextStyle, + text, + Ui::DialogTextOptions(), + context); + + updateName(view, data); + + if (_displaying) { + setLinkFrom(view, data); + const auto media = message ? message->media() : nullptr; + if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) { + _spoiler = nullptr; + } else if (!_spoiler) { + _spoiler = std::make_unique(repaint); + } + } else { + _spoiler = nullptr; + } +} + +bool Reply::expand() { + if (!_expandable || _expanded) { + return false; + } + _expanded = true; + return true; +} + +void Reply::setLinkFrom( + not_null view, + not_null data) { + const auto weak = base::make_weak(view); + const auto &fields = data->fields(); + const auto externalChannelId = peerToChannel(fields.externalPeerId); + const auto messageId = fields.messageId; + const auto quote = fields.manualQuote + ? fields.quote + : TextWithEntities(); + const auto returnToId = view->data()->fullId(); + const auto externalLink = [=](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + auto error = QString(); + const auto owner = &controller->session().data(); + if (const auto view = weak.get()) { + if (const auto reply = view->Get()) { + if (reply->expand()) { + owner->requestViewResize(view); + return; + } + } + } + if (externalChannelId) { + const auto channel = owner->channel(externalChannelId); + if (!channel->isForbidden()) { + if (messageId) { + JumpToMessageClickHandler( + channel, + messageId, + returnToId, + quote + )->onClick(context); + } else { + controller->showPeerInfo(channel); + } + } else if (channel->isBroadcast()) { + error = tr::lng_channel_not_accessible(tr::now); + } else { + error = tr::lng_group_not_accessible(tr::now); + } + } else { + error = tr::lng_reply_from_private_chat(tr::now); + } + if (!error.isEmpty()) { + controller->showToast(error); + } + } + }; + const auto message = data->resolvedMessage.get(); + const auto story = data->resolvedStory.get(); + _link = message + ? JumpToMessageClickHandler(message, returnToId, quote) + : story + ? JumpToStoryClickHandler(story) + : (data->external() + && (!fields.messageId + || (data->unavailable() && externalChannelId))) + ? std::make_shared(externalLink) + : nullptr; +} + +PeerData *Reply::sender( + not_null view, + not_null data) const { + const auto message = data->resolvedMessage.get(); + if (const auto story = data->resolvedStory.get()) { + return story->peer(); + } else if (!message) { + return _externalSender; + } else if (view->data()->Has()) { + // Forward of a reply. Show reply-to original sender. + const auto forwarded = message->Get(); + if (forwarded) { + return forwarded->originalSender; + } + } + if (const auto from = message->displayFrom()) { + return from; + } + return message->author().get(); +} + +QString Reply::senderName( + not_null view, + not_null data, + bool shorten) const { + if (const auto peer = sender(view, data)) { + return senderName(peer, shorten); + } else if (!data->resolvedMessage) { + return data->fields().externalSenderName; + } else if (view->data()->Has()) { + // Forward of a reply. Show reply-to original sender. + const auto forwarded + = data->resolvedMessage->Get(); + if (forwarded) { + Assert(forwarded->hiddenSenderInfo != nullptr); + return forwarded->hiddenSenderInfo->name; + } + } + return QString(); +} + +QString Reply::senderName( + not_null peer, + bool shorten) const { + const auto user = shorten ? peer->asUser() : nullptr; + return user ? user->firstName : peer->name(); +} + +bool Reply::isNameUpdated( + not_null view, + not_null data) const { + if (const auto from = sender(view, data)) { + if (_nameVersion < from->nameVersion()) { + updateName(view, data, from); + return true; + } + } + return false; +} + +void Reply::updateName( + not_null view, + not_null data, + std::optional resolvedSender) const { + auto viaBotUsername = QString(); + const auto story = data->resolvedStory.get(); + const auto message = data->resolvedMessage.get(); + if (message && !message->Has()) { + if (const auto bot = message->viaBot()) { + viaBotUsername = bot->username(); + } + } + const auto &fields = data->fields(); + const auto sender = resolvedSender.value_or(this->sender(view, data)); + const auto externalPeer = fields.externalPeerId + ? view->history()->owner().peer(fields.externalPeerId).get() + : nullptr; + const auto groupNameAdded = (externalPeer && externalPeer != sender); + const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded; + const auto name = sender + ? senderName(sender, shorten) + : senderName(view, data, shorten); + const auto previewSkip = _hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + const auto peerIcon = [](PeerData *peer) { + return !peer + ? &st::historyReplyUser + : peer->isBroadcast() + ? &st::historyReplyChannel + : (peer->isChannel() || peer->isChat()) + ? &st::historyReplyGroup + : &st::historyReplyUser; + }; + const auto peerEmoji = [&](PeerData *peer) { + const auto owner = &view->history()->owner(); + return Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji( + *peerIcon(peer))); + }; + auto nameFull = TextWithEntities(); + if (!groupNameAdded && data->external() && !fields.storyId) { + nameFull.append(peerEmoji(sender)); + } + nameFull.append(name); + if (groupNameAdded) { + nameFull.append(peerEmoji(externalPeer)); + nameFull.append(externalPeer->name()); + } + if (!viaBotUsername.isEmpty()) { + nameFull.append(u" @"_q).append(viaBotUsername); + } + const auto context = Core::MarkedTextContext{ + .session = &view->history()->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _name.setMarkedText( + st::fwdTextStyle, + nameFull, + Ui::NameTextOptions(), + context); + if (sender) { + _nameVersion = sender->nameVersion(); + } + const auto nameMaxWidth = previewSkip + + _name.maxWidth() + + (_hasQuoteIcon + ? st::messageTextStyle.blockquote.icon.width() + : 0); + const auto storySkip = fields.storyId + ? (st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width()) + : 0; + const auto optimalTextSize = _multiline + ? countMultilineOptimalSize(previewSkip) + : QSize( + (previewSkip + + storySkip + + std::min(_text.maxWidth(), st::maxSignatureSize)), + st::normalFont->height); + _maxWidth = std::max(nameMaxWidth, optimalTextSize.width()); + if (!data->displaying()) { + const auto unavailable = data->unavailable(); + _stateText = ((fields.messageId || fields.storyId) && !unavailable) + ? tr::lng_profile_loading(tr::now) + : fields.storyId + ? tr::lng_deleted_story(tr::now) + : tr::lng_deleted_message(tr::now); + const auto phraseWidth = st::msgDateFont->width(_stateText); + _maxWidth = unavailable + ? phraseWidth + : std::max(_maxWidth, phraseWidth); + } else { + _stateText = QString(); + } + _maxWidth = st::historyReplyPadding.left() + + _maxWidth + + st::historyReplyPadding.right(); + _minHeight = st::historyReplyPadding.top() + + st::msgServiceNameFont->height + + optimalTextSize.height() + + st::historyReplyPadding.bottom(); +} + +int Reply::resizeToWidth(int width) const { + _ripple.animation = nullptr; + + const auto previewSkip = _hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + if (width >= _maxWidth || !_multiline) { + _nameTwoLines = 0; + _expandable = 0; + _height = _minHeight; + return height(); + } + const auto innerw = width + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = innerw - previewSkip; + const auto desiredNameHeight = _name.countHeight(namew); + _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; + const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; + const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + auto lineCounter = 0; + auto elided = false; + const auto texth = _text.countDimensions( + textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; + _expandable = (_multiline && elided) ? 1 : 0; + _height = st::historyReplyPadding.top() + + nameh + + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) + + st::historyReplyPadding.bottom(); + return height(); +} + +Ui::Text::GeometryDescriptor Reply::textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const { + return { .layout = [=](Ui::Text::LineGeometry in) { + const auto skip = (*line ? 0 : firstLineSkip); + ++*line; + *outElided = *outElided + || !_multiline + || (!_expanded + && (*line == kNonExpandedLinesLimit) + && in.width > available - skip); + in.width = available - skip; + in.left += skip; + in.elided = *outElided; + return in; + } }; +} + +int Reply::height() const { + return _height + st::historyReplyTop + st::historyReplyBottom; +} + +QMargins Reply::margins() const { + return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom); +} + +QSize Reply::countMultilineOptimalSize( + int previewSkip) const { + auto elided = false; + auto lineCounter = 0; + const auto max = previewSkip + _text.maxWidth(); + const auto result = _text.countDimensions( + textGeometry(max, previewSkip, &lineCounter, &elided)); + return { result.width, result.height }; +} + +void Reply::paint( + Painter &p, + not_null view, + const Ui::ChatPaintContext &context, + int x, + int y, + int w, + bool inBubble) const { + const auto st = context.st; + const auto stm = context.messageStyle(); + + y += st::historyReplyTop; + const auto rect = QRect(x, y, w, _height); + const auto selected = context.selected(); + const auto backgroundEmojiId = _colorPeer + ? _colorPeer->backgroundEmojiId() + : DocumentId(); + const auto colorIndexPlusOne = _colorPeer + ? (_colorPeer->colorIndex() + 1) + : _hiddenSenderColorIndexPlusOne; + const auto useColorIndex = colorIndexPlusOne && !context.outbg; + const auto colorPattern = colorIndexPlusOne + ? st->colorPatternIndex(colorIndexPlusOne - 1) + : 0; + const auto cache = !inBubble + ? (_hasQuoteIcon + ? st->serviceQuoteCache(colorPattern) + : st->serviceReplyCache(colorPattern)).get() + : useColorIndex + ? (_hasQuoteIcon + ? st->coloredQuoteCache(selected, colorIndexPlusOne - 1) + : st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get() + : (_hasQuoteIcon + ? stm->quoteCache[colorPattern] + : stm->replyCache[colorPattern]).get(); + const auto "eSt = _hasQuoteIcon + ? st::messageTextStyle.blockquote + : st::messageQuoteStyle; + const auto backgroundEmoji = backgroundEmojiId + ? st->backgroundEmojiData(backgroundEmojiId).get() + : nullptr; + const auto backgroundEmojiCache = backgroundEmoji + ? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex( + selected, + context.outbg, + inBubble, + colorIndexPlusOne)] + : nullptr; + const auto rippleColor = cache->bg; + if (!inBubble) { + cache->bg = QColor(0, 0, 0, 0); + } + Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); + Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); + if (backgroundEmoji) { + ValidateBackgroundEmoji( + backgroundEmojiId, + backgroundEmoji, + backgroundEmojiCache, + cache, + view); + if (!backgroundEmojiCache->frames[0].isNull()) { + FillBackgroundEmoji(p, rect, _hasQuoteIcon, *backgroundEmojiCache); + } + } + if (!inBubble) { + cache->bg = rippleColor; + } + + if (_ripple.animation) { + _ripple.animation->paint(p, x, y, w, &rippleColor); + if (_ripple.animation->empty()) { + _ripple.animation.reset(); + } + } + + auto hasPreview = (_hasPreview != 0); + auto previewSkip = hasPreview + ? (st::messageQuoteStyle.outline + + st::historyReplyPreviewMargin.left() + + st::historyReplyPreview + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; + if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) { + hasPreview = false; + previewSkip = 0; + } + + const auto pausedSpoiler = context.paused + || On(PowerSaving::kChatSpoiler); + auto textLeft = x + st::historyReplyPadding.left(); + auto textTop = y + + st::historyReplyPadding.top() + + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); + if (w > st::historyReplyPadding.left()) { + if (_displaying) { + if (hasPreview) { + const auto data = view->data()->Get(); + const auto message = data + ? data->resolvedMessage.get() + : nullptr; + const auto media = message ? message->media() : nullptr; + const auto image = media + ? media->replyPreview() + : !data + ? nullptr + : data->resolvedStory + ? data->resolvedStory->replyPreview() + : nullptr; + if (image) { + auto to = style::rtlrect( + x + st::historyReplyPreviewMargin.left(), + y + st::historyReplyPreviewMargin.top(), + st::historyReplyPreview, + st::historyReplyPreview, + w + 2 * x); + const auto preview = image->pixSingle( + image->size() / style::DevicePixelRatio(), + { + .colored = (context.selected() + ? &st->msgStickerOverlay() + : nullptr), + .options = Images::Option::RoundSmall, + .outer = to.size(), + }); + p.drawPixmap(to.x(), to.y(), preview); + if (_spoiler) { + view->clearCustomEmojiRepaint(); + Ui::FillSpoilerRect( + p, + to, + Ui::DefaultImageSpoiler().frame( + _spoiler->index( + context.now, + pausedSpoiler))); + } + } + } + const auto textw = w + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = textw - previewSkip; + auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + if (namew > 0) { + p.setPen(!inBubble + ? st->msgImgReplyBarColor()->c + : useColorIndex + ? FromNameFg(context, colorIndexPlusOne - 1) + : stm->msgServiceFg->c); + _name.drawLeftElided( + p, + x + st::historyReplyPadding.left() + previewSkip, + y + st::historyReplyPadding.top(), + namew, + w + 2 * x, + _nameTwoLines ? 2 : 1); + + p.setPen(inBubble + ? stm->historyTextFg + : st->msgImgReplyBarColor()); + view->prepareCustomEmojiPaint(p, context, _text); + auto replyToTextPalette = &(!inBubble + ? st->imgReplyTextPalette() + : useColorIndex + ? st->coloredTextPalette(selected, colorIndexPlusOne - 1) + : stm->replyTextPalette); + if (_replyToStory) { + st::dialogsMiniReplyStory.icon.icon.paint( + p, + textLeft + firstLineSkip, + textTop, + w + 2 * x, + replyToTextPalette->linkFg->c); + firstLineSkip += st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width(); + } + auto owned = std::optional(); + auto copy = std::optional(); + if (inBubble && colorIndexPlusOne) { + copy.emplace(*replyToTextPalette); + owned.emplace(cache->icon); + copy->linkFg = owned->color(); + replyToTextPalette = &*copy; + } + auto l = 0; + auto e = false; + _text.draw(p, { + .position = { textLeft, textTop }, + .geometry = textGeometry(textw, firstLineSkip, &l, &e), + .palette = replyToTextPalette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = (context.paused + || On(PowerSaving::kEmojiChat)), + .pausedSpoiler = pausedSpoiler, + .elisionOneLine = true, + }); + p.setTextPalette(stm->textPalette); + } + } else { + p.setFont(st::msgDateFont); + p.setPen(cache->icon); + p.drawTextLeft( + textLeft, + (y + + st::historyReplyPadding.top() + + (st::msgDateFont->height / 2)), + w + 2 * x, + st::msgDateFont->elided( + _stateText, + x + w - textLeft - st::historyReplyPadding.right())); + } + } +} + +void Reply::createRippleAnimation( + not_null view, + QSize size) { + _ripple.animation = std::make_unique( + st::defaultRippleAnimation, + Ui::RippleAnimation::RoundRectMask( + size, + st::messageQuoteStyle.radius), + [=] { view->history()->owner().requestViewRepaint(view); }); +} + +void Reply::saveRipplePoint(QPoint point) const { + _ripple.lastPoint = point; +} + +void Reply::addRipple() { + if (_ripple.animation) { + _ripple.animation->add(_ripple.lastPoint); + } +} + +void Reply::stopLastRipple() { + if (_ripple.animation) { + _ripple.animation->lastStop(); + } +} + +void Reply::unloadPersistentAnimation() { + _text.unloadPersistentAnimation(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_reply.h b/Telegram/SourceFiles/history/view/history_view_reply.h new file mode 100644 index 000000000..10b5ebb9b --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_reply.h @@ -0,0 +1,116 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/history_view_element.h" + +namespace Ui { +class SpoilerAnimation; +} // namespace Ui + +namespace HistoryView { + +class Reply final : public RuntimeComponent { +public: + Reply(); + Reply(const Reply &other) = delete; + Reply(Reply &&other) = delete; + Reply &operator=(const Reply &other) = delete; + Reply &operator=(Reply &&other); + ~Reply(); + + void update( + not_null view, + not_null data); + + [[nodiscard]] bool isNameUpdated( + not_null view, + not_null data) const; + void updateName( + not_null view, + not_null data, + std::optional resolvedSender = std::nullopt) const; + [[nodiscard]] int resizeToWidth(int width) const; + [[nodiscard]] int height() const; + [[nodiscard]] QMargins margins() const; + + bool expand(); + + void paint( + Painter &p, + not_null view, + const Ui::ChatPaintContext &context, + int x, + int y, + int w, + bool inBubble) const; + void unloadPersistentAnimation(); + + void createRippleAnimation(not_null view, QSize size); + void saveRipplePoint(QPoint point) const; + void addRipple(); + void stopLastRipple(); + + [[nodiscard]] int maxWidth() const { + return _maxWidth; + } + [[nodiscard]] ClickHandlerPtr link() const { + return _link; + } + +private: + [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const; + [[nodiscard]] QSize countMultilineOptimalSize( + int firstLineSkip) const; + void setLinkFrom( + not_null view, + not_null data); + + [[nodiscard]] PeerData *sender( + not_null view, + not_null data) const; + [[nodiscard]] QString senderName( + not_null view, + not_null data, + bool shorten) const; + [[nodiscard]] QString senderName( + not_null peer, + bool shorten) const; + + ClickHandlerPtr _link; + std::unique_ptr _spoiler; + mutable PeerData *_externalSender = nullptr; + mutable PeerData *_colorPeer = nullptr; + mutable struct { + mutable std::unique_ptr animation; + QPoint lastPoint; + } _ripple; + mutable Ui::Text::String _name; + mutable Ui::Text::String _text; + mutable QString _stateText; + mutable int _maxWidth = 0; + mutable int _minHeight = 0; + mutable int _height = 0; + mutable int _nameVersion = 0; + uint8 _hiddenSenderColorIndexPlusOne = 0; + uint8 _hasQuoteIcon : 1 = 0; + uint8 _replyToStory : 1 = 0; + uint8 _expanded : 1 = 0; + mutable uint8 _expandable : 1 = 0; + mutable uint8 _nameTwoLines : 1 = 0; + mutable uint8 _hasPreview : 1 = 0; + mutable uint8 _displaying : 1 = 0; + mutable uint8 _multiline : 1 = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index a82fd13fd..140fdd86a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -419,7 +419,7 @@ bool ExtendedPreview::needsBubble() const { && (item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 340b1fa66..4f11171a8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_reply.h" #include "history/view/history_view_transcribe_button.h" #include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_media_spoiler.h" @@ -217,12 +218,12 @@ QSize Gif::countOptimalSize() { } else if (isUnwrapped()) { const auto item = _parent->data(); auto via = item->Get(); - auto reply = _parent->displayedReply(); + auto reply = _parent->Get(); auto forwarded = item->Get(); if (forwarded) { forwarded->create(via); } - maxWidth += additionalWidth(via, reply, forwarded); + maxWidth += additionalWidth(reply, via, forwarded); accumulate_max(maxWidth, _parent->reactionsOptimalWidth()); } return { maxWidth, minHeight }; @@ -274,10 +275,10 @@ QSize Gif::countCurrentSize(int newWidth) { const auto item = _parent->data(); auto via = item->Get(); - auto reply = _parent->displayedReply(); + auto reply = _parent->Get(); auto forwarded = item->Get(); if (via || reply || forwarded) { - auto additional = additionalWidth(via, reply, forwarded); + auto additional = additionalWidth(reply, via, forwarded); newWidth += additional; accumulate_min(newWidth, availableWidth); auto usew = maxWidth() - additional; @@ -385,13 +386,13 @@ void Gif::draw(Painter &p, const PaintContext &context) const { auto usex = 0, usew = paintw; const auto unwrapped = isUnwrapped(); const auto via = unwrapped ? item->Get() : nullptr; - const auto reply = unwrapped ? _parent->displayedReply() : nullptr; + const auto reply = unwrapped ? _parent->Get() : nullptr; const auto forwarded = unwrapped ? item->Get() : nullptr; const auto rightAligned = unwrapped && outbg && !_parent->delegate()->elementIsChatWide(); if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); + usew = maxWidth() - additionalWidth(reply, via, forwarded); if (rightAligned) { usex = width() - usew; } @@ -1013,13 +1014,13 @@ TextState Gif::textState(QPoint point, StateRequest request) const { const auto item = _parent->data(); auto usew = paintw, usex = 0; const auto via = unwrapped ? item->Get() : nullptr; - const auto reply = unwrapped ? _parent->displayedReply() : nullptr; + const auto reply = unwrapped ? _parent->Get() : nullptr; const auto forwarded = unwrapped ? item->Get() : nullptr; const auto rightAligned = unwrapped && outbg && !_parent->delegate()->elementIsChatWide(); if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); + usew = maxWidth() - additionalWidth(reply, via, forwarded); if (rightAligned) { usex = width() - usew; } @@ -1094,15 +1095,8 @@ TextState Gif::textState(QPoint point, StateRequest request) const { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { result.link = reply->link(); - reply->ripple.lastPoint = point - replyRect.topLeft(); - if (!reply->ripple.animation) { - reply->ripple.animation = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - replyRect.size(), - st::messageQuoteStyle.radius), - [=] { item->history()->owner().requestItemRepaint(item); }); - } + reply->saveRipplePoint(point - replyRect.topLeft()); + reply->createRippleAnimation(_parent, replyRect.size()); return result; } } @@ -1520,7 +1514,7 @@ bool Gif::needsBubble() const { return item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton(); @@ -1542,10 +1536,10 @@ QRect Gif::contentRectForReactions() const { && !_parent->delegate()->elementIsChatWide(); const auto item = _parent->data(); const auto via = item->Get(); - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); const auto forwarded = item->Get(); if (via || reply || forwarded) { - usew = maxWidth() - additionalWidth(via, reply, forwarded); + usew = maxWidth() - additionalWidth(reply, via, forwarded); } accumulate_max(usew, _parent->reactionsOptimalWidth()); if (rightAligned) { @@ -1602,8 +1596,8 @@ QPoint Gif::resolveCustomInfoRightBottom() const { int Gif::additionalWidth() const { const auto item = _parent->data(); return additionalWidth( + _parent->Get(), item->Get(), - item->Get(), item->Get()); } @@ -1763,7 +1757,10 @@ void Gif::refreshCaption() { _caption = createCaption(_parent->data()); } -int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { +int Gif::additionalWidth( + const Reply *reply, + const HistoryMessageVia *via, + const HistoryMessageForwarded *forwarded) const { int result = 0; if (forwarded) { accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index eac33cc7a..f20549771 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -37,6 +37,7 @@ enum class Error; namespace HistoryView { +class Reply; class TranscribeButton; class Gif final : public File { @@ -176,8 +177,8 @@ private: [[nodiscard]] bool needInfoDisplay() const; [[nodiscard]] bool needCornerStatusDisplay() const; [[nodiscard]] int additionalWidth( + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; [[nodiscard]] int additionalWidth() const; [[nodiscard]] bool isUnwrapped() const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index ab75f2051..293849ba3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -375,7 +375,7 @@ bool Location::needsBubble() const { return item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton(); 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 bea617b23..d0133aa76 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -869,7 +869,7 @@ bool GroupedMedia::computeNeedBubble() const { if (item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton() 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 69f4875e1..e61202239 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_sticker.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_reply.h" #include "history/history_item.h" #include "history/history_item_components.h" #include "lottie/lottie_single_player.h" @@ -54,13 +55,13 @@ QSize UnwrappedMedia::countOptimalSize() { if (_parent->media() == this) { const auto item = _parent->data(); const auto via = item->Get(); - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); const auto topic = _parent->displayedTopicButton(); const auto forwarded = getDisplayedForwardedInfo(); if (forwarded) { forwarded->create(via); } - maxWidth += additionalWidth(topic, via, reply, forwarded); + maxWidth += additionalWidth(topic, reply, via, forwarded); accumulate_max(maxWidth, _parent->reactionsOptimalWidth()); if (const auto size = _parent->rightActionSize()) { minHeight = std::max( @@ -93,11 +94,11 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { accumulate_max(newWidth, _parent->reactionsOptimalWidth()); _topAdded = 0; const auto via = item->Get(); - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); const auto topic = _parent->displayedTopicButton(); const auto forwarded = getDisplayedForwardedInfo(); if (topic || via || reply || forwarded) { - const auto additional = additionalWidth(topic, via, reply, forwarded); + const auto additional = additionalWidth(topic, reply, via, forwarded); const auto optimalw = maxWidth() - additional; const auto additionalMinWidth = std::min(additional, st::msgReplyPadding.left() + st::msgMinWidth / 2); _additionalOnTop = (optimalw + additionalMinWidth) > newWidth; @@ -107,7 +108,7 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) { if (reply) { [[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth); } - const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth); + const auto surrounding = surroundingInfo(topic, reply, via, forwarded, surroundingWidth); if (_additionalOnTop) { _topAdded = surrounding.height + st::msgMargin.bottom(); newHeight += _topAdded; @@ -166,17 +167,17 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const { if (!inWebPage && (context.skipDrawingParts != PaintContext::SkipDrawingParts::Surrounding)) { const auto via = inWebPage ? nullptr : item->Get(); - const auto reply = inWebPage ? nullptr : _parent->displayedReply(); + const auto reply = inWebPage ? nullptr : _parent->Get(); const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); - drawSurrounding(p, inner, context, topic, via, reply, forwarded); + drawSurrounding(p, inner, context, topic, reply, via, forwarded); } } UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded, int outerw) const { if (!topic && !via && !reply && !forwarded) { @@ -242,8 +243,8 @@ void UnwrappedMedia::drawSurrounding( const QRect &inner, const PaintContext &context, const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { const auto st = context.st; const auto sti = context.imageStyle(); @@ -263,9 +264,9 @@ void UnwrappedMedia::drawSurrounding( } auto replyRight = 0; auto rectw = _additionalOnTop - ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, via, reply, forwarded)) + ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded)) : (width() - inner.width() - st::msgReplyPadding.left()); - if (const auto surrounding = surroundingInfo(topic, via, reply, forwarded, rectw)) { + if (const auto surrounding = surroundingInfo(topic, reply, via, forwarded, rectw)) { auto recth = surrounding.panelHeight; if (!surrounding.topicSize.isEmpty()) { auto rectw = surrounding.topicSize.width(); @@ -416,14 +417,14 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { if (_parent->media() == this) { const auto via = inWebPage ? nullptr : item->Get(); - const auto reply = inWebPage ? nullptr : _parent->displayedReply(); + const auto reply = inWebPage ? nullptr : _parent->Get(); const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton(); const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo(); auto replyRight = 0; auto rectw = _additionalOnTop - ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, via, reply, forwarded)) + ? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded)) : (width() - inner.width() - st::msgReplyPadding.left()); - if (const auto surrounding = surroundingInfo(topic, via, reply, forwarded, rectw)) { + if (const auto surrounding = surroundingInfo(topic, reply, via, forwarded, rectw)) { auto recth = surrounding.panelHeight; if (!surrounding.topicSize.isEmpty()) { auto rectw = surrounding.topicSize.width(); @@ -486,16 +487,8 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const { const auto replyRect = QRect(rectx, recty, rectw, recth); if (replyRect.contains(point)) { result.link = reply->link(); - reply->ripple.lastPoint = point - replyRect.topLeft(); - if (!reply->ripple.animation) { - reply->ripple.animation = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - replyRect.size(), - st::messageQuoteStyle.radius), - [=] { item->history()->owner().requestItemRepaint(item); }); - } - return result; + reply->saveRipplePoint(point - replyRect.topLeft()); + reply->createRippleAnimation(_parent, replyRect.size()); } } replyRight = rectx + rectw - st::msgReplyPadding.right(); @@ -542,7 +535,7 @@ bool UnwrappedMedia::hasTextForCopy() const { } bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const { - const auto reply = _parent->displayedReply(); + const auto reply = _parent->Get(); return !reply || (reply->link() != p); } @@ -649,8 +642,8 @@ bool UnwrappedMedia::needInfoDisplay() const { int UnwrappedMedia::additionalWidth( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x(); if (topic) { 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 9932c0521..1cd71c86a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.h @@ -17,6 +17,7 @@ struct HistoryMessageForwarded; namespace HistoryView { +class Reply; struct TopicButton; class UnwrappedMedia final : public Media { @@ -120,8 +121,8 @@ private: }; [[nodiscard]] SurroundingInfo surroundingInfo( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded, int outerw) const; void drawSurrounding( @@ -129,8 +130,8 @@ private: const QRect &inner, const PaintContext &context, const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; QSize countOptimalSize() override; @@ -139,8 +140,8 @@ private: bool needInfoDisplay() const; int additionalWidth( const TopicButton *topic, + const Reply *reply, const HistoryMessageVia *via, - const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const; int calculateFullRight(const QRect &inner) const; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index de72e7786..b3d79c6fe 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -1077,7 +1077,7 @@ bool Photo::needsBubble() const { && (item->repliesAreComments() || item->externalReply() || item->viaBot() - || _parent->displayedReply() + || _parent->displayReply() || _parent->displayForwardedFrom() || _parent->displayFromName() || _parent->displayedTopicButton());