diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 62c48e016a..f3d69fc137 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1417,8 +1417,7 @@ void Element::overrideMedia(std::unique_ptr media) { Expects(!history()->owner().groups().find(data())); _text = Ui::Text::String(st::msgMinWidth); - _textWidth = -1; - _textHeight = 0; + invalidateTextSizeCache(); _media = std::move(media); if (!pendingResize()) { @@ -1603,8 +1602,8 @@ OnlyEmojiAndSpaces Element::isOnlyEmojiAndSpaces() const { } } -int Element::textHeightFor(int textWidth) { - validateText(); +int Element::textHeightFor(int textWidth) const { + const_cast(this)->validateText(); if (_textWidth != textWidth) { _textWidth = textWidth; _textHeight = _text.countHeight(textWidth); @@ -1873,20 +1872,17 @@ void Element::setTextWithLinks( if (const auto next = _text.nextFormattedDateUpdate()) { history()->session().data().registerFormattedDateUpdate(next, this); } - _textWidth = -1; - _textHeight = 0; + invalidateTextSizeCache(); } void Element::validateTextSkipBlock(bool has, int width, int height) { validateText(); if (!has) { if (_text.removeSkipBlock()) { - _textWidth = -1; - _textHeight = 0; + invalidateTextSizeCache(); } } else if (_text.updateSkipBlock(width, height)) { - _textWidth = -1; - _textHeight = 0; + invalidateTextSizeCache(); } } @@ -2519,17 +2515,21 @@ void Element::itemTextUpdated() { _flags &= ~Flag::SummaryShown; clearSpecialOnlyEmoji(); _text = Ui::Text::String(st::msgMinWidth); - _textWidth = -1; - _textHeight = 0; + invalidateTextSizeCache(); if (_media && !data()->media()) { refreshMedia(nullptr); } } void Element::blockquoteExpandChanged() { + invalidateTextSizeCache(); + history()->owner().requestViewResize(this); +} + +void Element::invalidateTextSizeCache() { _textWidth = -1; _textHeight = 0; - history()->owner().requestViewResize(this); + invalidateTextDependentCache(); } void Element::unloadHeavyPart() { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3da8a11ba5..9b789387c5 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -707,7 +707,7 @@ protected: virtual void refreshDataIdHook(); [[nodiscard]] const Ui::Text::String &text() const; - [[nodiscard]] int textHeightFor(int textWidth); + [[nodiscard]] int textHeightFor(int textWidth) const; void validateText(); void validateTextSkipBlock(bool has, int width, int height); void validateInlineKeyboard(HistoryMessageReplyMarkup *markup); @@ -743,8 +743,11 @@ private: virtual QSize performCountOptimalSize() = 0; virtual QSize performCountCurrentSize(int newWidth) = 0; + virtual void invalidateTextDependentCache() { + } void refreshMedia(Element *replacing); + void invalidateTextSizeCache(); void setTextWithLinks( const TextWithEntities &text, const std::vector &links = {}); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 8cc6624a2b..f81ab6a392 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -64,6 +64,7 @@ namespace { constexpr auto kSummarizeThreshold = 512; constexpr auto kPlayStatusLimit = 2; +constexpr auto kMaxNiceToReadLines = 6; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; struct SecondRightAction { @@ -673,6 +674,9 @@ QSize Message::performCountOptimalSize() { const auto botTop = item->isFakeAboutView() ? Get() : nullptr; + const auto bubble = drawBubble(); + auto withVisibleText = false; + auto fullTextualWidth = 0; if (botTop) { botTop->init(); } @@ -696,7 +700,7 @@ QSize Message::performCountOptimalSize() { } } - if (drawBubble()) { + if (bubble) { const auto forwarded = item->Get(); const auto via = item->Get(); const auto entry = logEntryOriginal(); @@ -718,8 +722,9 @@ QSize Message::performCountOptimalSize() { } // Entry page is always a bubble bottom. - const auto withVisibleText = hasVisibleText(); - const auto textualWidth = textualMaxWidth(); + withVisibleText = hasVisibleText(); + fullTextualWidth = textualMaxWidth(); + const auto textualWidth = bubbleTextualWidth(); auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/); auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); maxWidth = textualWidth; @@ -770,12 +775,6 @@ QSize Message::performCountOptimalSize() { const auto innerWidth = maxWidth - st::msgPadding.left() - st::msgPadding.right(); - if (withVisibleText) { - if (maxWidth < textualWidth) { - minHeight -= text().minHeight(); - minHeight += text().countHeight(innerWidth); - } - } if (reactionsInBubble) { minHeight -= _reactions->minHeight(); minHeight @@ -869,6 +868,10 @@ QSize Message::performCountOptimalSize() { if (markup && markup->inlineKeyboard && hasVisibleText()) { accumulate_max(maxWidth, markup->inlineKeyboard->naturalWidth()); } + if (bubble && withVisibleText && maxWidth < fullTextualWidth) { + minHeight -= text().minHeight(); + minHeight += textHeightFor(bubbleTextWidth(maxWidth)); + } return QSize(maxWidth, minHeight); } @@ -4021,6 +4024,52 @@ int Message::monospaceMaxWidth() const { + st::msgPadding.right(); } +int Message::bubbleTextWidth(int bubbleWidth) const { + return std::max(bubbleWidth, st::msgMinWidth) + - st::msgPadding.left() + - st::msgPadding.right(); +} + +int Message::bubbleTextualWidth() const { + const auto full = textualMaxWidth(); + const auto media = this->media(); + if (!hasVisibleText() + || !media + || !media->allowsNarrowBubble()) { + return full; + } + const auto minimum = std::max( + media->minBubbleWidthForNarrowBubble(), + st::msgMinWidth); + if (_bubbleTextualWidthMinimum != minimum) { + _bubbleTextualWidthMinimum = minimum; + if (minimum >= full) { + _bubbleTextualWidthCache = minimum; + } else { + const auto lineHeight = text().style()->font->height; + const auto fullTextHeight = textHeightFor(bubbleTextWidth(full)); + if (fullTextHeight > kMaxNiceToReadLines * lineHeight) { + _bubbleTextualWidthCache = full; + } else { + auto left = minimum; + auto right = full; + while (left < right) { + const auto middle = left + (right - left) / 2; + const auto middleHeight = textHeightFor( + bubbleTextWidth(middle)); + if (middleHeight <= kMaxNiceToReadLines * lineHeight) { + right = middle; + } else { + left = middle + 1; + } + } + _bubbleTextualWidthCache = right; + } + } + } + return _bubbleTextualWidthCache; +} + int Message::viewButtonHeight() const { return _viewButton ? _viewButton->height() : 0; } @@ -4801,7 +4850,7 @@ QRect Message::countGeometry() const { accumulate_min(contentWidth, maxWidth()); accumulate_min(contentWidth, int(_bubbleWidthLimit)); if (mediaWidth < contentWidth) { - const auto textualWidth = textualMaxWidth(); + const auto textualWidth = bubbleTextualWidth(); if (mediaWidth < textualWidth && (!media || !media->enforceBubbleWidth())) { accumulate_min(contentWidth, textualWidth); @@ -4930,10 +4979,10 @@ int Message::resizeContentGetHeight(int newWidth) { accumulate_min(contentWidth, maxWidth()); _bubbleWidthLimit = std::max(st::msgMaxWidth, monospaceMaxWidth()); accumulate_min(contentWidth, int(_bubbleWidthLimit)); + const auto textualWidth = bubbleTextualWidth(); if (mediaDisplayed) { media->resizeGetHeight(contentWidth); if (media->width() < contentWidth) { - const auto textualWidth = textualMaxWidth(); if (media->width() < textualWidth && !media->enforceBubbleWidth()) { accumulate_min(contentWidth, textualWidth); @@ -4942,12 +4991,17 @@ int Message::resizeContentGetHeight(int newWidth) { } } } - const auto textWidth = qMax(contentWidth - st::msgPadding.left() - st::msgPadding.right(), 1); + const auto bottomInfoWidth = qMax( + contentWidth - st::msgPadding.left() - st::msgPadding.right(), + 1); + const auto textWidth = bubble + ? bubbleTextWidth(contentWidth) + : bottomInfoWidth; const auto reactionsInBubble = _reactions && embedReactionsInBubble(); const auto bottomInfoHeight = _bottomInfo.resizeGetHeight( std::min( _bottomInfo.optimalSize().width(), - textWidth - 2 * st::msgDateDelta.x())); + bottomInfoWidth - 2 * st::msgDateDelta.x())); if (bubble) { auto reply = Get(); @@ -5094,6 +5148,11 @@ int Message::resizeContentGetHeight(int newWidth) { return newHeight; } +void Message::invalidateTextDependentCache() { + _bubbleTextualWidthMinimum = -1; + _bubbleTextualWidthCache = 0; +} + bool Message::needInfoDisplay() const { const auto media = this->media(); const auto mediaDisplayed = media ? media->isDisplayed() : false; diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 095f70744e..6ebe83cce4 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -349,6 +349,8 @@ private: void refreshTopicButton(); void refreshInfoSkipBlock(HistoryItem *textItem); [[nodiscard]] int monospaceMaxWidth() const; + [[nodiscard]] int bubbleTextWidth(int bubbleWidth) const; + [[nodiscard]] int bubbleTextualWidth() const; void ensureSummarizeButton() const; void paintSummarize( @@ -368,6 +370,7 @@ private: [[nodiscard]] ClickHandlerPtr createGoToCommentsLink() const; [[nodiscard]] ClickHandlerPtr psaTooltipLink() const; void psaTooltipToggled(bool shown) const; + void invalidateTextDependentCache() override; void refreshRightBadge(); [[nodiscard]] int rightBadgeWidth() const; @@ -387,6 +390,8 @@ private: mutable std::unique_ptr _fromNameStatus; mutable std::unique_ptr _selectionRoundCheckbox; mutable int _fromNameVersion = 0; + mutable int _bubbleTextualWidthMinimum : 16 = -1; + mutable int _bubbleTextualWidthCache : 16 = 0; uint32 _bubbleWidthLimit : 27 = 0; uint32 _invertMedia : 1 = 0; uint32 _hideReply : 1 = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 1cf8f4747e..310a5ce7ce 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -330,6 +330,12 @@ public: [[nodiscard]] virtual bool enforceBubbleWidth() const { return false; } + [[nodiscard]] virtual bool allowsNarrowBubble() const { + return false; + } + [[nodiscard]] virtual int minBubbleWidthForNarrowBubble() const { + return 0; + } // Sometimes click on media in message is overloaded by the message: // (for example it can open a link or a game instead of opening media) diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp index bc249c23e2..7f522d5a50 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp @@ -66,6 +66,7 @@ MediaGeneric::MediaGeneric( , _paintBg(_paintBgFactory ? _paintBgFactory() : nullptr) , _fullAreaLink(descriptor.fullAreaLink) , _maxWidthCap(descriptor.maxWidth) +, _expandCurrentWidth(descriptor.expandCurrentWidth) , _service(descriptor.service) , _hideServiceText(descriptor.hideServiceText) { generate(this, [&](std::unique_ptr part) { @@ -97,7 +98,7 @@ QSize MediaGeneric::countOptimalSize() { } QSize MediaGeneric::countCurrentSize(int newWidth) { - if (newWidth > maxWidth()) { + if (!_expandCurrentWidth && newWidth > maxWidth()) { newWidth = maxWidth(); } auto top = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h index 2251ba8840..61d61e2ba3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h @@ -61,6 +61,7 @@ struct MediaGenericDescriptor { int maxWidth = 0; MediaGenericPart::PaintBgFactory paintBgFactory; ClickHandlerPtr fullAreaLink; + bool expandCurrentWidth = false; bool service = false; bool hideServiceText = false; }; @@ -133,6 +134,7 @@ private: int _maxWidthCap = 0; int _marginTop = 0; int _marginBottom = 0; + bool _expandCurrentWidth : 1 = false; bool _service : 1 = false; bool _hideServiceText : 1 = false; diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 1cf85bf2f6..22297fa742 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -533,6 +533,7 @@ QSize WebPage::countOptimalSize() { .paintBgFactory = [=] { return UniqueGiftBg(_parent, _data->uniqueGift); }, + .expandCurrentWidth = true, }); } else if (!_attach && _data->auction) { const auto &gift = _data->auction->auctionGift; @@ -556,6 +557,7 @@ QSize WebPage::countOptimalSize() { _data->auction->auctionGift->auctionStartDate, _data->auction->endDate); }, + .expandCurrentWidth = true, }); } else if (!_attach && !_asArticle) { _attach = CreateAttach( @@ -1623,6 +1625,15 @@ bool WebPage::enforceBubbleWidth() const { && (_data->document->isWallPaper() || _data->document->isTheme()); } +bool WebPage::allowsNarrowBubble() const { + return (_attach != nullptr) + && (_data->uniqueGift != nullptr || _data->auction != nullptr); +} + +int WebPage::minBubbleWidthForNarrowBubble() const { + return allowsNarrowBubble() ? maxWidth() : 0; +} + void WebPage::playAnimation(bool autoplay) { if (_attach) { if (autoplay) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h index e96a534cc4..63cca4e6c5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h @@ -94,6 +94,8 @@ public: return true; } bool enforceBubbleWidth() const override; + bool allowsNarrowBubble() const override; + int minBubbleWidthForNarrowBubble() const override; Media *attach() const { return _attach.get();