mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Extract reply view to a separate component.
This commit is contained in:
parent
56ad825693
commit
4e0490494e
18 changed files with 1037 additions and 880 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 "eSt = hasQuote
|
||||
? st::messageTextStyle.blockquote
|
||||
: st::messageQuoteStyle;
|
||||
const auto backgroundEmoji = backgroundEmojiId
|
||||
? st->backgroundEmojiData(backgroundEmojiId).get()
|
||||
: nullptr;
|
||||
const auto backgroundEmojiCache = backgroundEmoji
|
||||
? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex(
|
||||
selected,
|
||||
context.outbg,
|
||||
inBubble,
|
||||
colorIndexPlusOne)]
|
||||
: nullptr;
|
||||
const auto rippleColor = cache->bg;
|
||||
if (!inBubble) {
|
||||
cache->bg = QColor(0, 0, 0, 0);
|
||||
}
|
||||
Ui::Text::ValidateQuotePaintCache(*cache, quoteSt);
|
||||
Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt);
|
||||
if (backgroundEmoji) {
|
||||
ValidateBackgroundEmoji(
|
||||
backgroundEmojiId,
|
||||
backgroundEmoji,
|
||||
backgroundEmojiCache,
|
||||
cache,
|
||||
holder);
|
||||
if (!backgroundEmojiCache->frames[0].isNull()) {
|
||||
FillBackgroundEmoji(p, rect, hasQuote, *backgroundEmojiCache);
|
||||
}
|
||||
}
|
||||
if (!inBubble) {
|
||||
cache->bg = rippleColor;
|
||||
}
|
||||
|
||||
if (ripple.animation) {
|
||||
ripple.animation->paint(p, x, y, w, &rippleColor);
|
||||
if (ripple.animation->empty()) {
|
||||
ripple.animation.reset();
|
||||
}
|
||||
}
|
||||
|
||||
auto hasPreview = (resolvedStory
|
||||
&& resolvedStory->hasReplyPreview())
|
||||
|| (resolvedMessage
|
||||
&& resolvedMessage->media()
|
||||
&& resolvedMessage->media()->hasReplyPreview());
|
||||
auto previewSkip = hasPreview
|
||||
? (st::messageQuoteStyle.outline
|
||||
+ st::historyReplyPreviewMargin.left()
|
||||
+ st::historyReplyPreview
|
||||
+ st::historyReplyPreviewMargin.right()
|
||||
- st::historyReplyPadding.left())
|
||||
: 0;
|
||||
if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) {
|
||||
hasPreview = false;
|
||||
previewSkip = 0;
|
||||
}
|
||||
|
||||
const auto pausedSpoiler = context.paused
|
||||
|| On(PowerSaving::kChatSpoiler);
|
||||
auto textLeft = x + st::historyReplyPadding.left();
|
||||
auto textTop = y
|
||||
+ st::historyReplyPadding.top()
|
||||
+ (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1));
|
||||
if (w > st::historyReplyPadding.left()) {
|
||||
if (_displaying) {
|
||||
const auto media = resolvedMessage ? resolvedMessage->media() : nullptr;
|
||||
if (hasPreview) {
|
||||
const auto image = media
|
||||
? media->replyPreview()
|
||||
: resolvedStory->replyPreview();
|
||||
if (image) {
|
||||
auto to = style::rtlrect(
|
||||
x + st::historyReplyPreviewMargin.left(),
|
||||
y + st::historyReplyPreviewMargin.top(),
|
||||
st::historyReplyPreview,
|
||||
st::historyReplyPreview,
|
||||
w + 2 * x);
|
||||
const auto preview = image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.colored = (context.selected()
|
||||
? &st->msgStickerOverlay()
|
||||
: nullptr),
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = to.size(),
|
||||
});
|
||||
p.drawPixmap(to.x(), to.y(), preview);
|
||||
if (spoiler) {
|
||||
holder->clearCustomEmojiRepaint();
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
spoiler->index(
|
||||
context.now,
|
||||
pausedSpoiler)));
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto textw = w
|
||||
- st::historyReplyPadding.left()
|
||||
- st::historyReplyPadding.right();
|
||||
const auto namew = textw - previewSkip;
|
||||
auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
|
||||
if (namew > 0) {
|
||||
p.setPen(!inBubble
|
||||
? st->msgImgReplyBarColor()->c
|
||||
: useColorIndex
|
||||
? FromNameFg(context, colorIndexPlusOne - 1)
|
||||
: stm->msgServiceFg->c);
|
||||
_name.drawLeftElided(
|
||||
p,
|
||||
x + st::historyReplyPadding.left() + previewSkip,
|
||||
y + st::historyReplyPadding.top(),
|
||||
namew,
|
||||
w + 2 * x,
|
||||
_nameTwoLines ? 2 : 1);
|
||||
|
||||
p.setPen(inBubble
|
||||
? stm->historyTextFg
|
||||
: st->msgImgReplyBarColor());
|
||||
holder->prepareCustomEmojiPaint(p, context, _text);
|
||||
auto replyToTextPalette = &(!inBubble
|
||||
? st->imgReplyTextPalette()
|
||||
: useColorIndex
|
||||
? st->coloredTextPalette(selected, colorIndexPlusOne - 1)
|
||||
: stm->replyTextPalette);
|
||||
if (_fields.storyId) {
|
||||
st::dialogsMiniReplyStory.icon.icon.paint(
|
||||
p,
|
||||
textLeft + firstLineSkip,
|
||||
textTop,
|
||||
w + 2 * x,
|
||||
replyToTextPalette->linkFg->c);
|
||||
firstLineSkip += st::dialogsMiniReplyStory.skipText
|
||||
+ st::dialogsMiniReplyStory.icon.icon.width();
|
||||
}
|
||||
auto owned = std::optional<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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
804
Telegram/SourceFiles/history/view/history_view_reply.cpp
Normal file
804
Telegram/SourceFiles/history/view/history_view_reply.cpp
Normal 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 "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()
|
||||
: 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
|
116
Telegram/SourceFiles/history/view/history_view_reply.h
Normal file
116
Telegram/SourceFiles/history/view/history_view_reply.h
Normal 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
|
|
@ -419,7 +419,7 @@ bool ExtendedPreview::needsBubble() const {
|
|||
&& (item->repliesAreComments()
|
||||
|| item->externalReply()
|
||||
|| item->viaBot()
|
||||
|| _parent->displayedReply()
|
||||
|| _parent->displayReply()
|
||||
|| _parent->displayForwardedFrom()
|
||||
|| _parent->displayFromName()
|
||||
|| _parent->displayedTopicButton());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1077,7 +1077,7 @@ bool Photo::needsBubble() const {
|
|||
&& (item->repliesAreComments()
|
||||
|| item->externalReply()
|
||||
|| item->viaBot()
|
||||
|| _parent->displayedReply()
|
||||
|| _parent->displayReply()
|
||||
|| _parent->displayForwardedFrom()
|
||||
|| _parent->displayFromName()
|
||||
|| _parent->displayedTopicButton());
|
||||
|
|
Loading…
Add table
Reference in a new issue