diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 5caa2ce5c8..47674bd5ef 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -55,6 +55,7 @@ public: rpl::producer value, Fn repaint); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; @@ -80,6 +81,10 @@ DefaultIconEmoji::DefaultIconEmoji( }, _lifetime); } +int DefaultIconEmoji::width() { + return st::emojiSize + 2 * st::emojiPadding; +} + QString DefaultIconEmoji::entityData() { return u"topic_icon:%1"_q.arg(_icon.colorId); } diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index e331703aeb..758590db90 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "data/stickers/data_custom_emoji.h" +#include "data/data_channel.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_user.h" @@ -54,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { +constexpr auto kNonExpandedLinesLimit = 5; const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; void ValidateBackgroundEmoji( @@ -469,7 +471,10 @@ FullReplyTo ReplyToFromMTP( }); } -HistoryMessageReply::HistoryMessageReply() = default; +HistoryMessageReply::HistoryMessageReply() +: _name(st::maxSignatureSize / 2) +, _text(st::maxSignatureSize / 2) { +} HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply &&other) = default; @@ -528,8 +533,7 @@ bool HistoryMessageReply::updateData( .customEmojiRepaint = repaint, }; const auto external = this->external(); - _multiline = 0; - // #TODO !_fields.storyId && (external || !_fields.quote.empty()); + _multiline = !_fields.storyId && (external || !_fields.quote.empty()); const auto displaying = resolvedMessage || resolvedStory @@ -607,29 +611,70 @@ void HistoryMessageReply::updateFields( } } +bool HistoryMessageReply::expand() { + if (!_expandable || _expanded) { + return false; + } + _expanded = true; + return true; +} + void HistoryMessageReply::setLinkFrom( not_null holder) { - const auto externalPeerId = _fields.externalSenderId; - const auto external = externalPeerId - || !_fields.externalSenderName.isEmpty(); + const auto externalChannelId = peerToChannel(_fields.externalPeerId); + const auto messageId = _fields.messageId; + const auto quote = _fields.manualQuote + ? _fields.quote + : TextWithEntities(); + const auto returnToId = holder->fullId(); const auto externalLink = [=](ClickContext context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { - if (externalPeerId) { - controller->showPeerInfo( - controller->session().data().peer(externalPeerId)); + auto error = QString(); + const auto owner = &controller->session().data(); + if (const auto item = owner->message(returnToId)) { + if (const auto reply = item->Get()) { + if (reply->expand()) { + owner->requestItemResize(item); + return; + } + } + } + if (externalChannelId) { + const auto channel = owner->channel(externalChannelId); + if (!channel->isForbidden()) { + if (messageId) { + JumpToMessageClickHandler( + channel, + messageId, + returnToId, + quote + )->onClick(context); + } else { + controller->showPeerInfo(channel); + } + } else if (channel->isBroadcast()) { + error = tr::lng_channel_not_accessible(tr::now); + } else { + error = tr::lng_group_not_accessible(tr::now); + } + } else { + error = tr::lng_reply_from_private_chat(tr::now); + } + if (!error.isEmpty()) { + controller->showToast(error); } - controller->showToast(tr::lng_reply_from_private_chat(tr::now)); } }; _link = resolvedMessage ? JumpToMessageClickHandler( resolvedMessage.get(), - holder->fullId(), - _fields.manualQuote ? _fields.quote : TextWithEntities()) + returnToId, + quote) : resolvedStory ? JumpToStoryClickHandler(resolvedStory.get()) - : (external && !_fields.messageId) + : (external() + && (!_fields.messageId || (_unavailable && externalChannelId))) ? std::make_shared(externalLink) : nullptr; } @@ -753,11 +798,13 @@ void HistoryMessageReply::updateName( || (resolvedMessage && resolvedMessage->media() && resolvedMessage->media()->hasReplyPreview()); - const auto previewSkip = st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right() - - st::historyReplyPadding.left(); + 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 @@ -774,7 +821,7 @@ void HistoryMessageReply::updateName( *peerIcon(peer))); }; auto nameFull = TextWithEntities(); - if (!groupNameAdded && !_fields.storyId) { + if (!groupNameAdded && external() && !_fields.storyId) { nameFull.append(peerEmoji(sender)); } nameFull.append(name); @@ -836,23 +883,61 @@ int HistoryMessageReply::resizeToWidth(int width) const { || (resolvedMessage && resolvedMessage->media() && resolvedMessage->media()->hasReplyPreview()); - const auto textLeft = hasPreview + const auto previewSkip = hasPreview ? (st::messageQuoteStyle.outline + st::historyReplyPreviewMargin.left() + st::historyReplyPreview - + st::historyReplyPreviewMargin.right()) - : st::historyReplyPadding.left(); + + st::historyReplyPreviewMargin.right() + - st::historyReplyPadding.left()) + : 0; if (width >= _maxWidth || !_multiline) { + _nameTwoLines = 0; + _expandable = 0; _height = _minHeight; return height(); } - // #TODO - _height = _minHeight; + const auto innerw = width + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = innerw - previewSkip; + const auto desiredNameHeight = _name.countHeight(namew); + _nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0; + const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height; + const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip; + auto lineCounter = 0; + auto elided = false; + const auto texth = _text.countDimensions( + textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height; + _expandable = (_multiline && elided) ? 1 : 0; + _height = st::historyReplyPadding.top() + + nameh + + (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth) + + st::historyReplyPadding.bottom(); return height(); } +Ui::Text::GeometryDescriptor HistoryMessageReply::textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const { + return { .layout = [=](Ui::Text::LineGeometry in) { + const auto skip = (*line ? 0 : firstLineSkip); + ++*line; + *outElided = *outElided + || !_multiline + || (!_expanded + && (*line == kNonExpandedLinesLimit) + && in.width > available - skip); + in.width = available - skip; + in.left += skip; + in.elided = *outElided; + return in; + } }; +} + int HistoryMessageReply::height() const { - return _height + st::historyReplyTop + st::historyReplyBottom; + return _height + st::historyReplyTop + st::historyReplyBottom; } QMargins HistoryMessageReply::margins() const { @@ -882,8 +967,13 @@ bool HistoryMessageReply::hasQuoteIcon() const { } QSize HistoryMessageReply::countMultilineOptimalSize( - int firstLineSkip) const { - return QSize(); // #TODO + 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( @@ -972,23 +1062,33 @@ void HistoryMessageReply::paint( } } - const auto withPreviewLeft = st::messageQuoteStyle.outline - + st::historyReplyPreviewMargin.left() - + st::historyReplyPreview - + st::historyReplyPreviewMargin.right(); - auto textLeft = st::historyReplyPadding.left(); + 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); - if (w > textLeft) { + auto textLeft = x + st::historyReplyPadding.left(); + auto textTop = y + + st::historyReplyPadding.top() + + (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1)); + if (w > st::historyReplyPadding.left()) { if (resolvedMessage || resolvedStory || !_text.isEmpty()) { const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; - auto hasPreview = (media && media->hasReplyPreview()) - || (resolvedStory && resolvedStory->hasReplyPreview()); - if (hasPreview && w <= withPreviewLeft) { - hasPreview = false; - } if (hasPreview) { - textLeft = withPreviewLeft; const auto image = media ? media->replyPreview() : resolvedStory->replyPreview(); @@ -1021,22 +1121,29 @@ void HistoryMessageReply::paint( } } } - if (w > textLeft + st::historyReplyPadding.right()) { - w -= textLeft + st::historyReplyPadding.right(); + 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 + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); + _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 replyToTextPosition = QPoint( - x + textLeft, - y + st::historyReplyPadding.top() + st::msgServiceNameFont->height); auto replyToTextPalette = &(!inBubble ? st->imgReplyTextPalette() : useColorIndex @@ -1045,13 +1152,12 @@ void HistoryMessageReply::paint( if (_fields.storyId) { st::dialogsMiniReplyStory.icon.icon.paint( p, - replyToTextPosition, - w + 2 * x + 2 * textLeft, + textLeft + firstLineSkip, + textTop, + w + 2 * x, replyToTextPalette->linkFg->c); - replyToTextPosition += QPoint( - st::dialogsMiniReplyStory.skipText - + st::dialogsMiniReplyStory.icon.icon.width(), - 0); + firstLineSkip += st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width(); } auto owned = std::optional(); auto copy = std::optional(); @@ -1061,9 +1167,11 @@ void HistoryMessageReply::paint( copy->linkFg = owned->color(); replyToTextPalette = &*copy; } + auto l = 0; + auto e = false; _text.draw(p, { - .position = replyToTextPosition, - .availableWidth = w, + .position = { textLeft, textTop }, + .geometry = textGeometry(textw, firstLineSkip, &l, &e), .palette = replyToTextPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, @@ -1078,9 +1186,9 @@ void HistoryMessageReply::paint( p.setFont(st::msgDateFont); p.setPen(cache->icon); p.drawTextLeft( - x + textLeft, - (y + (_height - st::msgDateFont->height) / 2), - w + 2 * x + 2 * textLeft, + textLeft, + y + st::historyReplyPadding.top() + (st::msgDateFont->height / 2), + w + 2 * x, st::msgDateFont->elided( statePhrase(), w - textLeft - st::historyReplyPadding.right())); diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 55c4e149bb..e4159106fe 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -25,6 +25,10 @@ struct PeerUserpicView; class SpoilerAnimation; } // namespace Ui +namespace Ui::Text { +struct GeometryDescriptor; +} // namespace Ui::Text + namespace Data { class Session; class Story; @@ -296,6 +300,8 @@ struct HistoryMessageReply not_null holder, not_null removed); + bool expand(); + void paint( Painter &p, not_null holder, @@ -353,6 +359,11 @@ struct HistoryMessageReply private: [[nodiscard]] bool hasQuoteIcon() const; + [[nodiscard]] Ui::Text::GeometryDescriptor textGeometry( + int available, + int firstLineSkip, + not_null line, + not_null outElided) const; [[nodiscard]] QSize countMultilineOptimalSize( int firstLineSkip) const; @@ -368,7 +379,9 @@ private: uint8 _unavailable : 1 = 0; uint8 _displaying : 1 = 0; uint8 _multiline : 1 = 0; - uint8 _expandable : 1 = 0; + mutable uint8 _expandable : 1 = 0; + uint8 _expanded : 1 = 0; + mutable uint8 _nameTwoLines : 1 = 0; }; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index 13570306b7..3571a9e63c 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -47,6 +47,7 @@ public: QPoint shift, int index); + int width() override; QString entityData() override; void paint(QPainter &p, const Context &context) override; void unload() override; @@ -73,6 +74,10 @@ StripEmoji::StripEmoji( , _index(index) { } +int StripEmoji::width() { + return _wrapped->width(); +} + QString StripEmoji::entityData() { return _wrapped->entityData(); }