Extract reply view to a separate component.

This commit is contained in:
John Preston 2023-11-03 13:25:11 +04:00
parent 56ad825693
commit 4e0490494e
18 changed files with 1037 additions and 880 deletions

View File

@ -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

View File

@ -723,7 +723,7 @@ HistoryItem::HistoryItem(
HistoryItem::~HistoryItem() {
_media = nullptr;
clearSavedMedia();
if (auto reply = Get<HistoryMessageReply>()) {
if (const auto reply = Get<HistoryMessageReply>()) {
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<HistoryMessageReply>()) {
if (reply->link()) {
reply->setLinkFrom(this);
}
incrementReplyToTopCounter();
}
}

View File

@ -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<Ui::BackgroundEmojiData*> data,
not_null<Ui::BackgroundEmojiCache*> cache,
not_null<Ui::Text::QuotePaintCache*> quote,
not_null<const HistoryView::Element*> 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<Ui::SpoilerAnimation>(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<HistoryItem*> 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<ClickHandlerContext>();
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<HistoryMessageReply>()) {
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<LambdaClickHandler>(externalLink)
: nullptr;
}
void HistoryMessageReply::setTopMessageId(MsgId topMessageId) {
_fields.topMessageId = topMessageId;
}
@ -696,11 +470,8 @@ void HistoryMessageReply::clearData(not_null<HistoryItem*> 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<HistoryItem*> 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<HistoryMessageForwarded>()) {
// Forward of a reply. Show reply-to original sender.
const auto forwarded
= resolvedMessage->Get<HistoryMessageForwarded>();
if (forwarded) {
return forwarded->originalSender;
}
}
if (const auto from = resolvedMessage->displayFrom()) {
return from;
}
return resolvedMessage->author().get();
}
QString HistoryMessageReply::senderName(
not_null<HistoryItem*> holder,
bool shorten) const {
if (const auto peer = sender(holder)) {
return senderName(peer, shorten);
} else if (!resolvedMessage) {
return _fields.externalSenderName;
} else if (holder->Has<HistoryMessageForwarded>()) {
// Forward of a reply. Show reply-to original sender.
const auto forwarded
= resolvedMessage->Get<HistoryMessageForwarded>();
if (forwarded) {
Assert(forwarded->hiddenSenderInfo != nullptr);
return forwarded->hiddenSenderInfo->name;
}
}
return QString();
}
QString HistoryMessageReply::senderName(
not_null<PeerData*> peer,
bool shorten) const {
const auto user = shorten ? peer->asUser() : nullptr;
return user ? user->firstName : peer->name();
}
bool HistoryMessageReply::isNameUpdated(
not_null<HistoryItem*> holder) const {
if (const auto from = sender(holder)) {
if (_nameVersion < from->nameVersion()) {
updateName(holder, from);
return true;
}
}
return false;
}
void HistoryMessageReply::updateName(
not_null<HistoryItem*> holder,
std::optional<PeerData*> resolvedSender) const {
auto viaBotUsername = QString();
if (resolvedMessage
&& !resolvedMessage->Has<HistoryMessageForwarded>()) {
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<int*> line,
not_null<bool*> 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<HistoryItem*> holder,
not_null<HistoryItem*> 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<const HistoryView::Element*> 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 &quoteSt = 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<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;
}
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;

View File

@ -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<HistoryItem*> holder);
[[nodiscard]] bool external() const;
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
[[nodiscard]] QString senderName(
not_null<HistoryItem*> holder,
bool shorten) const;
[[nodiscard]] QString senderName(
not_null<PeerData*> peer,
bool shorten) const;
[[nodiscard]] bool isNameUpdated(not_null<HistoryItem*> holder) const;
void updateName(
not_null<HistoryItem*> holder,
std::optional<PeerData*> resolvedSender = std::nullopt) const;
[[nodiscard]] int resizeToWidth(int width) const;
[[nodiscard]] int height() const;
[[nodiscard]] QMargins margins() const;
void itemRemoved(
not_null<HistoryItem*> holder,
not_null<HistoryItem*> removed);
@ -300,19 +283,7 @@ struct HistoryMessageReply
not_null<HistoryItem*> holder,
not_null<Data::Story*> removed);
bool expand();
void paint(
Painter &p,
not_null<const HistoryView::Element*> 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<HistoryItem*> holder);
void setTopMessageId(MsgId topMessageId);
void refreshReplyToMedia();
@ -350,38 +322,12 @@ struct HistoryMessageReply
WebPageId replyToWebPageId = 0;
ReplyToMessagePointer resolvedMessage;
ReplyToStoryPointer resolvedStory;
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
struct {
mutable std::unique_ptr<Ui::RippleAnimation> animation;
QPoint lastPoint;
} ripple;
private:
[[nodiscard]] bool hasQuoteIcon() const;
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
int available,
int firstLineSkip,
not_null<int*> line,
not_null<bool*> 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;
};

View File

@ -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<Reply>();
}
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<HistoryMessageReply>()) {
if (const auto reply = Get<Reply>()) {
reply->unloadPersistentAnimation();
}
}

View File

@ -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<QPoint> pressPoint) const;
[[nodiscard]] virtual TimeId displayedEditDate() const;
[[nodiscard]] virtual bool hasVisibleText() const;
[[nodiscard]] virtual HistoryMessageReply *displayedReply() const;
virtual void applyGroupAdminChanges(
const base::flat_set<UserId> &changes) {
}

View File

@ -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<HistoryMessageReply>();
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<Reply>();
if (reply) {
reply->update(this, replyData);
}
if (drawBubble()) {
const auto forwarded = item->Get<HistoryMessageForwarded>();
const auto reply = displayedReply();
const auto via = item->Get<HistoryMessageVia>();
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<Message*>(this)->setPendingResize();
if (const auto reply = Get<Reply>()) {
if (const auto replyData = item->Get<HistoryMessageReply>()) {
if (reply->isNameUpdated(this, replyData)) {
const_cast<Message*>(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>()) {
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>()
; 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<Reply>();
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<Ui::RippleAnimation>(
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<TextState*> outResult) const {
if (const auto reply = displayedReply()) {
if (const auto reply = Get<Reply>()) {
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<HistoryMessageReply>()) {
if (const auto reply = Get<Reply>()) {
trect.setTop(trect.top() + reply->height());
}
if (const auto via = item->Get<HistoryMessageVia>()) {
@ -3123,13 +3137,6 @@ WebPage *Message::logEntryOriginal() const {
return nullptr;
}
HistoryMessageReply *Message::displayedReply() const {
if (const auto reply = data()->Get<HistoryMessageReply>()) {
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<Reply>()
|| item->Has<HistoryMessageVia>();
};
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<Reply>()) {
// 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<Reply>();
auto via = item->Get<HistoryMessageVia>();
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);

View File

@ -137,7 +137,6 @@ public:
[[nodiscard]] ClickHandlerPtr rightActionLink(
std::optional<QPoint> 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> _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;

View File

@ -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<Ui::BackgroundEmojiData*> data,
not_null<Ui::BackgroundEmojiCache*> cache,
not_null<Ui::Text::QuotePaintCache*> quote,
not_null<const Element*> 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<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();
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<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 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
)->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<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->hiddenSenderInfo != nullptr);
return forwarded->hiddenSenderInfo->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 story = data->resolvedStory.get();
const auto message = data->resolvedMessage.get();
if (message && !message->Has<HistoryMessageForwarded>()) {
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<int*> line,
not_null<bool*> 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<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 &quoteSt = _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()
: 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<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;
}
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<const Element*> view,
QSize size) {
_ripple.animation = std::make_unique<Ui::RippleAnimation>(
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

View File

@ -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<Reply, Element> {
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<Element*> view,
not_null<HistoryMessageReply*> data);
[[nodiscard]] bool isNameUpdated(
not_null<const Element*> view,
not_null<HistoryMessageReply*> data) const;
void updateName(
not_null<const Element*> view,
not_null<HistoryMessageReply*> data,
std::optional<PeerData*> 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<const Element*> view,
const Ui::ChatPaintContext &context,
int x,
int y,
int w,
bool inBubble) const;
void unloadPersistentAnimation();
void createRippleAnimation(not_null<const Element*> 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<int*> line,
not_null<bool*> outElided) const;
[[nodiscard]] QSize countMultilineOptimalSize(
int firstLineSkip) const;
void setLinkFrom(
not_null<Element*> view,
not_null<HistoryMessageReply*> data);
[[nodiscard]] PeerData *sender(
not_null<const Element*> view,
not_null<HistoryMessageReply*> data) const;
[[nodiscard]] QString senderName(
not_null<const Element*> view,
not_null<HistoryMessageReply*> data,
bool shorten) const;
[[nodiscard]] QString senderName(
not_null<PeerData*> peer,
bool shorten) const;
ClickHandlerPtr _link;
std::unique_ptr<Ui::SpoilerAnimation> _spoiler;
mutable PeerData *_externalSender = nullptr;
mutable PeerData *_colorPeer = nullptr;
mutable struct {
mutable std::unique_ptr<Ui::RippleAnimation> 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

View File

@ -419,7 +419,7 @@ bool ExtendedPreview::needsBubble() const {
&& (item->repliesAreComments()
|| item->externalReply()
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName()
|| _parent->displayedTopicButton());

View File

@ -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<HistoryMessageVia>();
auto reply = _parent->displayedReply();
auto reply = _parent->Get<Reply>();
auto forwarded = item->Get<HistoryMessageForwarded>();
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<HistoryMessageVia>();
auto reply = _parent->displayedReply();
auto reply = _parent->Get<Reply>();
auto forwarded = item->Get<HistoryMessageForwarded>();
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<HistoryMessageVia>() : nullptr;
const auto reply = unwrapped ? _parent->displayedReply() : nullptr;
const auto reply = unwrapped ? _parent->Get<Reply>() : nullptr;
const auto forwarded = unwrapped ? item->Get<HistoryMessageForwarded>() : 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<HistoryMessageVia>() : nullptr;
const auto reply = unwrapped ? _parent->displayedReply() : nullptr;
const auto reply = unwrapped ? _parent->Get<Reply>() : nullptr;
const auto forwarded = unwrapped ? item->Get<HistoryMessageForwarded>() : 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<Ui::RippleAnimation>(
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<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto reply = _parent->Get<Reply>();
const auto forwarded = item->Get<HistoryMessageForwarded>();
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<Reply>(),
item->Get<HistoryMessageVia>(),
item->Get<HistoryMessageReply>(),
item->Get<HistoryMessageForwarded>());
}
@ -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());

View File

@ -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;

View File

@ -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();

View File

@ -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()

View File

@ -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<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto reply = _parent->Get<Reply>();
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<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto reply = _parent->Get<Reply>();
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<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto reply = inWebPage ? nullptr : _parent->Get<Reply>();
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<HistoryMessageVia>();
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
const auto reply = inWebPage ? nullptr : _parent->Get<Reply>();
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<Ui::RippleAnimation>(
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<Reply>();
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) {

View File

@ -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;

View File

@ -1077,7 +1077,7 @@ bool Photo::needsBubble() const {
&& (item->repliesAreComments()
|| item->externalReply()
|| item->viaBot()
|| _parent->displayedReply()
|| _parent->displayReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName()
|| _parent->displayedTopicButton());