/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/peer_gifts/info_peer_gifts_common.h" #include "boxes/send_credits_box.h" // SetButtonMarkedLabel #include "boxes/star_gift_box.h" #include "boxes/sticker_set_box.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_lottie.h" #include "core/ui_integration.h" #include "data/stickers/data_custom_emoji.h" #include "data/data_credits.h" // CreditsHistoryEntry #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" #include "history/view/media/history_view_sticker_player.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_credits_graphics.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/dynamic_image.h" #include "ui/dynamic_thumbnails.h" #include "ui/effects/premium_graphics.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_credits.h" #include "styles/style_layers.h" #include "styles/style_premium.h" namespace Info::PeerGifts { namespace { constexpr auto kGiftsPerRow = 3; } // 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.bg1.rgb() <=> b.bg1.rgb()); if (result2 != std::strong_ordering::equal) { return result2; } const auto result3 = (a.bg2.rgb() <=> b.bg2.rgb()); if (result3 != std::strong_ordering::equal) { return result3; } const auto result4 = (a.fg.rgb() <=> b.fg.rgb()); if (result4 != std::strong_ordering::equal) { return result4; } return a.gradient <=> b.gradient; } GiftButton::GiftButton( QWidget *parent, not_null delegate) : AbstractButton(parent) , _delegate(delegate) { } GiftButton::~GiftButton() { unsubscribe(); } void GiftButton::unsubscribe() { if (base::take(_subscribed)) { _userpic->subscribeToUpdates(nullptr); } } void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) { if (_descriptor == descriptor) { return; } auto player = base::take(_player); const auto starsType = Ui::Premium::MiniStars::Type::SlowStars; _mediaLifetime.destroy(); _descriptor = descriptor; unsubscribe(); v::match(descriptor, [&](const GiftTypePremium &data) { const auto months = data.months; _text = Ui::Text::String(st::giftBoxGiftHeight / 4); _text.setMarkedText( st::defaultTextStyle, Ui::Text::Bold( tr::lng_months(tr::now, lt_count, months) ).append('\n').append( tr::lng_gift_premium_label(tr::now) )); _price.setText( st::semiboldTextStyle, Ui::FillAmountAndCurrency( data.cost, data.currency, true)); if (const auto stars = data.stars) { const auto starsText = Lang::FormatCountDecimal(stars); _byStars.setMarkedText( st::giftBoxByStarsStyle, tr::lng_gift_premium_by_stars( tr::now, lt_amount, _delegate->ministar().append(' ' + starsText), Ui::Text::WithEntities), kMarkupTextOptions, _delegate->textContext()); } _userpic = nullptr; if (!_stars) { _stars.emplace(this, true, starsType); } _stars->setColorOverride(QGradientStops{ { 0., anim::with_alpha(st::windowActiveTextFg->c, .3) }, { 1., st::windowActiveTextFg->c }, }); }, [&](const GiftTypeStars &data) { const auto unique = data.info.unique.get(); const auto soldOut = data.info.limitedCount && !data.userpic && !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( st::semiboldTextStyle, (unique ? tr::lng_gift_transfer_button( tr::now, Ui::Text::WithEntities) : _delegate->star().append( ' ' + Lang::FormatCountDecimal(data.info.stars))), kMarkupTextOptions, _delegate->textContext()); if (!_stars) { _stars.emplace(this, true, starsType); } if (unique) { const auto white = QColor(255, 255, 255); _stars->setColorOverride(QGradientStops{ { 0., anim::with_alpha(white, .3) }, { 1., white }, }); } else if (soldOut) { _stars.reset(); } else { _stars->setColorOverride( Ui::Premium::CreditsIconGradientStops()); } }); _delegate->sticker( descriptor ) | rpl::start_with_next([=](not_null document) { setDocument(document); }, lifetime()); _patterned = false; _uniqueBackgroundCache = QImage(); _uniquePatternEmoji = nullptr; _uniquePatternCache.clear(); if (mode != Mode::Full) { _button = QRect(); _small = true; return; } const auto buttonw = _price.maxWidth(); const auto buttonh = st::semiboldFont->height; const auto inner = QRect( QPoint(), QSize(buttonw, buttonh) ).marginsAdded(st::giftBoxButtonPadding); const auto skipy = _delegate->buttonSize().height() - (_byStars.isEmpty() ? st::giftBoxButtonBottom : st::giftBoxButtonBottomByStars) - inner.height(); const auto skipx = (width() - inner.width()) / 2; const auto outer = (width() - 2 * skipx); _button = QRect(skipx, skipy, outer, inner.height()); if (_stars) { const auto padding = _button.height() / 2; _stars->setCenter(_button - QMargins(padding, 0, padding, 0)); } } bool GiftButton::documentResolved() const { return _player || _mediaLifetime; } void GiftButton::setDocument(not_null document) { const auto media = document->createMediaView(); media->checkStickerLarge(); media->goodThumbnailWanted(); rpl::single() | rpl::then( document->owner().session().downloaderTaskFinished() ) | rpl::filter([=] { return media->loaded(); }) | rpl::start_with_next([=] { _mediaLifetime.destroy(); auto result = std::unique_ptr(); const auto sticker = document->sticker(); if (sticker->isLottie()) { result = std::make_unique( ChatHelpers::LottiePlayerFromDocument( media.get(), ChatHelpers::StickerLottieSize::InlineResults, st::giftBoxStickerSize, Lottie::Quality::High)); } else if (sticker->isWebm()) { result = std::make_unique( media->owner()->location(), media->bytes(), st::giftBoxStickerSize); } else { result = std::make_unique( media->owner()->location(), media->bytes(), st::giftBoxStickerSize); } result->setRepaintCallback([=] { update(); }); _player = std::move(result); update(); }, _mediaLifetime); } void GiftButton::setGeometry(QRect inner, QMargins extend) { _extend = extend; AbstractButton::setGeometry(inner.marginsAdded(extend)); } QMargins GiftButton::currentExtend() const { const auto progress = _selectedAnimation.value(_selected ? 1. : 0.); const auto added = anim::interpolate(0, st::giftBoxSelectSkip, progress); return _extend + QMargins(added, added, added, added); } void GiftButton::toggleSelected(bool selected) { if (_selected == selected) { return; } const auto duration = st::defaultRoundCheckbox.duration; _selected = selected; _selectedAnimation.start([=] { update(); }, selected ? 0. : 1., selected ? 1. : 0., duration, anim::easeOutCirc); } void GiftButton::paintBackground(QPainter &p, const QImage &background) { const auto removed = currentExtend() - _extend; const auto x = removed.left(); const auto y = removed.top(); const auto width = this->width() - x - removed.right(); const auto height = this->height() - y - removed.bottom(); const auto dpr = int(background.devicePixelRatio()); const auto bwidth = background.width() / dpr; const auto bheight = background.height() / dpr; const auto fillRow = [&](int yfrom, int ytill, int bfrom) { const auto fill = [&](int xto, int wto, int xfrom, int wfrom = 0) { const auto fheight = ytill - yfrom; p.drawImage( QRect(x + xto, y + yfrom, wto, fheight), background, QRect( QPoint(xfrom, bfrom) * dpr, QSize((wfrom ? wfrom : wto), fheight) * dpr)); }; if (width < bwidth) { const auto xhalf = width / 2; fill(0, xhalf, 0); fill(xhalf, width - xhalf, bwidth - (width - xhalf)); } else if (width == bwidth) { fill(0, width, 0); } else { const auto half = bwidth / (2 * dpr); fill(0, half, 0); fill(width - half, half, bwidth - half); fill(half, width - 2 * half, half, 1); } }; if (height < bheight) { fillRow(0, height / 2, 0); fillRow(height / 2, height, bheight - (height - (height / 2))); } else { fillRow(0, height, 0); } auto hq = PainterHighQualityEnabler(p); const auto progress = _selectedAnimation.value(_selected ? 1. : 0.); if (progress < 0.01) { return; } const auto pwidth = progress * st::defaultRoundCheckbox.width; p.setPen(QPen(st::defaultRoundCheckbox.bgActive->c, pwidth)); p.setBrush(Qt::NoBrush); const auto rounded = rect().marginsRemoved(_extend); const auto phalf = pwidth / 2.; const auto extended = QRectF(rounded).marginsRemoved( { phalf, phalf, phalf, phalf }); const auto xradius = removed.left() + st::giftBoxGiftRadius - phalf; const auto yradius = removed.top() + st::giftBoxGiftRadius - phalf; p.drawRoundedRect(extended, xradius, yradius); } void GiftButton::resizeEvent(QResizeEvent *e) { if (!_button.isEmpty()) { _button.moveLeft((width() - _button.width()) / 2); if (_stars) { const auto padding = _button.height() / 2; _stars->setCenter(_button - QMargins(padding, 0, padding, 0)); } } } void GiftButton::contextMenuEvent(QContextMenuEvent *e) { _contextMenuRequests.fire_copy((e->reason() == QContextMenuEvent::Mouse) ? e->globalPos() : QCursor::pos()); } void GiftButton::cacheUniqueBackground( not_null unique, int width, int height) { if (!_uniquePatternEmoji) { _uniquePatternEmoji = _delegate->buttonPatternEmoji(unique, [=] { update(); }); [[maybe_unused]] const auto preload = _uniquePatternEmoji->ready(); } const auto outer = QRect(0, 0, width, height); const auto extend = currentExtend(); const auto inner = outer.marginsRemoved( extend ).translated(-extend.left(), -extend.top()); const auto ratio = style::DevicePixelRatio(); if (_uniqueBackgroundCache.size() != inner.size() * ratio) { _uniqueBackgroundCache = QImage( inner.size() * ratio, QImage::Format_ARGB32_Premultiplied); _uniqueBackgroundCache.fill(Qt::transparent); _uniqueBackgroundCache.setDevicePixelRatio(ratio); const auto radius = st::giftBoxGiftRadius; auto p = QPainter(&_uniqueBackgroundCache); auto hq = PainterHighQualityEnabler(p); auto gradient = QRadialGradient(inner.center(), inner.width() / 2); gradient.setStops({ { 0., unique->backdrop.centerColor }, { 1., unique->backdrop.edgeColor }, }); p.setBrush(gradient); p.setPen(Qt::NoPen); p.drawRoundedRect(inner, radius, radius); _patterned = false; } if (!_patterned && _uniquePatternEmoji->ready()) { _patterned = true; auto p = QPainter(&_uniqueBackgroundCache); p.setClipRect(inner); const auto skip = inner.width() / 3; Ui::PaintPoints( p, Ui::PatternPointsSmall(), _uniquePatternCache, _uniquePatternEmoji.get(), *unique, QRect(-skip, 0, inner.width() + 2 * skip, inner.height())); } } void GiftButton::paintEvent(QPaintEvent *e) { auto p = QPainter(this); const auto unique = v::is(_descriptor) ? v::get(_descriptor).info.unique.get() : nullptr; const auto hidden = v::is(_descriptor) && v::get(_descriptor).hidden;; const auto extend = currentExtend(); const auto position = QPoint(extend.left(), extend.top()); const auto background = _delegate->background(); const auto width = this->width(); const auto dpr = int(background.devicePixelRatio()); paintBackground(p, background); if (unique) { cacheUniqueBackground(unique, width, background.height() / dpr); p.drawImage(extend.left(), extend.top(), _uniqueBackgroundCache); } if (_userpic) { if (!_subscribed) { _subscribed = true; _userpic->subscribeToUpdates([=] { update(); }); } const auto image = _userpic->image(st::giftBoxUserpicSize); const auto skip = st::giftBoxUserpicSkip; p.drawImage(extend.left() + skip, extend.top() + skip, image); } auto frame = QImage(); if (_player && _player->ready()) { const auto paused = !isOver(); auto info = _player->frame( st::giftBoxStickerSize, QColor(0, 0, 0, 0), false, crl::now(), paused); frame = info.image; const auto finished = (info.index + 1 == _player->framesCount()); if (!finished || !paused) { _player->markFrameShown(); } const auto size = frame.size() / style::DevicePixelRatio(); p.drawImage( QRect( (width - size.width()) / 2, (_small ? st::giftBoxSmallStickerTop : _text.isEmpty() ? st::giftBoxStickerStarTop : _byStars.isEmpty() ? st::giftBoxStickerTop : st::giftBoxStickerTopByStars), size.width(), size.height()), frame); } if (hidden) { const auto topleft = QPoint( (width - st::giftBoxStickerSize.width()) / 2, (_small ? st::giftBoxSmallStickerTop : _text.isEmpty() ? st::giftBoxStickerStarTop : _byStars.isEmpty() ? st::giftBoxStickerTop : st::giftBoxStickerTopByStars)); _delegate->hiddenMark()->paint( p, frame, _hiddenBgCache, topleft, st::giftBoxStickerSize, width); } auto hq = PainterHighQualityEnabler(p); const auto premium = v::is(_descriptor); const auto singlew = width - extend.left() - extend.right(); const auto font = st::semiboldFont; p.setFont(font); const auto badge = v::match(_descriptor, [&](GiftTypePremium data) { if (data.discountPercent > 0) { p.setBrush(st::attentionButtonFg); const auto kMinus = QChar(0x2212); return GiftBadge{ .text = kMinus + QString::number(data.discountPercent) + '%', .bg1 = st::premiumButtonBg3->c, .bg2 = st::premiumButtonBg2->c, .fg = st::windowBg->c, .gradient = true, .small = true, }; } return GiftBadge(); }, [&](const GiftTypeStars &data) { const auto count = data.info.limitedCount; const auto pinned = data.pinned || data.pinnedSelection; if (count || pinned) { const auto soldOut = !pinned && !data.userpic && !data.info.limitedLeft; return GiftBadge{ .text = (soldOut ? tr::lng_gift_stars_sold_out(tr::now) : (unique && pinned) ? ('#' + QString::number(unique->number)) : (!data.userpic && !data.info.unique) ? tr::lng_gift_stars_limited(tr::now) : (count == 1) ? tr::lng_gift_limited_of_one(tr::now) : tr::lng_gift_limited_of_count( tr::now, lt_amount, (((count % 1000) && (count < 10'000)) ? Lang::FormatCountDecimal(count) : Lang::FormatCountToShort(count).string))), .bg1 = (unique ? unique->backdrop.edgeColor : soldOut ? st::attentionButtonFg->c : st::windowActiveTextFg->c), .bg2 = (unique ? unique->backdrop.patternColor : QColor(0, 0, 0, 0)), .fg = unique ? QColor(255, 255, 255) : st::windowBg->c, .small = true, }; } return GiftBadge(); }); if (badge) { const auto rubberOut = st::lineWidth; const auto inner = rect().marginsRemoved(extend); p.setClipRect(inner.marginsAdded( { rubberOut, rubberOut, rubberOut, rubberOut })); const auto cached = _delegate->cachedBadge(badge); const auto width = cached.width() / cached.devicePixelRatio(); p.drawImage( position.x() + singlew + rubberOut - width, position.y() - rubberOut, cached); } v::match(_descriptor, [](const GiftTypePremium &) { }, [&](const GiftTypeStars &data) { if (unique && data.pinned) { auto hq = PainterHighQualityEnabler(p); const auto &icon = st::giftBoxPinIcon; const auto skip = st::giftBoxUserpicSkip; const auto add = (st::giftBoxUserpicSize - icon.width()) / 2; p.setPen(Qt::NoPen); p.setBrush(unique->backdrop.patternColor); const auto rect = QRect( QPoint(extend.left() + skip, extend.top() + skip), QSize(icon.width() + 2 * add, icon.height() + 2 * add)); p.drawEllipse(rect); icon.paintInCenter(p, rect); } }); if (!_button.isEmpty()) { p.setBrush(unique ? QBrush(QColor(255, 255, 255, .2 * 255)) : premium ? st::lightButtonBgOver : st::creditsBg3); p.setPen(Qt::NoPen); if (!unique && !premium) { p.setOpacity(0.12); } const auto geometry = _button; const auto radius = geometry.height() / 2.; p.drawRoundedRect(geometry, radius, radius); if (!premium) { p.setOpacity(1.); } if (_stars) { if (unique) { _stars->paint(p); } else { auto clipPath = QPainterPath(); clipPath.addRoundedRect(geometry, radius, radius); p.setClipPath(clipPath); _stars->paint(p); p.setClipping(false); } } } if (!_text.isEmpty()) { p.setPen(st::windowFg); _text.draw(p, { .position = (position + QPoint(0, _byStars.isEmpty() ? st::giftBoxPremiumTextTop : st::giftBoxPremiumTextTopByStars)), .availableWidth = singlew, .align = style::al_top, }); } if (!_button.isEmpty()) { const auto padding = st::giftBoxButtonPadding; p.setPen(unique ? QPen(QColor(255, 255, 255)) : premium ? st::windowActiveTextFg : st::creditsFg); _price.draw(p, { .position = (_button.topLeft() + QPoint(padding.left(), padding.top())), .availableWidth = _price.maxWidth(), }); if (!_byStars.isEmpty()) { p.setPen(st::creditsFg); _byStars.draw(p, { .position = QPoint( position.x(), _button.y() + _button.height() + st::giftBoxByStarsSkip), .availableWidth = singlew, .align = style::al_top, }); } } } Delegate::Delegate(not_null session, GiftButtonMode mode) : _session(session) , _hiddenMark(std::make_unique( _session, st::giftBoxHiddenMark, RectPart::Center)) , _mode(mode) { } Delegate::Delegate(Delegate &&other) = default; Delegate::~Delegate() = default; TextWithEntities Delegate::star() { return _session->data().customEmojiManager().creditsEmoji(); } TextWithEntities Delegate::ministar() { const auto owner = &_session->data(); const auto top = st::giftBoxByStarsStarTop; return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 }); } Ui::Text::MarkedContext Delegate::textContext() { return Core::TextContext({ .session = _session }); } QSize Delegate::buttonSize() { if (!_single.isEmpty()) { return _single; } const auto width = st::boxWideWidth; const auto padding = st::giftBoxPadding; const auto available = width - padding.left() - padding.right(); const auto singlew = (available - 2 * st::giftBoxGiftSkip.x()) / kGiftsPerRow; const auto minimal = (_mode == GiftButtonMode::Minimal); _single = QSize( singlew, minimal ? st::giftBoxGiftSmall : st::giftBoxGiftHeight); return _single; } QMargins Delegate::buttonExtend() { return st::defaultDropdownMenu.wrap.shadow.extend; } auto Delegate::buttonPatternEmoji( not_null unique, Fn repaint) -> std::unique_ptr { return _session->data().customEmojiManager().create( unique->pattern.document, repaint, Data::CustomEmojiSizeTag::Large); } QImage Delegate::background() { if (!_bg.isNull()) { return _bg; } const auto single = buttonSize(); const auto extend = buttonExtend(); const auto bgSize = single.grownBy(extend); const auto ratio = style::DevicePixelRatio(); auto bg = QImage( bgSize * ratio, QImage::Format_ARGB32_Premultiplied); bg.setDevicePixelRatio(ratio); bg.fill(Qt::transparent); const auto radius = st::giftBoxGiftRadius; const auto rect = QRect(QPoint(), bgSize).marginsRemoved(extend); { auto p = QPainter(&bg); auto hq = PainterHighQualityEnabler(p); p.setOpacity(0.3); p.setPen(Qt::NoPen); p.setBrush(st::windowShadowFg); p.drawRoundedRect( QRectF(rect).translated(0, radius / 12.), radius, radius); } bg = bg.scaled( (bgSize * ratio) / 2, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); bg = Images::Blur(std::move(bg), true); bg = bg.scaled( bgSize * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); { auto p = QPainter(&bg); auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(st::windowBg); p.drawRoundedRect(rect, radius, radius); } _bg = std::move(bg); return _bg; } rpl::producer> Delegate::sticker( const GiftDescriptor &descriptor) { return GiftStickerValue(_session, descriptor); } not_null Delegate::hiddenMark() { 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( not_null session, const GiftDescriptor &descriptor) { return v::match(descriptor, [&](GiftTypePremium data) { auto &packs = session->giftBoxStickersPacks(); packs.load(); return packs.lookup(data.months); }, [&](GiftTypeStars data) { return data.info.document.get(); }); } rpl::producer> GiftStickerValue( not_null session, const GiftDescriptor &descriptor) { return v::match(descriptor, [&](GiftTypePremium data) { const auto months = data.months; auto &packs = session->giftBoxStickersPacks(); packs.load(); if (const auto result = packs.lookup(months)) { return result->sticker() ? (rpl::single(not_null(result)) | rpl::type_erased()) : rpl::never>(); } return packs.updated( ) | rpl::map([=] { return session->giftBoxStickersPacks().lookup(data.months); }) | rpl::filter([](DocumentData *document) { return document && document->sticker(); }) | rpl::take(1) | rpl::map([=](DocumentData *document) { return not_null(document); }) | rpl::type_erased(); }, [&](GiftTypeStars data) { return rpl::single(data.info.document) | rpl::type_erased(); }); } QImage ValidateRotatedBadge(const GiftBadge &badge, int added) { const auto &font = badge.small ? st::giftBoxGiftBadgeFont : 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.save(); p.translate(textpos); p.rotate(45.); const auto rect = QRect(-5 * twidth, 0, twidth * 12, font->height); if (badge.gradient) { const auto skip = font->height / M_SQRT2; auto gradient = QLinearGradient( QPointF(-twidth - skip, 0), QPointF(twidth + skip, 0)); gradient.setStops({ { 0., badge.bg1 }, { 1., badge.bg2 }, }); p.setBrush(gradient); p.drawRect(rect); } else { p.setBrush(badge.bg1); p.drawRect(rect); if (badge.bg2.alpha() > 0) { p.setOpacity(0.5); p.setBrush(badge.bg2); p.drawRect(rect); p.setOpacity(1.); } } p.restore(); p.drawImage(0, 0, scaled); } return result; } void SelectGiftToUnpin( std::shared_ptr show, const std::vector &pinned, Fn chosen) { show->show(Box([=](not_null box) { struct State { explicit State(not_null session) : delegate(session, GiftButtonMode::Minimal) { } Delegate delegate; rpl::variable selected = -1; std::vector> buttons; }; const auto session = &show->session(); const auto state = box->lifetime().make_state(session); box->setStyle(st::giftTooManyPinnedBox); box->setWidth(st::boxWideWidth); box->addRow( object_ptr( box, tr::lng_gift_many_pinned_title(), st::giftBoxSubtitle), st::giftBoxSubtitleMargin); box->addRow( object_ptr( box, tr::lng_gift_many_pinned_choose(), st::giftTooManyPinnedChoose), st::giftBoxAboutMargin); const auto gifts = box->addRow( object_ptr(box), QMargins( st::giftBoxPadding.left(), st::giftTooManyPinnedBox.buttonPadding.top(), st::giftBoxPadding.right(), 0)); for (const auto &entry : pinned) { const auto index = int(state->buttons.size()); state->buttons.push_back( Ui::CreateChild(gifts, &state->delegate)); const auto button = state->buttons.back(); button->setDescriptor(GiftTypeStars{ .info = { .id = entry.stargiftId, .unique = entry.uniqueGift, .document = entry.uniqueGift->model.document, }, .pinnedSelection = true, }, GiftButton::Mode::Minimal); button->setClickedCallback([=] { const auto now = state->selected.current(); state->selected = (now == index) ? -1 : index; }); } state->selected.value( ) | rpl::combine_previous( ) | rpl::start_with_next([=](int old, int now) { if (old >= 0) state->buttons[old]->toggleSelected(false); if (now >= 0) state->buttons[now]->toggleSelected(true); }, gifts->lifetime()); gifts->widthValue() | rpl::start_with_next([=](int width) { const auto singleMin = state->delegate.buttonSize(); if (width < singleMin.width()) { return; } const auto count = int(state->buttons.size()); const auto skipw = st::giftBoxGiftSkip.x(); const auto skiph = st::giftBoxGiftSkip.y(); const auto perRow = std::min( (width + skipw) / (singleMin.width() + skipw), std::max(count, 1)); if (perRow <= 0) { return; } const auto single = (width - (perRow - 1) * skipw) / perRow; const auto height = singleMin.height(); const auto rows = (count + perRow - 1) / perRow; for (auto row = 0; row != rows; ++row) { const auto y = row * (height + skiph); for (auto column = 0; column != perRow; ++column) { const auto index = row * perRow + column; if (index >= count) { break; } const auto &button = state->buttons[index]; const auto x = column * (single + skipw); button->setGeometry( QRect(x, y, single, height), state->delegate.buttonExtend()); } } gifts->resize(width, rows * (height + skiph) - skiph); }, gifts->lifetime()); const auto button = box->addButton(rpl::single(QString()), [=] { const auto index = state->selected.current(); if (index < 0) { return; } Assert(index < int(pinned.size())); const auto &entry = pinned[index]; const auto weak = Ui::MakeWeak(box); chosen(::Settings::EntryToSavedStarGiftId(session, entry)); if (const auto strong = weak.data()) { strong->closeBox(); } }); const auto label = Ui::SetButtonMarkedLabel( button, tr::lng_context_unpin_from_top(Ui::Text::WithEntities), &show->session(), st::creditsBoxButtonLabel, &st::giftTooManyPinnedBox.button.textFg); state->selected.value() | rpl::start_with_next([=](int value) { const auto has = (value >= 0); label->setOpacity(has ? 1. : 0.5); button->setAttribute(Qt::WA_TransparentForMouseEvents, !has); }, box->lifetime()); const auto buttonPadding = st::giftTooManyPinnedBox.buttonPadding; const auto buttonWidth = st::boxWideWidth - buttonPadding.left() - buttonPadding.right(); button->resizeToWidth(buttonWidth); button->widthValue() | rpl::start_with_next([=](int width) { if (width != buttonWidth) { button->resizeToWidth(buttonWidth); } }, button->lifetime()); })); } } // namespace Info::PeerGifts