Improve layout of gift / auction webpage preview.

This commit is contained in:
John Preston
2026-03-13 18:36:28 +04:00
parent eef6ba797f
commit ff54ea88e2
9 changed files with 117 additions and 28 deletions
@@ -1417,8 +1417,7 @@ void Element::overrideMedia(std::unique_ptr<Media> 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<Element*>(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() {
@@ -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<ClickHandlerPtr> &links = {});
@@ -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<FakeBotAboutTop>()
: 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<HistoryMessageForwarded>();
const auto via = item->Get<HistoryMessageVia>();
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<Reply>();
@@ -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;
@@ -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> _fromNameStatus;
mutable std::unique_ptr<Ui::RoundCheckbox> _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;
@@ -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)
@@ -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> 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;
@@ -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;
@@ -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) {
@@ -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();