Improve gift corner badge display.

This commit is contained in:
John Preston 2025-01-06 14:53:21 +04:00
parent 549de7fa54
commit e6060ea277
10 changed files with 295 additions and 153 deletions

View file

@ -1528,7 +1528,7 @@ void SendGiftBox(
bool sending = false; bool sending = false;
}; };
const auto state = raw->lifetime().make_state<State>(State{ const auto state = raw->lifetime().make_state<State>(State{
.delegate = Delegate(window), .delegate = Delegate(window, GiftButtonMode::Full),
}); });
const auto single = state->delegate.buttonSize(); const auto single = state->delegate.buttonSize();
const auto shadow = st::defaultDropdownMenu.wrap.shadow; const auto shadow = st::defaultDropdownMenu.wrap.shadow;
@ -1575,7 +1575,7 @@ void SendGiftBox(
for (auto i = 0; i != count; ++i) { for (auto i = 0; i != count; ++i) {
const auto button = state->buttons[i].get(); const auto button = state->buttons[i].get();
const auto &descriptor = gifts.list[order[i]]; const auto &descriptor = gifts.list[order[i]];
button->setDescriptor(descriptor); button->setDescriptor(descriptor, GiftButton::Mode::Full);
const auto last = !((i + 1) % perRow); const auto last = !((i + 1) % perRow);
if (last) { if (last) {

View file

@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h" #include "settings/settings_credits_graphics.h"
#include "settings/settings_credits_graphics.h" // GiftedCreditsBox #include "settings/settings_credits_graphics.h" // GiftedCreditsBox
#include "settings/settings_premium.h" // Settings::ShowGiftPremium #include "settings/settings_premium.h" // Settings::ShowGiftPremium
#include "ui/chat/chat_style.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -300,20 +301,35 @@ void PremiumGift::draw(
} }
} }
QString PremiumGift::cornerTagText() { QImage PremiumGift::cornerTag(const PaintContext &context) {
auto badge = Info::PeerGifts::GiftBadge();
if (_data.unique) { if (_data.unique) {
return tr::lng_gift_collectible_tag(tr::now); badge = {
.text = tr::lng_gift_collectible_tag(tr::now),
.bg = _data.unique->backdrop.patternColor,
.fg = QColor(255, 255, 255),
};
} else if (const auto count = _data.limitedCount) { } else if (const auto count = _data.limitedCount) {
return (count == 1) badge = {
? tr::lng_gift_limited_of_one(tr::now) .text = ((count == 1)
: tr::lng_gift_limited_of_count( ? tr::lng_gift_limited_of_one(tr::now)
tr::now, : tr::lng_gift_limited_of_count(
lt_amount, tr::now,
((count % 1000) lt_amount,
? Lang::FormatCountDecimal(count) (((count % 1000) && (count < 10'000))
: Lang::FormatCountToShort(count).string)); ? Lang::FormatCountDecimal(count)
: Lang::FormatCountToShort(count).string))),
.bg = context.st->msgServiceBg()->c,
.fg = context.st->msgServiceFg()->c,
};
} else {
return {};
} }
return QString(); if (_badgeCache.isNull() || _badgeKey != badge) {
_badgeKey = badge;
_badgeCache = ValidateRotatedBadge(badge, 0);
}
return _badgeCache;
} }
bool PremiumGift::hideServiceText() { bool PremiumGift::hideServiceText() {

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_service_box.h" #include "history/view/media/history_view_service_box.h"
#include "info/peer_gifts/info_peer_gifts_common.h"
namespace Data { namespace Data {
class MediaGiftBox; class MediaGiftBox;
@ -30,7 +31,7 @@ public:
TextWithEntities subtitle() override; TextWithEntities subtitle() override;
rpl::producer<QString> button() override; rpl::producer<QString> button() override;
bool buttonMinistars() override; bool buttonMinistars() override;
QString cornerTagText() override; QImage cornerTag(const PaintContext &context) override;
int buttonSkip() override; int buttonSkip() override;
void draw( void draw(
Painter &p, Painter &p,
@ -60,6 +61,8 @@ private:
const not_null<Element*> _parent; const not_null<Element*> _parent;
const not_null<Data::MediaGiftBox*> _gift; const not_null<Data::MediaGiftBox*> _gift;
const Data::GiftCode &_data; const Data::GiftCode &_data;
QImage _badgeCache;
Info::PeerGifts::GiftBadge _badgeKey;
mutable std::optional<Sticker> _sticker; mutable std::optional<Sticker> _sticker;
}; };

View file

@ -199,28 +199,9 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const {
_content->draw(p, context, content); _content->draw(p, context, content);
if (const auto tag = _content->cornerTagText(); !tag.isEmpty()) { if (const auto tag = _content->cornerTag(context); !tag.isNull()) {
const auto font = st::semiboldFont; const auto width = tag.width() / tag.devicePixelRatio();
p.setFont(font); p.drawImage(_innerSize.width() - width, 0, tag);
p.setPen(Qt::NoPen);
const auto twidth = font->width(tag);
const auto pos = QPoint(_innerSize.width() - twidth, font->height);
const auto add = 0;// style::ConvertScale(2);
p.save();
p.setClipRect(
-add,
-add,
_innerSize.width() + 2 * add,
_innerSize.height() + 2 * add);
p.translate(pos);
p.rotate(45.);
p.translate(-pos);
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg()); // ?
p.drawRect(-5 * twidth, 0, twidth * 12, font->height);
p.setPen(context.st->msgServiceFg());
p.drawText(pos - QPoint(0, font->descent), tag);
p.restore();
} }
p.translate(0, -st::msgServiceGiftBoxTopSkip); p.translate(0, -st::msgServiceGiftBoxTopSkip);

View file

@ -11,11 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui { namespace Ui {
class RippleAnimation; class RippleAnimation;
namespace Premium {
class ColoredMiniStars;
} // namespace Premium
} // namespace Ui } // namespace Ui
namespace Ui::Premium {
class ColoredMiniStars;
} // namespace Ui::Premium
namespace HistoryView { namespace HistoryView {
class ServiceBoxContent { class ServiceBoxContent {
@ -34,7 +35,7 @@ public:
[[nodiscard]] virtual bool buttonMinistars() { [[nodiscard]] virtual bool buttonMinistars() {
return false; return false;
} }
[[nodiscard]] virtual QString cornerTagText() { [[nodiscard]] virtual QImage cornerTag(const PaintContext &context) {
return {}; return {};
} }
virtual void draw( virtual void draw(

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "info/peer_gifts/info_peer_gifts_common.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_credits_graphics.h" #include "settings/settings_credits_graphics.h"
@ -490,6 +491,8 @@ Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
QImage bg; QImage bg;
base::flat_map<float64, QImage> cache; base::flat_map<float64, QImage> cache;
std::unique_ptr<Ui::Text::CustomEmoji> pattern; std::unique_ptr<Ui::Text::CustomEmoji> pattern;
QImage badgeCache;
Info::PeerGifts::GiftBadge badgeKey;
}; };
const auto state = std::make_shared<State>(); const auto state = std::make_shared<State>();
state->pattern = view->history()->owner().customEmojiManager().create( state->pattern = view->history()->owner().customEmojiManager().create(
@ -531,29 +534,28 @@ Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
Ui::PaintPoints(p, state->cache, state->pattern.get(), *gift, outer); Ui::PaintPoints(p, state->cache, state->pattern.get(), *gift, outer);
p.setClipping(false); p.setClipping(false);
p.save();
p.translate(inner.topLeft());
const auto tag = tr::lng_gift_collectible_tag(tr::now);
const auto font = st::semiboldFont;
p.setFont(font);
p.setPen(Qt::NoPen);
const auto twidth = font->width(tag);
const auto pos = QPoint(inner.width() - twidth, font->height);
const auto add = style::ConvertScale(2); const auto add = style::ConvertScale(2);
p.setClipRect( p.setClipRect(
-add, inner.x() - add,
-add, inner.y() - add,
inner.width() + 2 * add, inner.width() + 2 * add,
inner.height() + 2 * add); inner.height() + 2 * add);
p.translate(pos); auto badge = Info::PeerGifts::GiftBadge{
p.rotate(45.); .text = tr::lng_gift_collectible_tag(tr::now),
p.translate(-pos); .bg = gift->backdrop.patternColor,
p.setPen(Qt::NoPen); .fg = gift->backdrop.textColor,
p.setBrush(gift->backdrop.patternColor); };
p.drawRect(-5 * twidth, 0, twidth * 12, font->height); if (state->badgeCache.isNull() || state->badgeKey != badge) {
p.setPen(gift->backdrop.textColor); state->badgeKey = badge;
p.drawText(pos - QPoint(0, font->descent), tag); state->badgeCache = ValidateRotatedBadge(badge, add);
p.restore(); }
const auto badgeRatio = state->badgeCache.devicePixelRatio();
const auto badgeWidth = state->badgeCache.width() / badgeRatio;
p.drawImage(
inner.x() + inner.width() + add - badgeWidth,
inner.y() - add,
state->badgeCache);
p.setClipping(false);
}; };
} }

View file

@ -37,12 +37,23 @@ constexpr auto kGiftsPerRow = 3;
} // namespace } // namespace
std::strong_ordering operator<=>(const GiftBadge &a, const GiftBadge &b) {
const auto result1 = (a.text <=> b.text);
if (result1 != std::strong_ordering::equal) {
return result1;
}
const auto result2 = (a.bg.rgb() <=> b.bg.rgb());
if (result2 != std::strong_ordering::equal) {
return result2;
}
return a.fg.rgb() <=> b.fg.rgb();
}
GiftButton::GiftButton( GiftButton::GiftButton(
QWidget *parent, QWidget *parent,
not_null<GiftButtonDelegate*> delegate) not_null<GiftButtonDelegate*> delegate)
: AbstractButton(parent) : AbstractButton(parent)
, _delegate(delegate) , _delegate(delegate) {
, _stars(this, true, Ui::Premium::MiniStars::Type::SlowStars) {
} }
GiftButton::~GiftButton() { GiftButton::~GiftButton() {
@ -55,11 +66,12 @@ void GiftButton::unsubscribe() {
} }
} }
void GiftButton::setDescriptor(const GiftDescriptor &descriptor) { void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
if (_descriptor == descriptor) { if (_descriptor == descriptor) {
return; return;
} }
auto player = base::take(_player); auto player = base::take(_player);
const auto starsType = Ui::Premium::MiniStars::Type::SlowStars;
_mediaLifetime.destroy(); _mediaLifetime.destroy();
_descriptor = descriptor; _descriptor = descriptor;
unsubscribe(); unsubscribe();
@ -82,7 +94,10 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) {
data.currency, data.currency,
true)); true));
_userpic = nullptr; _userpic = nullptr;
_stars.setColorOverride(QGradientStops{ if (!_stars) {
_stars.emplace(this, true, starsType);
}
_stars->setColorOverride(QGradientStops{
{ 0., anim::with_alpha(st::windowActiveTextFg->c, .3) }, { 0., anim::with_alpha(st::windowActiveTextFg->c, .3) },
{ 1., st::windowActiveTextFg->c }, { 1., st::windowActiveTextFg->c },
}); });
@ -91,6 +106,16 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) {
const auto soldOut = data.info.limitedCount const auto soldOut = data.info.limitedCount
&& !data.userpic && !data.userpic
&& !data.info.limitedLeft; && !data.info.limitedLeft;
_userpic = !data.userpic
? nullptr
: data.from
? Ui::MakeUserpicThumbnail(data.from)
: Ui::MakeHiddenAuthorThumbnail();
if (mode == Mode::Minimal) {
_price = {};
_stars.reset();
return;
}
_price.setMarkedText( _price.setMarkedText(
st::semiboldTextStyle, st::semiboldTextStyle,
(unique (unique
@ -99,24 +124,20 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) {
' ' + QString::number(data.info.stars))), ' ' + QString::number(data.info.stars))),
kMarkupTextOptions, kMarkupTextOptions,
_delegate->textContext()); _delegate->textContext());
_userpic = !data.userpic if (!_stars) {
? nullptr _stars.emplace(this, true, starsType);
: data.from }
? Ui::MakeUserpicThumbnail(data.from)
: Ui::MakeHiddenAuthorThumbnail();
if (unique) { if (unique) {
const auto white = QColor(255, 255, 255); const auto white = QColor(255, 255, 255);
_stars.setColorOverride(QGradientStops{ _stars->setColorOverride(QGradientStops{
{ 0., anim::with_alpha(white, .3) }, { 0., anim::with_alpha(white, .3) },
{ 1., white }, { 1., white },
}); });
} else if (soldOut) { } else if (soldOut) {
_stars.setColorOverride(QGradientStops{ _stars.reset();
{ 0., Qt::transparent },
{ 1., Qt::transparent },
});
} else { } else {
_stars.setColorOverride(Ui::Premium::CreditsIconGradientStops()); _stars->setColorOverride(
Ui::Premium::CreditsIconGradientStops());
} }
}); });
_delegate->sticker( _delegate->sticker(
@ -125,6 +146,10 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) {
setDocument(document); setDocument(document);
}, lifetime()); }, lifetime());
if (mode != Mode::Full) {
_button = QRect();
return;
}
const auto buttonw = _price.maxWidth(); const auto buttonw = _price.maxWidth();
const auto buttonh = st::semiboldFont->height; const auto buttonh = st::semiboldFont->height;
const auto inner = QRect( const auto inner = QRect(
@ -137,9 +162,9 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) {
const auto skipx = (width() - inner.width()) / 2; const auto skipx = (width() - inner.width()) / 2;
const auto outer = (width() - 2 * skipx); const auto outer = (width() - 2 * skipx);
_button = QRect(skipx, skipy, outer, inner.height()); _button = QRect(skipx, skipy, outer, inner.height());
{ if (_stars) {
const auto padding = _button.height() / 2; const auto padding = _button.height() / 2;
_stars.setCenter(_button - QMargins(padding, 0, padding, 0)); _stars->setCenter(_button - QMargins(padding, 0, padding, 0));
} }
} }
@ -193,8 +218,10 @@ void GiftButton::setGeometry(QRect inner, QMargins extend) {
void GiftButton::resizeEvent(QResizeEvent *e) { void GiftButton::resizeEvent(QResizeEvent *e) {
if (!_button.isEmpty()) { if (!_button.isEmpty()) {
_button.moveLeft((width() - _button.width()) / 2); _button.moveLeft((width() - _button.width()) / 2);
const auto padding = _button.height() / 2; if (_stars) {
_stars.setCenter(_button - QMargins(padding, 0, padding, 0)); const auto padding = _button.height() / 2;
_stars->setCenter(_button - QMargins(padding, 0, padding, 0));
}
} }
} }
@ -338,76 +365,85 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto singlew = width - _extend.left() - _extend.right(); const auto singlew = width - _extend.left() - _extend.right();
const auto font = st::semiboldFont; const auto font = st::semiboldFont;
p.setFont(font); p.setFont(font);
const auto text = v::match(_descriptor, [&](GiftTypePremium data) {
const auto badge = v::match(_descriptor, [&](GiftTypePremium data) {
if (data.discountPercent > 0) { if (data.discountPercent > 0) {
p.setBrush(st::attentionButtonFg); p.setBrush(st::attentionButtonFg);
const auto kMinus = QChar(0x2212); const auto kMinus = QChar(0x2212);
return kMinus + QString::number(data.discountPercent) + '%'; return GiftBadge{
.text = kMinus + QString::number(data.discountPercent) + '%',
.bg = st::attentionButtonFg->c,
.fg = st::windowBg->c,
};
} }
return QString(); return GiftBadge();
}, [&](const GiftTypeStars &data) { }, [&](const GiftTypeStars &data) {
if (const auto count = data.info.limitedCount) { if (const auto count = data.info.limitedCount) {
const auto soldOut = !data.userpic && !data.info.limitedLeft; const auto soldOut = !data.userpic && !data.info.limitedLeft;
p.setBrush(unique return GiftBadge{
? QBrush(unique->backdrop.patternColor) .text = (soldOut
: soldOut ? tr::lng_gift_stars_sold_out(tr::now)
? st::attentionButtonFg : !data.userpic
: st::windowActiveTextFg); ? tr::lng_gift_stars_limited(tr::now)
return soldOut : (count == 1)
? tr::lng_gift_stars_sold_out(tr::now) ? tr::lng_gift_limited_of_one(tr::now)
: !data.userpic : tr::lng_gift_limited_of_count(
? tr::lng_gift_stars_limited(tr::now) tr::now,
: (count == 1) lt_amount,
? tr::lng_gift_limited_of_one(tr::now) (((count % 1000) && (count < 10'000))
: tr::lng_gift_limited_of_count( ? Lang::FormatCountDecimal(count)
tr::now, : Lang::FormatCountToShort(count).string))),
lt_amount, .bg = (unique
((count % 1000) ? unique->backdrop.patternColor
? Lang::FormatCountDecimal(count) : soldOut
: Lang::FormatCountToShort(count).string)); ? st::attentionButtonFg->c
: st::windowActiveTextFg->c),
.fg = unique ? QColor(255, 255, 255) : st::windowBg->c,
};
} }
return QString(); return GiftBadge();
}); });
if (!text.isEmpty()) {
p.setPen(Qt::NoPen); if (badge) {
const auto twidth = font->width(text);
const auto pos = position + QPoint(singlew - twidth, font->height);
p.save();
const auto rubberOut = _extend.top(); const auto rubberOut = _extend.top();
const auto inner = rect().marginsRemoved(_extend); const auto inner = rect().marginsRemoved(_extend);
p.setClipRect(inner.marginsAdded( p.setClipRect(inner.marginsAdded(
{ rubberOut, rubberOut, rubberOut, rubberOut })); { rubberOut, rubberOut, rubberOut, rubberOut }));
p.translate(pos);
p.rotate(45.); const auto cached = _delegate->cachedBadge(badge);
p.translate(-pos); const auto width = cached.width() / cached.devicePixelRatio();
p.drawRect(-5 * twidth, position.y(), twidth * 12, font->height); p.drawImage(
p.setPen(unique ? QPen(QColor(255, 255, 255)) : st::windowBg); position.x() + singlew + _extend.top() - width,
p.drawText(pos - QPoint(0, font->descent), text); position.y() - _extend.top(),
p.restore(); cached);
} }
p.setBrush(unique if (!_button.isEmpty()) {
? QBrush(QColor(255, 255, 255, .2 * 255)) p.setBrush(unique
: premium ? QBrush(QColor(255, 255, 255, .2 * 255))
? st::lightButtonBgOver : premium
: st::creditsBg3); ? st::lightButtonBgOver
p.setPen(Qt::NoPen); : st::creditsBg3);
if (!unique && !premium) { p.setPen(Qt::NoPen);
p.setOpacity(0.12); if (!unique && !premium) {
} p.setOpacity(0.12);
const auto geometry = _button; }
const auto radius = geometry.height() / 2.; const auto geometry = _button;
p.drawRoundedRect(geometry, radius, radius); const auto radius = geometry.height() / 2.;
if (!premium) { p.drawRoundedRect(geometry, radius, radius);
p.setOpacity(1.); if (!premium) {
} p.setOpacity(1.);
if (unique) { }
_stars.paint(p); if (_stars) {
} else { if (unique) {
auto clipPath = QPainterPath(); _stars->paint(p);
clipPath.addRoundedRect(geometry, radius, radius); } else {
p.setClipPath(clipPath); auto clipPath = QPainterPath();
_stars.paint(p); clipPath.addRoundedRect(geometry, radius, radius);
p.setClipping(false); p.setClipPath(clipPath);
_stars->paint(p);
p.setClipping(false);
}
}
} }
if (!_text.isEmpty()) { if (!_text.isEmpty()) {
@ -420,25 +456,30 @@ void GiftButton::paintEvent(QPaintEvent *e) {
}); });
} }
const auto padding = st::giftBoxButtonPadding; if (!_button.isEmpty()) {
p.setPen(unique const auto padding = st::giftBoxButtonPadding;
? QPen(QColor(255, 255, 255)) p.setPen(unique
: premium ? QPen(QColor(255, 255, 255))
? st::windowActiveTextFg : premium
: st::creditsFg); ? st::windowActiveTextFg
_price.draw(p, { : st::creditsFg);
.position = (geometry.topLeft() _price.draw(p, {
+ QPoint(padding.left(), padding.top())), .position = (_button.topLeft()
.availableWidth = _price.maxWidth(), + QPoint(padding.left(), padding.top())),
}); .availableWidth = _price.maxWidth(),
});
}
} }
Delegate::Delegate(not_null<Window::SessionController*> window) Delegate::Delegate(
not_null<Window::SessionController*> window,
GiftButtonMode mode)
: _window(window) : _window(window)
, _hiddenMark(std::make_unique<StickerPremiumMark>( , _hiddenMark(std::make_unique<StickerPremiumMark>(
&window->session(), &window->session(),
st::giftBoxHiddenMark, st::giftBoxHiddenMark,
RectPart::Center)) { RectPart::Center))
, _mode(mode) {
} }
Delegate::Delegate(Delegate &&other) = default; Delegate::Delegate(Delegate &&other) = default;
@ -466,7 +507,10 @@ QSize Delegate::buttonSize() {
const auto available = width - padding.left() - padding.right(); const auto available = width - padding.left() - padding.right();
const auto singlew = (available - 2 * st::giftBoxGiftSkip.x()) const auto singlew = (available - 2 * st::giftBoxGiftSkip.x())
/ kGiftsPerRow; / kGiftsPerRow;
_single = QSize(singlew, st::giftBoxGiftHeight); const auto minimal = (_mode == GiftButtonMode::Minimal);
_single = QSize(
singlew,
minimal ? st::giftBoxGiftSmall : st::giftBoxGiftHeight);
return _single; return _single;
} }
@ -542,6 +586,15 @@ not_null<StickerPremiumMark*> Delegate::hiddenMark() {
return _hiddenMark.get(); return _hiddenMark.get();
} }
QImage Delegate::cachedBadge(const GiftBadge &badge) {
auto &image = _badges[badge];
if (image.isNull()) {
const auto &extend = buttonExtend();
image = ValidateRotatedBadge(badge, extend.top());
}
return image;
}
DocumentData *LookupGiftSticker( DocumentData *LookupGiftSticker(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const GiftDescriptor &descriptor) { const GiftDescriptor &descriptor) {
@ -577,7 +630,57 @@ rpl::producer<not_null<DocumentData*>> GiftStickerValue(
}, [&](GiftTypeStars data) { }, [&](GiftTypeStars data) {
return rpl::single(data.info.document) | rpl::type_erased(); return rpl::single(data.info.document) | rpl::type_erased();
}); });
}
QImage ValidateRotatedBadge(const GiftBadge &badge, int added) {
const auto &font = st::semiboldFont;
const auto twidth = font->width(badge.text) + 2 * added;
const auto skip = int(std::ceil(twidth / M_SQRT2));
const auto ratio = style::DevicePixelRatio();
const auto multiplier = ratio * 3;
const auto size = (twidth + font->height * 2);
const auto textpos = QPoint(size - skip, added);
auto image = QImage(
QSize(size, size) * multiplier,
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
image.setDevicePixelRatio(multiplier);
{
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
p.translate(textpos);
p.rotate(45.);
p.setFont(font);
p.setPen(badge.fg);
p.drawText(QPoint(added, font->ascent), badge.text);
}
auto scaled = image.scaled(
QSize(size, size) * ratio,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
scaled.setDevicePixelRatio(ratio);
auto result = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
result.fill(Qt::transparent);
{
auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(badge.bg);
p.save();
p.translate(textpos);
p.rotate(45.);
p.drawRect(-5 * twidth, 0, twidth * 12, font->height);
p.restore();
p.drawImage(0, 0, scaled);
}
return result;
} }
} // namespace Info::PeerGifts } // namespace Info::PeerGifts

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/qt/qt_compare.h"
#include "data/data_star_gift.h" #include "data/data_star_gift.h"
#include "ui/abstract_button.h" #include "ui/abstract_button.h"
#include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_stars_colored.h"
@ -71,6 +72,29 @@ struct GiftDescriptor : std::variant<GiftTypePremium, GiftTypeStars> {
const GiftDescriptor&) = default; const GiftDescriptor&) = default;
}; };
struct GiftBadge {
QString text;
QColor bg;
QColor fg;
explicit operator bool() const {
return !text.isEmpty();
}
friend std::strong_ordering operator<=>(
const GiftBadge &a,
const GiftBadge &b);
friend inline bool operator==(
const GiftBadge &,
const GiftBadge &) = default;
};
enum class GiftButtonMode {
Full,
Minimal,
};
class GiftButtonDelegate { class GiftButtonDelegate {
public: public:
[[nodiscard]] virtual TextWithEntities star() = 0; [[nodiscard]] virtual TextWithEntities star() = 0;
@ -85,6 +109,7 @@ public:
[[nodiscard]] virtual rpl::producer<not_null<DocumentData*>> sticker( [[nodiscard]] virtual rpl::producer<not_null<DocumentData*>> sticker(
const GiftDescriptor &descriptor) = 0; const GiftDescriptor &descriptor) = 0;
[[nodiscard]] virtual not_null<StickerPremiumMark*> hiddenMark() = 0; [[nodiscard]] virtual not_null<StickerPremiumMark*> hiddenMark() = 0;
[[nodiscard]] virtual QImage cachedBadge(const GiftBadge &badge) = 0;
}; };
class GiftButton final : public Ui::AbstractButton { class GiftButton final : public Ui::AbstractButton {
@ -92,7 +117,9 @@ public:
GiftButton(QWidget *parent, not_null<GiftButtonDelegate*> delegate); GiftButton(QWidget *parent, not_null<GiftButtonDelegate*> delegate);
~GiftButton(); ~GiftButton();
void setDescriptor(const GiftDescriptor &descriptor);
using Mode = GiftButtonMode;
void setDescriptor(const GiftDescriptor &descriptor, Mode mode);
void setGeometry(QRect inner, QMargins extend); void setGeometry(QRect inner, QMargins extend);
private: private:
@ -118,7 +145,7 @@ private:
QImage _uniqueBackgroundCache; QImage _uniqueBackgroundCache;
std::unique_ptr<Ui::Text::CustomEmoji> _uniquePatternEmoji; std::unique_ptr<Ui::Text::CustomEmoji> _uniquePatternEmoji;
base::flat_map<float64, QImage> _uniquePatternCache; base::flat_map<float64, QImage> _uniquePatternCache;
Ui::Premium::ColoredMiniStars _stars; std::optional<Ui::Premium::ColoredMiniStars> _stars;
bool _subscribed = false; bool _subscribed = false;
bool _patterned = false; bool _patterned = false;
@ -132,7 +159,9 @@ private:
class Delegate final : public GiftButtonDelegate { class Delegate final : public GiftButtonDelegate {
public: public:
explicit Delegate(not_null<Window::SessionController*> window); Delegate(
not_null<Window::SessionController*> window,
GiftButtonMode mode);
Delegate(Delegate &&other); Delegate(Delegate &&other);
~Delegate(); ~Delegate();
@ -148,12 +177,15 @@ public:
rpl::producer<not_null<DocumentData*>> sticker( rpl::producer<not_null<DocumentData*>> sticker(
const GiftDescriptor &descriptor) override; const GiftDescriptor &descriptor) override;
not_null<StickerPremiumMark*> hiddenMark() override; not_null<StickerPremiumMark*> hiddenMark() override;
QImage cachedBadge(const GiftBadge &badge) override;
private: private:
const not_null<Window::SessionController*> _window; const not_null<Window::SessionController*> _window;
std::unique_ptr<StickerPremiumMark> _hiddenMark; std::unique_ptr<StickerPremiumMark> _hiddenMark;
base::flat_map<GiftBadge, QImage> _badges;
QSize _single; QSize _single;
QImage _bg; QImage _bg;
GiftButtonMode _mode = GiftButtonMode::Full;
}; };
@ -165,4 +197,6 @@ private:
not_null<Main::Session*> session, not_null<Main::Session*> session,
const GiftDescriptor &descriptor); const GiftDescriptor &descriptor);
[[nodiscard]] QImage ValidateRotatedBadge(const GiftBadge &badge, int added);
} // namespace Info::PeerGifts } // namespace Info::PeerGifts

View file

@ -116,7 +116,7 @@ InnerWidget::InnerWidget(
not_null<UserData*> user) not_null<UserData*> user)
: BoxContentDivider(parent) : BoxContentDivider(parent)
, _window(controller->parentController()) , _window(controller->parentController())
, _delegate(_window) , _delegate(_window, GiftButtonMode::Minimal)
, _controller(controller) , _controller(controller)
, _about(std::make_unique<Ui::FlatLabel>( , _about(std::make_unique<Ui::FlatLabel>(
this, this,
@ -271,6 +271,7 @@ void InnerWidget::validateButtons() {
const auto fullw = _perRow * (_single.width() + skipw) - skipw; const auto fullw = _perRow * (_single.width() + skipw) - skipw;
const auto left = padding.left() + (available - fullw) / 2; const auto left = padding.left() + (available - fullw) / 2;
const auto oneh = _single.height() + st::giftBoxGiftSkip.y(); const auto oneh = _single.height() + st::giftBoxGiftSkip.y();
const auto mode = GiftButton::Mode::Minimal;
auto x = left; auto x = left;
auto y = vskip + fromRow * oneh; auto y = vskip + fromRow * oneh;
auto views = std::vector<View>(); auto views = std::vector<View>();
@ -301,7 +302,7 @@ void InnerWidget::validateButtons() {
.entry = index, .entry = index,
}); });
} }
views.back().button->setDescriptor(descriptor); views.back().button->setDescriptor(descriptor, mode);
views.back().button->setClickedCallback(callback); views.back().button->setClickedCallback(callback);
}; };
for (auto j = fromRow; j != tillRow; ++j) { for (auto j = fromRow; j != tillRow; ++j) {

View file

@ -112,6 +112,7 @@ giftBoxTabBgActive: windowBgRipple;
giftBoxPadding: margins(20px, 4px, 20px, 24px); giftBoxPadding: margins(20px, 4px, 20px, 24px);
giftBoxGiftSkip: point(10px, 8px); giftBoxGiftSkip: point(10px, 8px);
giftBoxGiftHeight: 164px; giftBoxGiftHeight: 164px;
giftBoxGiftSmall: 124px;
giftBoxGiftRadius: 12px; giftBoxGiftRadius: 12px;
giftBoxPremiumIconSize: 64px; giftBoxPremiumIconSize: 64px;
giftBoxPremiumIconTop: 10px; giftBoxPremiumIconTop: 10px;