| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908 |
- /*
- 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/view/history_view_item_preview.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;
- } // namespace
- void ValidateBackgroundEmoji(
- DocumentId backgroundEmojiId,
- not_null<Ui::BackgroundEmojiData*> data,
- not_null<Ui::BackgroundEmojiCache*> cache,
- not_null<Ui::Text::QuotePaintCache*> quote,
- not_null<const Element*> view) {
- if (data->firstFrameMask.isNull() && !data->emoji) {
- data->emoji = CreateBackgroundEmojiInstance(
- &view->history()->owner(),
- backgroundEmojiId,
- crl::guard(view, [=] { view->repaint(); }));
- }
- ValidateBackgroundEmoji(backgroundEmojiId, data, cache, quote);
- }
- void ValidateBackgroundEmoji(
- DocumentId backgroundEmojiId,
- not_null<Ui::BackgroundEmojiData*> data,
- not_null<Ui::BackgroundEmojiCache*> cache,
- not_null<Ui::Text::QuotePaintCache*> quote) {
- Expects(!data->firstFrameMask.isNull() || data->emoji != nullptr);
- if (data->firstFrameMask.isNull()) {
- if (!cache->frames[0].isNull()) {
- for (auto &frame : cache->frames) {
- frame = QImage();
- }
- }
- if (!data->emoji->ready()) {
- return;
- }
- const auto tag = Data::CustomEmojiSizeTag::Isolated;
- 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);
- }
- auto CreateBackgroundEmojiInstance(
- not_null<Data::Session*> owner,
- DocumentId backgroundEmojiId,
- Fn<void()> repaint)
- -> std::unique_ptr<Ui::Text::CustomEmoji> {
- return owner->customEmojiManager().create(
- backgroundEmojiId,
- repaint,
- Data::CustomEmojiSizeTag::Isolated);
- }
- void FillBackgroundEmoji(
- QPainter &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.);
- }
- Reply::Reply()
- : _name(st::maxSignatureSize / 2)
- , _text(st::maxSignatureSize / 2) {
- }
- Reply &Reply::operator=(Reply &&other) = default;
- Reply::~Reply() = default;
- void Reply::update(
- not_null<Element*> view,
- not_null<HistoryMessageReply*> data) {
- const auto item = view->data();
- const auto &fields = data->fields();
- const auto message = data->resolvedMessage.get();
- const auto story = data->resolvedStory.get();
- const auto externalMedia = fields.externalMedia.get();
- if (!_externalSender) {
- if (const auto id = fields.externalSenderId) {
- _externalSender = view->history()->owner().peer(id);
- }
- }
- _colorPeer = message
- ? message->contentColorsFrom()
- : story
- ? story->peer().get()
- : _externalSender
- ? _externalSender
- : nullptr;
- _hiddenSenderColorIndexPlusOne = (!_colorPeer && message)
- ? (message->originalHiddenSenderInfo()->colorIndex + 1)
- : 0;
- const auto hasPreview = (story && story->hasReplyPreview())
- || (message
- && message->media()
- && message->media()->hasReplyPreview())
- || (externalMedia && externalMedia->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()
- : (message && (fields.quote.empty() || !fields.manualQuote))
- ? message->inReplyText()
- : !fields.quote.empty()
- ? fields.quote
- : story
- ? story->inReplyText()
- : externalMedia
- ? externalMedia->toPreview({
- .hideSender = true,
- .hideCaption = true,
- .ignoreMessageText = true,
- .generateImages = false,
- .ignoreGroup = true,
- .ignoreTopic = true,
- }).text
- : TextWithEntities();
- const auto repaint = [=] { item->customEmojiRepaint(); };
- const auto context = Core::TextContext({
- .session = &view->history()->session(),
- .repaint = repaint,
- });
- _text.setMarkedText(
- st::defaultTextStyle,
- text,
- _multiline ? Ui::ItemTextDefaultOptions() : 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<Ui::SpoilerAnimation>(repaint);
- }
- } else {
- _spoiler = nullptr;
- }
- }
- bool Reply::expand() {
- if (!_expandable || _expanded) {
- return false;
- }
- _expanded = true;
- return true;
- }
- void Reply::setLinkFrom(
- not_null<Element*> view,
- not_null<HistoryMessageReply*> 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 quoteOffset = fields.quoteOffset;
- const auto returnToId = view->data()->fullId();
- const auto externalLink = [=](ClickContext context) {
- const auto my = context.other.value<ClickHandlerContext>();
- 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<Reply>()) {
- 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,
- quoteOffset
- )->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, quoteOffset)
- : story
- ? JumpToStoryClickHandler(story)
- : (data->external()
- && (!fields.messageId
- || (data->unavailable() && externalChannelId)))
- ? std::make_shared<LambdaClickHandler>(externalLink)
- : nullptr;
- }
- PeerData *Reply::sender(
- not_null<const Element*> view,
- not_null<HistoryMessageReply*> 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<HistoryMessageForwarded>()) {
- // Forward of a reply. Show reply-to original sender.
- const auto forwarded = message->Get<HistoryMessageForwarded>();
- if (forwarded) {
- return forwarded->originalSender;
- }
- }
- if (const auto from = message->displayFrom()) {
- return from;
- }
- return message->author().get();
- }
- QString Reply::senderName(
- not_null<const Element*> view,
- not_null<HistoryMessageReply*> 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<HistoryMessageForwarded>()) {
- // Forward of a reply. Show reply-to original sender.
- const auto forwarded
- = data->resolvedMessage->Get<HistoryMessageForwarded>();
- if (forwarded) {
- Assert(forwarded->originalHiddenSenderInfo != nullptr);
- return forwarded->originalHiddenSenderInfo->name;
- }
- }
- return QString();
- }
- QString Reply::senderName(
- not_null<PeerData*> peer,
- bool shorten) const {
- const auto user = shorten ? peer->asUser() : nullptr;
- return user ? user->firstName : peer->name();
- }
- bool Reply::isNameUpdated(
- not_null<const Element*> view,
- not_null<HistoryMessageReply*> 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<const Element*> view,
- not_null<HistoryMessageReply*> data,
- std::optional<PeerData*> resolvedSender) const {
- auto viaBotUsername = QString();
- const auto message = data->resolvedMessage.get();
- const auto forwarded = message
- ? message->Get<HistoryMessageForwarded>()
- : nullptr;
- if (message && !forwarded) {
- if (const auto bot = message->viaBot()) {
- viaBotUsername = bot->username();
- }
- }
- const auto history = view->history();
- const auto &fields = data->fields();
- const auto sender = resolvedSender.value_or(this->sender(view, data));
- const auto externalPeer = fields.externalPeerId
- ? history->owner().peer(fields.externalPeerId).get()
- : nullptr;
- const auto displayAsExternal = data->displayAsExternal(view->data());
- const auto groupNameAdded = displayAsExternal
- && externalPeer
- && (externalPeer != sender)
- && (externalPeer->isChat() || externalPeer->isMegagroup());
- const auto originalNameAdded = !displayAsExternal
- && forwarded
- && !message->isDiscussionPost()
- && (forwarded->forwardOfForward()
- || (!message->showForwardsFromSender(forwarded)
- && !view->data()->Has<HistoryMessageForwarded>()));
- const auto shorten = !viaBotUsername.isEmpty()
- || groupNameAdded
- || originalNameAdded;
- 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;
- auto nameFull = TextWithEntities();
- if (displayAsExternal && !groupNameAdded && !fields.storyId) {
- nameFull.append(PeerEmoji(history, sender));
- }
- nameFull.append(name);
- if (groupNameAdded) {
- nameFull.append(' ').append(PeerEmoji(history, externalPeer));
- nameFull.append(externalPeer->name());
- } else if (originalNameAdded) {
- nameFull.append(' ').append(ForwardEmoji(&history->owner()));
- nameFull.append(forwarded->originalSender
- ? forwarded->originalSender->name()
- : forwarded->originalHiddenSenderInfo->name);
- }
- if (!viaBotUsername.isEmpty()) {
- nameFull.append(u" @"_q).append(viaBotUsername);
- }
- const auto context = Core::TextContext({
- .session = &history->session(),
- .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 = _minHeightExpandable;
- _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 elided = false;
- const auto texth = _text.countDimensions(
- textGeometry(innerw, firstLineSkip, &elided)).height;
- _expandable = elided ? 1 : 0;
- _height = st::historyReplyPadding.top()
- + nameh
- + std::max(texth, st::normalFont->height)
- + st::historyReplyPadding.bottom();
- return height();
- }
- Ui::Text::GeometryDescriptor Reply::textGeometry(
- int available,
- int firstLineSkip,
- bool *outElided) const {
- return { .layout = [=](int line) {
- const auto skip = (line ? 0 : firstLineSkip);
- const auto elided = !_multiline
- || (!_expanded && (line + 1 >= kNonExpandedLinesLimit));
- return Ui::Text::LineGeometry{
- .left = skip,
- .width = available - skip,
- .elided = elided,
- };
- }, .outElided = outElided };
- }
- 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;
- const auto max = previewSkip + _text.maxWidth();
- const auto result = _text.countDimensions(
- textGeometry(max, previewSkip, &elided));
- _minHeightExpandable = elided ? 1 : 0;
- return {
- result.width + st::historyReplyPadding.right(),
- std::max(result.height, st::normalFont->height),
- };
- }
- void Reply::paint(
- Painter &p,
- not_null<const Element*> 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<HistoryMessageReply>();
- 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()
- : data->fields().externalMedia
- ? data->fields().externalMedia->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
- - (_hasQuoteIcon
- ? st::messageTextStyle.blockquote.icon.width()
- : 0);
- 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);
- auto owned = std::optional<style::owned_color>();
- auto copy = std::optional<style::TextPalette>();
- if (inBubble && colorIndexPlusOne) {
- copy.emplace(*replyToTextPalette);
- owned.emplace(cache->icon);
- copy->linkFg = owned->color();
- replyToTextPalette = &*copy;
- }
- 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();
- }
- _text.draw(p, {
- .position = { textLeft, textTop },
- .geometry = textGeometry(textw, firstLineSkip),
- .palette = replyToTextPalette,
- .spoiler = Ui::Text::DefaultSpoilerCache(),
- .now = context.now,
- .pausedEmoji = (context.paused
- || On(PowerSaving::kEmojiChat)),
- .pausedSpoiler = pausedSpoiler,
- .elisionLines = 1,
- });
- 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<const Element*> view,
- QSize size) {
- _ripple.animation = std::make_unique<Ui::RippleAnimation>(
- st::defaultRippleAnimation,
- Ui::RippleAnimation::RoundRectMask(
- size,
- st::messageQuoteStyle.radius),
- [=] { view->repaint(); });
- }
- 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();
- }
- }
- TextWithEntities Reply::PeerEmoji(
- not_null<History*> history,
- PeerData *peer) {
- return PeerEmoji(&history->owner(), peer);
- }
- TextWithEntities Reply::PeerEmoji(
- not_null<Data::Session*> owner,
- PeerData *peer) {
- using namespace std;
- const auto icon = !peer
- ? pair(&st::historyReplyUser, st::historyReplyUserPadding)
- : peer->isBroadcast()
- ? pair(&st::historyReplyChannel, st::historyReplyChannelPadding)
- : (peer->isChannel() || peer->isChat())
- ? pair(&st::historyReplyGroup, st::historyReplyGroupPadding)
- : pair(&st::historyReplyUser, st::historyReplyUserPadding);
- return Ui::Text::SingleCustomEmoji(
- owner->customEmojiManager().registerInternalEmoji(
- *icon.first,
- icon.second));
- }
- TextWithEntities Reply::ForwardEmoji(not_null<Data::Session*> owner) {
- return Ui::Text::SingleCustomEmoji(
- owner->customEmojiManager().registerInternalEmoji(
- st::historyReplyForward,
- st::historyReplyForwardPadding));
- }
- TextWithEntities Reply::ComposePreviewName(
- not_null<History*> history,
- not_null<HistoryItem*> to,
- bool quote) {
- const auto sender = [&] {
- if (const auto from = to->displayFrom()) {
- return not_null(from);
- }
- return to->author();
- }();
- const auto toPeer = to->history()->peer;
- const auto displayAsExternal = (to->history() != history);
- const auto groupNameAdded = displayAsExternal
- && (toPeer != sender)
- && (toPeer->isChat() || toPeer->isMegagroup());
- const auto shorten = groupNameAdded || quote;
- auto nameFull = TextWithEntities();
- using namespace HistoryView;
- if (displayAsExternal && !groupNameAdded) {
- nameFull.append(Reply::PeerEmoji(history, sender));
- }
- nameFull.append(shorten ? sender->shortName() : sender->name());
- if (groupNameAdded) {
- nameFull.append(' ').append(Reply::PeerEmoji(history, toPeer));
- nameFull.append(toPeer->name());
- }
- return (quote
- ? tr::lng_preview_reply_to_quote
- : tr::lng_preview_reply_to)(
- tr::now,
- lt_name,
- nameFull,
- Ui::Text::WithEntities);
- }
- void Reply::unloadPersistentAnimation() {
- _text.unloadPersistentAnimation();
- }
- } // namespace HistoryView
|