From f15b883471a77a353e91000fdf115f95609138fc Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Nov 2025 13:29:19 +0400 Subject: [PATCH] Support subtext in Stars Bubble in bids. --- Telegram/Resources/langs/lang.strings | 1 + .../boxes/star_gift_auction_box.cpp | 21 +++++ .../payments/ui/payments_reaction_box.cpp | 2 +- .../payments/ui/payments_reaction_box.h | 6 +- Telegram/SourceFiles/ui/effects/premium.style | 6 ++ .../SourceFiles/ui/effects/premium_bubble.cpp | 82 ++++++++++++++----- .../SourceFiles/ui/effects/premium_bubble.h | 7 +- 7 files changed, 100 insertions(+), 25 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a3a51bfd72..6b15cbe9b4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4031,6 +4031,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_auction_menu_share" = "Share"; "lng_auction_bid_title" = "Place a Bid"; "lng_auction_bid_your" = "your bid"; +"lng_auction_bid_custom" = "click to bid more"; "lng_auction_bid_threshold#one" = "TOP {count}"; "lng_auction_bid_threshold#other" = "TOP {count}"; "lng_auction_bid_minimal#one" = "minimum bid"; diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp index 6d56bf093f..271ab4c6ea 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -536,6 +536,7 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { rpl::variable value; rpl::variable chosen; + rpl::variable subtext; }; const auto state = box->lifetime().make_state( std::move(args.state)); @@ -556,6 +557,23 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { const auto chosen = mine ? mine : std::clamp(mine, min, max); state->chosen = chosen; + state->subtext = rpl::combine( + state->value.value(), + state->chosen.value() + ) | rpl::map([=]( + const Data::GiftAuctionState &state, + int chosen) { + if (state.my.bid == chosen) { + return tr::lng_auction_bid_your(tr::now); + } else if (chosen == max) { + return tr::lng_auction_bid_custom(tr::now); + } else if (state.my.bid && chosen > state.my.bid) { + const auto delta = chosen - state.my.bid; + return '+' + Lang::FormatCountDecimal(delta); + } + return QString(); + }); + const auto giftId = now.gift->id; const auto show = args.show; args.peer->owner().giftAuctionGots( @@ -634,6 +652,9 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { state->chosen = value; }), std::move(min), state->chosen.current())); }); + state->subtext.value() | rpl::start_with_next([=](QString &&text) { + bubble->setSubtext(std::move(text)); + }, bubble->lifetime()); PaidReactionSlider( content, diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp index f0e4a4c346..15ed49ab2e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -937,7 +937,7 @@ void AddStarSelectBalance( }, balance->lifetime()); } -not_null AddStarSelectBubble( +not_null AddStarSelectBubble( not_null box, rpl::producer value, int max, diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h index ff88975652..26836f0630 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -19,6 +19,10 @@ namespace Main { class Session; } // namespace Main +namespace Ui::Premium { +class BubbleWidget; +} // namespace Ui::Premium + namespace Ui { class AbstractButton; @@ -94,7 +98,7 @@ void AddStarSelectBalance( rpl::producer balanceValue, bool dark = false); -not_null AddStarSelectBubble( +not_null AddStarSelectBubble( not_null box, rpl::producer value, int max, diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index 2de0534ec7..298e315055 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -27,6 +27,8 @@ PremiumBubble { font: font; additionalStyle: TextStyle; additionalSkip: pixels; + subtextStyle: TextStyle; + subtextPadding: margins; } PremiumCover { bg: color; @@ -263,6 +265,10 @@ boostBubble: PremiumBubble(defaultPremiumBubble) { font: font(14px semibold); } additionalSkip: 2px; + subtextStyle: TextStyle(defaultTextStyle) { + font: font(10px); + } + subtextPadding: margins(11px, -5px, 11px, 0px); } starRatingBubble: PremiumBubble(boostBubble) { font: font(16px semibold); diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp index f1cf19a3c5..88ed2352e7 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp @@ -56,7 +56,6 @@ Bubble::Bubble( , _icon(icon) , _numberAnimation(_st.font, _updateCallback) , _height(_st.height + _st.tailSize.height()) -, _textTop((_height - _st.tailSize.height() - _st.font->height) / 2) , _hasTail(hasTail) { _numberAnimation.setDisabledMonospace(true); _numberAnimation.setWidthChangedCallback([=] { @@ -83,7 +82,7 @@ int Bubble::height() const { } int Bubble::bubbleRadius() const { - return (_height - _st.tailSize.height()) / 2 - kBubbleRadiusSubtractor; + return (_st.height / 2) - kBubbleRadiusSubtractor; } int Bubble::filledWidth() const { @@ -93,7 +92,7 @@ int Bubble::filledWidth() const { + _st.padding.right(); } -int Bubble::width() const { +int Bubble::topTextWidth() const { return filledWidth() + _numberAnimation.countWidth() + (_additional.isEmpty() @@ -101,21 +100,16 @@ int Bubble::width() const { : (_st.additionalSkip + _additional.maxWidth())); } -int Bubble::countMaxWidth(int maxPossibleCounter) const { - auto numbers = Ui::NumbersAnimation(_st.font, [] {}); - numbers.setDisabledMonospace(true); - numbers.setDuration(0); - const auto textsZero = _textFactory(0); - const auto textsMax = _textFactory(maxPossibleCounter); - numbers.setText(textsZero.counter, 0); - numbers.setText(textsMax.counter, maxPossibleCounter); - numbers.finishAnimating(); - return filledWidth() - + numbers.maxWidth() - + (_additional.isEmpty() - ? 0 - : (_st.additionalSkip - + _st.additionalStyle.font->width(textsMax.additional))); +int Bubble::bottomTextWidth() const { + return _subtext.isEmpty() + ? 0 + : (_st.subtextPadding.left() + + _subtext.maxWidth() + + _st.subtextPadding.right()); +} + +int Bubble::width() const { + return std::max(topTextWidth(), bottomTextWidth()); } int Bubble::countTargetWidth(int targetCounter) const { @@ -125,12 +119,13 @@ int Bubble::countTargetWidth(int targetCounter) const { const auto texts = _textFactory(targetCounter); numbers.setText(texts.counter, targetCounter); numbers.finishAnimating(); - return filledWidth() + const auto top = filledWidth() + numbers.maxWidth() + (_additional.isEmpty() ? 0 : (_st.additionalSkip + _st.additionalStyle.font->width(texts.additional))); + return std::max(top, bottomTextWidth()); } void Bubble::setCounter(int value) { @@ -210,6 +205,18 @@ QPainterPath Bubble::bubblePath(const QRect &r) const { return result; } +void Bubble::setSubtext(QString subtext) { + if (_subtext.toString() == subtext) { + return; + } else if (subtext.isEmpty()) { + _subtext = {}; + } else { + _subtext.setText(_st.subtextStyle, subtext); + } + _widthChanges.fire({}); + _updateCallback(); +} + void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) { if (!_counter.has_value()) { return; @@ -230,12 +237,25 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) { } p.setPen(st::activeButtonFg); p.setFont(_st.font); - const auto iconLeft = r.x() + _st.padding.left(); + const auto withSubtext = !_subtext.isEmpty(); + const auto height = withSubtext + ? (_st.font->height + + _st.subtextPadding.top() + + _st.subtextStyle.font->height) + : _st.font->height; + const auto topWidth = withSubtext ? topTextWidth() : 0; + const auto bottomWidth = withSubtext ? bottomTextWidth() : 0; + const auto iconShift = (topWidth >= bottomWidth) + ? 0 + : (bottomWidth - topWidth) / 2; + const auto iconLeft = r.x() + _st.padding.left() + iconShift; + const auto topShift = (_st.font->height - height) / 2; const auto iconTop = bubbleRect.y() + + topShift + (bubbleRect.height() - _icon->height()) / 2; _icon->paint(p, iconLeft, iconTop, bubbleRect.width()); const auto numberLeft = iconLeft + _icon->width() + _st.textSkip; - const auto numberTop = r.y() + _textTop; + const auto numberTop = r.y() + (_st.height - height) / 2; _numberAnimation.paint(p, numberLeft, numberTop, width()); if (!_additional.isEmpty()) { p.setOpacity(0.7); @@ -250,6 +270,22 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) { .availableWidth = _additional.maxWidth(), }); } + if (withSubtext) { + p.setOpacity(1.); + const auto subtextShift = (bottomWidth >= topWidth) + ? 0 + : (topWidth - bottomWidth) / 2; + const auto subtextLeft = r.x() + + _st.subtextPadding.left() + + subtextShift; + const auto subtextTop = numberTop + + _st.font->height + + _st.subtextPadding.top(); + _subtext.draw(p, { + .position = { subtextLeft, subtextTop }, + .availableWidth = _subtext.maxWidth(), + }); + } } rpl::producer<> Bubble::widthChanges() const { @@ -494,6 +530,10 @@ void BubbleWidget::setBrushOverride(std::optional brushOverride) { update(); } +void BubbleWidget::setSubtext(QString subtext) { + _bubble.setSubtext(subtext); +} + void BubbleWidget::paintEvent(QPaintEvent *e) { if (!_bubble.counter().has_value()) { return; diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.h b/Telegram/SourceFiles/ui/effects/premium_bubble.h index 756fb4f838..b0434ab8ff 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.h +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.h @@ -56,7 +56,6 @@ public: [[nodiscard]] int height() const; [[nodiscard]] int width() const; [[nodiscard]] int bubbleRadius() const; - [[nodiscard]] int countMaxWidth(int maxPossibleCounter) const; [[nodiscard]] int countTargetWidth(int targetCounter) const; [[nodiscard]] QRect bubbleGeometry(const QRect &r) const; @@ -65,11 +64,14 @@ public: void setFlipHorizontal(bool value); void paintBubble(QPainter &p, const QRect &r, const QBrush &brush); [[nodiscard]] QPainterPath bubblePath(const QRect &r) const; + void setSubtext(QString subtext); [[nodiscard]] rpl::producer<> widthChanges() const; private: [[nodiscard]] int filledWidth() const; + [[nodiscard]] int topTextWidth() const; + [[nodiscard]] int bottomTextWidth() const; const style::PremiumBubble &_st; @@ -79,8 +81,8 @@ private: const style::icon *_icon; NumbersAnimation _numberAnimation; Text::String _additional; + Text::String _subtext; const int _height; - const int _textTop; const bool _hasTail; std::optional _counter; @@ -120,6 +122,7 @@ public: const style::margins &outerPadding); void setBrushOverride(std::optional brushOverride); + void setSubtext(QString subtext); protected: void paintEvent(QPaintEvent *e) override;