info_peer_gifts_common.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "info/peer_gifts/info_peer_gifts_common.h"
  8. #include "boxes/send_credits_box.h" // SetButtonMarkedLabel
  9. #include "boxes/star_gift_box.h"
  10. #include "boxes/sticker_set_box.h"
  11. #include "chat_helpers/stickers_gift_box_pack.h"
  12. #include "chat_helpers/stickers_lottie.h"
  13. #include "core/ui_integration.h"
  14. #include "data/stickers/data_custom_emoji.h"
  15. #include "data/data_credits.h" // CreditsHistoryEntry
  16. #include "data/data_document.h"
  17. #include "data/data_document_media.h"
  18. #include "data/data_session.h"
  19. #include "history/view/media/history_view_sticker_player.h"
  20. #include "lang/lang_keys.h"
  21. #include "main/main_session.h"
  22. #include "settings/settings_credits_graphics.h"
  23. #include "ui/layers/generic_box.h"
  24. #include "ui/text/format_values.h"
  25. #include "ui/text/text_utilities.h"
  26. #include "ui/widgets/buttons.h"
  27. #include "ui/dynamic_image.h"
  28. #include "ui/dynamic_thumbnails.h"
  29. #include "ui/effects/premium_graphics.h"
  30. #include "ui/painter.h"
  31. #include "window/window_session_controller.h"
  32. #include "styles/style_credits.h"
  33. #include "styles/style_layers.h"
  34. #include "styles/style_premium.h"
  35. namespace Info::PeerGifts {
  36. namespace {
  37. constexpr auto kGiftsPerRow = 3;
  38. } // namespace
  39. std::strong_ordering operator<=>(const GiftBadge &a, const GiftBadge &b) {
  40. const auto result1 = (a.text <=> b.text);
  41. if (result1 != std::strong_ordering::equal) {
  42. return result1;
  43. }
  44. const auto result2 = (a.bg1.rgb() <=> b.bg1.rgb());
  45. if (result2 != std::strong_ordering::equal) {
  46. return result2;
  47. }
  48. const auto result3 = (a.bg2.rgb() <=> b.bg2.rgb());
  49. if (result3 != std::strong_ordering::equal) {
  50. return result3;
  51. }
  52. const auto result4 = (a.fg.rgb() <=> b.fg.rgb());
  53. if (result4 != std::strong_ordering::equal) {
  54. return result4;
  55. }
  56. return a.gradient <=> b.gradient;
  57. }
  58. GiftButton::GiftButton(
  59. QWidget *parent,
  60. not_null<GiftButtonDelegate*> delegate)
  61. : AbstractButton(parent)
  62. , _delegate(delegate) {
  63. }
  64. GiftButton::~GiftButton() {
  65. unsubscribe();
  66. }
  67. void GiftButton::unsubscribe() {
  68. if (base::take(_subscribed)) {
  69. _userpic->subscribeToUpdates(nullptr);
  70. }
  71. }
  72. void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
  73. if (_descriptor == descriptor) {
  74. return;
  75. }
  76. auto player = base::take(_player);
  77. const auto starsType = Ui::Premium::MiniStars::Type::SlowStars;
  78. _mediaLifetime.destroy();
  79. _descriptor = descriptor;
  80. unsubscribe();
  81. v::match(descriptor, [&](const GiftTypePremium &data) {
  82. const auto months = data.months;
  83. _text = Ui::Text::String(st::giftBoxGiftHeight / 4);
  84. _text.setMarkedText(
  85. st::defaultTextStyle,
  86. Ui::Text::Bold(
  87. tr::lng_months(tr::now, lt_count, months)
  88. ).append('\n').append(
  89. tr::lng_gift_premium_label(tr::now)
  90. ));
  91. _price.setText(
  92. st::semiboldTextStyle,
  93. Ui::FillAmountAndCurrency(
  94. data.cost,
  95. data.currency,
  96. true));
  97. if (const auto stars = data.stars) {
  98. const auto starsText = Lang::FormatCountDecimal(stars);
  99. _byStars.setMarkedText(
  100. st::giftBoxByStarsStyle,
  101. tr::lng_gift_premium_by_stars(
  102. tr::now,
  103. lt_amount,
  104. _delegate->ministar().append(' ' + starsText),
  105. Ui::Text::WithEntities),
  106. kMarkupTextOptions,
  107. _delegate->textContext());
  108. }
  109. _userpic = nullptr;
  110. if (!_stars) {
  111. _stars.emplace(this, true, starsType);
  112. }
  113. _stars->setColorOverride(QGradientStops{
  114. { 0., anim::with_alpha(st::windowActiveTextFg->c, .3) },
  115. { 1., st::windowActiveTextFg->c },
  116. });
  117. }, [&](const GiftTypeStars &data) {
  118. const auto unique = data.info.unique.get();
  119. const auto soldOut = data.info.limitedCount
  120. && !data.userpic
  121. && !data.info.limitedLeft;
  122. _userpic = !data.userpic
  123. ? nullptr
  124. : data.from
  125. ? Ui::MakeUserpicThumbnail(data.from)
  126. : Ui::MakeHiddenAuthorThumbnail();
  127. if (mode == Mode::Minimal) {
  128. _price = {};
  129. _stars.reset();
  130. return;
  131. }
  132. _price.setMarkedText(
  133. st::semiboldTextStyle,
  134. (unique
  135. ? tr::lng_gift_price_unique(tr::now, Ui::Text::WithEntities)
  136. : _delegate->star().append(
  137. ' ' + Lang::FormatCountDecimal(data.info.stars))),
  138. kMarkupTextOptions,
  139. _delegate->textContext());
  140. if (!_stars) {
  141. _stars.emplace(this, true, starsType);
  142. }
  143. if (unique) {
  144. const auto white = QColor(255, 255, 255);
  145. _stars->setColorOverride(QGradientStops{
  146. { 0., anim::with_alpha(white, .3) },
  147. { 1., white },
  148. });
  149. } else if (soldOut) {
  150. _stars.reset();
  151. } else {
  152. _stars->setColorOverride(
  153. Ui::Premium::CreditsIconGradientStops());
  154. }
  155. });
  156. _delegate->sticker(
  157. descriptor
  158. ) | rpl::start_with_next([=](not_null<DocumentData*> document) {
  159. setDocument(document);
  160. }, lifetime());
  161. _patterned = false;
  162. _uniqueBackgroundCache = QImage();
  163. _uniquePatternEmoji = nullptr;
  164. _uniquePatternCache.clear();
  165. if (mode != Mode::Full) {
  166. _button = QRect();
  167. _small = true;
  168. return;
  169. }
  170. const auto buttonw = _price.maxWidth();
  171. const auto buttonh = st::semiboldFont->height;
  172. const auto inner = QRect(
  173. QPoint(),
  174. QSize(buttonw, buttonh)
  175. ).marginsAdded(st::giftBoxButtonPadding);
  176. const auto skipy = _delegate->buttonSize().height()
  177. - (_byStars.isEmpty()
  178. ? st::giftBoxButtonBottom
  179. : st::giftBoxButtonBottomByStars)
  180. - inner.height();
  181. const auto skipx = (width() - inner.width()) / 2;
  182. const auto outer = (width() - 2 * skipx);
  183. _button = QRect(skipx, skipy, outer, inner.height());
  184. if (_stars) {
  185. const auto padding = _button.height() / 2;
  186. _stars->setCenter(_button - QMargins(padding, 0, padding, 0));
  187. }
  188. }
  189. bool GiftButton::documentResolved() const {
  190. return _player || _mediaLifetime;
  191. }
  192. void GiftButton::setDocument(not_null<DocumentData*> document) {
  193. const auto media = document->createMediaView();
  194. media->checkStickerLarge();
  195. media->goodThumbnailWanted();
  196. rpl::single() | rpl::then(
  197. document->owner().session().downloaderTaskFinished()
  198. ) | rpl::filter([=] {
  199. return media->loaded();
  200. }) | rpl::start_with_next([=] {
  201. _mediaLifetime.destroy();
  202. auto result = std::unique_ptr<HistoryView::StickerPlayer>();
  203. const auto sticker = document->sticker();
  204. if (sticker->isLottie()) {
  205. result = std::make_unique<HistoryView::LottiePlayer>(
  206. ChatHelpers::LottiePlayerFromDocument(
  207. media.get(),
  208. ChatHelpers::StickerLottieSize::InlineResults,
  209. st::giftBoxStickerSize,
  210. Lottie::Quality::High));
  211. } else if (sticker->isWebm()) {
  212. result = std::make_unique<HistoryView::WebmPlayer>(
  213. media->owner()->location(),
  214. media->bytes(),
  215. st::giftBoxStickerSize);
  216. } else {
  217. result = std::make_unique<HistoryView::StaticStickerPlayer>(
  218. media->owner()->location(),
  219. media->bytes(),
  220. st::giftBoxStickerSize);
  221. }
  222. result->setRepaintCallback([=] { update(); });
  223. _player = std::move(result);
  224. update();
  225. }, _mediaLifetime);
  226. }
  227. void GiftButton::setGeometry(QRect inner, QMargins extend) {
  228. _extend = extend;
  229. AbstractButton::setGeometry(inner.marginsAdded(extend));
  230. }
  231. QMargins GiftButton::currentExtend() const {
  232. const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
  233. const auto added = anim::interpolate(0, st::giftBoxSelectSkip, progress);
  234. return _extend + QMargins(added, added, added, added);
  235. }
  236. void GiftButton::toggleSelected(bool selected) {
  237. if (_selected == selected) {
  238. return;
  239. }
  240. const auto duration = st::defaultRoundCheckbox.duration;
  241. _selected = selected;
  242. _selectedAnimation.start([=] {
  243. update();
  244. }, selected ? 0. : 1., selected ? 1. : 0., duration, anim::easeOutCirc);
  245. }
  246. void GiftButton::paintBackground(QPainter &p, const QImage &background) {
  247. const auto removed = currentExtend() - _extend;
  248. const auto x = removed.left();
  249. const auto y = removed.top();
  250. const auto width = this->width() - x - removed.right();
  251. const auto height = this->height() - y - removed.bottom();
  252. const auto dpr = int(background.devicePixelRatio());
  253. const auto bwidth = background.width() / dpr;
  254. const auto bheight = background.height() / dpr;
  255. const auto fillRow = [&](int yfrom, int ytill, int bfrom) {
  256. const auto fill = [&](int xto, int wto, int xfrom, int wfrom = 0) {
  257. const auto fheight = ytill - yfrom;
  258. p.drawImage(
  259. QRect(x + xto, y + yfrom, wto, fheight),
  260. background,
  261. QRect(
  262. QPoint(xfrom, bfrom) * dpr,
  263. QSize((wfrom ? wfrom : wto), fheight) * dpr));
  264. };
  265. if (width < bwidth) {
  266. const auto xhalf = width / 2;
  267. fill(0, xhalf, 0);
  268. fill(xhalf, width - xhalf, bwidth - (width - xhalf));
  269. } else if (width == bwidth) {
  270. fill(0, width, 0);
  271. } else {
  272. const auto half = bwidth / (2 * dpr);
  273. fill(0, half, 0);
  274. fill(width - half, half, bwidth - half);
  275. fill(half, width - 2 * half, half, 1);
  276. }
  277. };
  278. if (height < bheight) {
  279. fillRow(0, height / 2, 0);
  280. fillRow(height / 2, height, bheight - (height - (height / 2)));
  281. } else {
  282. fillRow(0, height, 0);
  283. }
  284. auto hq = PainterHighQualityEnabler(p);
  285. const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
  286. const auto pwidth = progress * st::defaultRoundCheckbox.width;
  287. p.setPen(QPen(st::defaultRoundCheckbox.bgActive->c, pwidth));
  288. p.setBrush(Qt::NoBrush);
  289. const auto rounded = rect().marginsRemoved(_extend);
  290. const auto phalf = pwidth / 2.;
  291. const auto extended = QRectF(rounded).marginsRemoved(
  292. { phalf, phalf, phalf, phalf });
  293. const auto xradius = removed.left() + st::giftBoxGiftRadius - phalf;
  294. const auto yradius = removed.top() + st::giftBoxGiftRadius - phalf;
  295. p.drawRoundedRect(extended, xradius, yradius);
  296. }
  297. void GiftButton::resizeEvent(QResizeEvent *e) {
  298. if (!_button.isEmpty()) {
  299. _button.moveLeft((width() - _button.width()) / 2);
  300. if (_stars) {
  301. const auto padding = _button.height() / 2;
  302. _stars->setCenter(_button - QMargins(padding, 0, padding, 0));
  303. }
  304. }
  305. }
  306. void GiftButton::contextMenuEvent(QContextMenuEvent *e) {
  307. _contextMenuRequests.fire_copy((e->reason() == QContextMenuEvent::Mouse)
  308. ? e->globalPos()
  309. : QCursor::pos());
  310. }
  311. void GiftButton::cacheUniqueBackground(
  312. not_null<Data::UniqueGift*> unique,
  313. int width,
  314. int height) {
  315. if (!_uniquePatternEmoji) {
  316. _uniquePatternEmoji = _delegate->buttonPatternEmoji(unique, [=] {
  317. update();
  318. });
  319. [[maybe_unused]] const auto preload = _uniquePatternEmoji->ready();
  320. }
  321. const auto outer = QRect(0, 0, width, height);
  322. const auto extend = currentExtend();
  323. const auto inner = outer.marginsRemoved(
  324. extend
  325. ).translated(-extend.left(), -extend.top());
  326. const auto ratio = style::DevicePixelRatio();
  327. if (_uniqueBackgroundCache.size() != inner.size() * ratio) {
  328. _uniqueBackgroundCache = QImage(
  329. inner.size() * ratio,
  330. QImage::Format_ARGB32_Premultiplied);
  331. _uniqueBackgroundCache.fill(Qt::transparent);
  332. _uniqueBackgroundCache.setDevicePixelRatio(ratio);
  333. const auto radius = st::giftBoxGiftRadius;
  334. auto p = QPainter(&_uniqueBackgroundCache);
  335. auto hq = PainterHighQualityEnabler(p);
  336. auto gradient = QRadialGradient(inner.center(), inner.width() / 2);
  337. gradient.setStops({
  338. { 0., unique->backdrop.centerColor },
  339. { 1., unique->backdrop.edgeColor },
  340. });
  341. p.setBrush(gradient);
  342. p.setPen(Qt::NoPen);
  343. p.drawRoundedRect(inner, radius, radius);
  344. _patterned = false;
  345. }
  346. if (!_patterned && _uniquePatternEmoji->ready()) {
  347. _patterned = true;
  348. auto p = QPainter(&_uniqueBackgroundCache);
  349. p.setClipRect(inner);
  350. const auto skip = inner.width() / 3;
  351. Ui::PaintPoints(
  352. p,
  353. Ui::PatternPointsSmall(),
  354. _uniquePatternCache,
  355. _uniquePatternEmoji.get(),
  356. *unique,
  357. QRect(-skip, 0, inner.width() + 2 * skip, inner.height()));
  358. }
  359. }
  360. void GiftButton::paintEvent(QPaintEvent *e) {
  361. auto p = QPainter(this);
  362. const auto unique = v::is<GiftTypeStars>(_descriptor)
  363. ? v::get<GiftTypeStars>(_descriptor).info.unique.get()
  364. : nullptr;
  365. const auto hidden = v::is<GiftTypeStars>(_descriptor)
  366. && v::get<GiftTypeStars>(_descriptor).hidden;;
  367. const auto extend = currentExtend();
  368. const auto position = QPoint(extend.left(), extend.top());
  369. const auto background = _delegate->background();
  370. const auto width = this->width();
  371. const auto dpr = int(background.devicePixelRatio());
  372. paintBackground(p, background);
  373. if (unique) {
  374. cacheUniqueBackground(unique, width, background.height() / dpr);
  375. p.drawImage(extend.left(), extend.top(), _uniqueBackgroundCache);
  376. }
  377. if (_userpic) {
  378. if (!_subscribed) {
  379. _subscribed = true;
  380. _userpic->subscribeToUpdates([=] { update(); });
  381. }
  382. const auto image = _userpic->image(st::giftBoxUserpicSize);
  383. const auto skip = st::giftBoxUserpicSkip;
  384. p.drawImage(extend.left() + skip, extend.top() + skip, image);
  385. }
  386. auto frame = QImage();
  387. if (_player && _player->ready()) {
  388. const auto paused = !isOver();
  389. auto info = _player->frame(
  390. st::giftBoxStickerSize,
  391. QColor(0, 0, 0, 0),
  392. false,
  393. crl::now(),
  394. paused);
  395. frame = info.image;
  396. const auto finished = (info.index + 1 == _player->framesCount());
  397. if (!finished || !paused) {
  398. _player->markFrameShown();
  399. }
  400. const auto size = frame.size() / style::DevicePixelRatio();
  401. p.drawImage(
  402. QRect(
  403. (width - size.width()) / 2,
  404. (_small
  405. ? st::giftBoxSmallStickerTop
  406. : _text.isEmpty()
  407. ? st::giftBoxStickerStarTop
  408. : _byStars.isEmpty()
  409. ? st::giftBoxStickerTop
  410. : st::giftBoxStickerTopByStars),
  411. size.width(),
  412. size.height()),
  413. frame);
  414. }
  415. if (hidden) {
  416. const auto topleft = QPoint(
  417. (width - st::giftBoxStickerSize.width()) / 2,
  418. (_small
  419. ? st::giftBoxSmallStickerTop
  420. : _text.isEmpty()
  421. ? st::giftBoxStickerStarTop
  422. : _byStars.isEmpty()
  423. ? st::giftBoxStickerTop
  424. : st::giftBoxStickerTopByStars));
  425. _delegate->hiddenMark()->paint(
  426. p,
  427. frame,
  428. _hiddenBgCache,
  429. topleft,
  430. st::giftBoxStickerSize,
  431. width);
  432. }
  433. auto hq = PainterHighQualityEnabler(p);
  434. const auto premium = v::is<GiftTypePremium>(_descriptor);
  435. const auto singlew = width - extend.left() - extend.right();
  436. const auto font = st::semiboldFont;
  437. p.setFont(font);
  438. const auto badge = v::match(_descriptor, [&](GiftTypePremium data) {
  439. if (data.discountPercent > 0) {
  440. p.setBrush(st::attentionButtonFg);
  441. const auto kMinus = QChar(0x2212);
  442. return GiftBadge{
  443. .text = kMinus + QString::number(data.discountPercent) + '%',
  444. .bg1 = st::premiumButtonBg3->c,
  445. .bg2 = st::premiumButtonBg2->c,
  446. .fg = st::windowBg->c,
  447. .gradient = true,
  448. .small = true,
  449. };
  450. }
  451. return GiftBadge();
  452. }, [&](const GiftTypeStars &data) {
  453. const auto count = data.info.limitedCount;
  454. const auto pinned = data.pinned || data.pinnedSelection;
  455. if (count || pinned) {
  456. const auto soldOut = !pinned
  457. && !data.userpic
  458. && !data.info.limitedLeft;
  459. return GiftBadge{
  460. .text = (soldOut
  461. ? tr::lng_gift_stars_sold_out(tr::now)
  462. : (unique && pinned)
  463. ? ('#' + QString::number(unique->number))
  464. : (!data.userpic && !data.info.unique)
  465. ? tr::lng_gift_stars_limited(tr::now)
  466. : (count == 1)
  467. ? tr::lng_gift_limited_of_one(tr::now)
  468. : tr::lng_gift_limited_of_count(
  469. tr::now,
  470. lt_amount,
  471. (((count % 1000) && (count < 10'000))
  472. ? Lang::FormatCountDecimal(count)
  473. : Lang::FormatCountToShort(count).string))),
  474. .bg1 = (unique
  475. ? unique->backdrop.edgeColor
  476. : soldOut
  477. ? st::attentionButtonFg->c
  478. : st::windowActiveTextFg->c),
  479. .bg2 = (unique
  480. ? unique->backdrop.patternColor
  481. : QColor(0, 0, 0, 0)),
  482. .fg = unique ? QColor(255, 255, 255) : st::windowBg->c,
  483. .small = true,
  484. };
  485. }
  486. return GiftBadge();
  487. });
  488. if (badge) {
  489. const auto rubberOut = st::lineWidth;
  490. const auto inner = rect().marginsRemoved(extend);
  491. p.setClipRect(inner.marginsAdded(
  492. { rubberOut, rubberOut, rubberOut, rubberOut }));
  493. const auto cached = _delegate->cachedBadge(badge);
  494. const auto width = cached.width() / cached.devicePixelRatio();
  495. p.drawImage(
  496. position.x() + singlew + rubberOut - width,
  497. position.y() - rubberOut,
  498. cached);
  499. }
  500. v::match(_descriptor, [](const GiftTypePremium &) {
  501. }, [&](const GiftTypeStars &data) {
  502. if (unique && data.pinned) {
  503. auto hq = PainterHighQualityEnabler(p);
  504. const auto &icon = st::giftBoxPinIcon;
  505. const auto skip = st::giftBoxUserpicSkip;
  506. const auto add = (st::giftBoxUserpicSize - icon.width()) / 2;
  507. p.setPen(Qt::NoPen);
  508. p.setBrush(unique->backdrop.patternColor);
  509. const auto rect = QRect(
  510. QPoint(extend.left() + skip, extend.top() + skip),
  511. QSize(icon.width() + 2 * add, icon.height() + 2 * add));
  512. p.drawEllipse(rect);
  513. icon.paintInCenter(p, rect);
  514. }
  515. });
  516. if (!_button.isEmpty()) {
  517. p.setBrush(unique
  518. ? QBrush(QColor(255, 255, 255, .2 * 255))
  519. : premium
  520. ? st::lightButtonBgOver
  521. : st::creditsBg3);
  522. p.setPen(Qt::NoPen);
  523. if (!unique && !premium) {
  524. p.setOpacity(0.12);
  525. }
  526. const auto geometry = _button;
  527. const auto radius = geometry.height() / 2.;
  528. p.drawRoundedRect(geometry, radius, radius);
  529. if (!premium) {
  530. p.setOpacity(1.);
  531. }
  532. if (_stars) {
  533. if (unique) {
  534. _stars->paint(p);
  535. } else {
  536. auto clipPath = QPainterPath();
  537. clipPath.addRoundedRect(geometry, radius, radius);
  538. p.setClipPath(clipPath);
  539. _stars->paint(p);
  540. p.setClipping(false);
  541. }
  542. }
  543. }
  544. if (!_text.isEmpty()) {
  545. p.setPen(st::windowFg);
  546. _text.draw(p, {
  547. .position = (position + QPoint(0, _byStars.isEmpty()
  548. ? st::giftBoxPremiumTextTop
  549. : st::giftBoxPremiumTextTopByStars)),
  550. .availableWidth = singlew,
  551. .align = style::al_top,
  552. });
  553. }
  554. if (!_button.isEmpty()) {
  555. const auto padding = st::giftBoxButtonPadding;
  556. p.setPen(unique
  557. ? QPen(QColor(255, 255, 255))
  558. : premium
  559. ? st::windowActiveTextFg
  560. : st::creditsFg);
  561. _price.draw(p, {
  562. .position = (_button.topLeft()
  563. + QPoint(padding.left(), padding.top())),
  564. .availableWidth = _price.maxWidth(),
  565. });
  566. if (!_byStars.isEmpty()) {
  567. p.setPen(st::creditsFg);
  568. _byStars.draw(p, {
  569. .position = QPoint(
  570. position.x(),
  571. _button.y() + _button.height() + st::giftBoxByStarsSkip),
  572. .availableWidth = singlew,
  573. .align = style::al_top,
  574. });
  575. }
  576. }
  577. }
  578. Delegate::Delegate(not_null<Main::Session*> session, GiftButtonMode mode)
  579. : _session(session)
  580. , _hiddenMark(std::make_unique<StickerPremiumMark>(
  581. _session,
  582. st::giftBoxHiddenMark,
  583. RectPart::Center))
  584. , _mode(mode) {
  585. }
  586. Delegate::Delegate(Delegate &&other) = default;
  587. Delegate::~Delegate() = default;
  588. TextWithEntities Delegate::star() {
  589. return _session->data().customEmojiManager().creditsEmoji();
  590. }
  591. TextWithEntities Delegate::ministar() {
  592. const auto owner = &_session->data();
  593. const auto top = st::giftBoxByStarsStarTop;
  594. return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 });
  595. }
  596. Ui::Text::MarkedContext Delegate::textContext() {
  597. return Core::TextContext({ .session = _session });
  598. }
  599. QSize Delegate::buttonSize() {
  600. if (!_single.isEmpty()) {
  601. return _single;
  602. }
  603. const auto width = st::boxWideWidth;
  604. const auto padding = st::giftBoxPadding;
  605. const auto available = width - padding.left() - padding.right();
  606. const auto singlew = (available - 2 * st::giftBoxGiftSkip.x())
  607. / kGiftsPerRow;
  608. const auto minimal = (_mode == GiftButtonMode::Minimal);
  609. _single = QSize(
  610. singlew,
  611. minimal ? st::giftBoxGiftSmall : st::giftBoxGiftHeight);
  612. return _single;
  613. }
  614. QMargins Delegate::buttonExtend() {
  615. return st::defaultDropdownMenu.wrap.shadow.extend;
  616. }
  617. auto Delegate::buttonPatternEmoji(
  618. not_null<Data::UniqueGift*> unique,
  619. Fn<void()> repaint)
  620. -> std::unique_ptr<Ui::Text::CustomEmoji> {
  621. return _session->data().customEmojiManager().create(
  622. unique->pattern.document,
  623. repaint,
  624. Data::CustomEmojiSizeTag::Large);
  625. }
  626. QImage Delegate::background() {
  627. if (!_bg.isNull()) {
  628. return _bg;
  629. }
  630. const auto single = buttonSize();
  631. const auto extend = buttonExtend();
  632. const auto bgSize = single.grownBy(extend);
  633. const auto ratio = style::DevicePixelRatio();
  634. auto bg = QImage(
  635. bgSize * ratio,
  636. QImage::Format_ARGB32_Premultiplied);
  637. bg.setDevicePixelRatio(ratio);
  638. bg.fill(Qt::transparent);
  639. const auto radius = st::giftBoxGiftRadius;
  640. const auto rect = QRect(QPoint(), bgSize).marginsRemoved(extend);
  641. {
  642. auto p = QPainter(&bg);
  643. auto hq = PainterHighQualityEnabler(p);
  644. p.setOpacity(0.3);
  645. p.setPen(Qt::NoPen);
  646. p.setBrush(st::windowShadowFg);
  647. p.drawRoundedRect(
  648. QRectF(rect).translated(0, radius / 12.),
  649. radius,
  650. radius);
  651. }
  652. bg = bg.scaled(
  653. (bgSize * ratio) / 2,
  654. Qt::IgnoreAspectRatio,
  655. Qt::SmoothTransformation);
  656. bg = Images::Blur(std::move(bg), true);
  657. bg = bg.scaled(
  658. bgSize * ratio,
  659. Qt::IgnoreAspectRatio,
  660. Qt::SmoothTransformation);
  661. {
  662. auto p = QPainter(&bg);
  663. auto hq = PainterHighQualityEnabler(p);
  664. p.setPen(Qt::NoPen);
  665. p.setBrush(st::windowBg);
  666. p.drawRoundedRect(rect, radius, radius);
  667. }
  668. _bg = std::move(bg);
  669. return _bg;
  670. }
  671. rpl::producer<not_null<DocumentData*>> Delegate::sticker(
  672. const GiftDescriptor &descriptor) {
  673. return GiftStickerValue(_session, descriptor);
  674. }
  675. not_null<StickerPremiumMark*> Delegate::hiddenMark() {
  676. return _hiddenMark.get();
  677. }
  678. QImage Delegate::cachedBadge(const GiftBadge &badge) {
  679. auto &image = _badges[badge];
  680. if (image.isNull()) {
  681. const auto &extend = buttonExtend();
  682. image = ValidateRotatedBadge(badge, extend.top());
  683. }
  684. return image;
  685. }
  686. DocumentData *LookupGiftSticker(
  687. not_null<Main::Session*> session,
  688. const GiftDescriptor &descriptor) {
  689. return v::match(descriptor, [&](GiftTypePremium data) {
  690. auto &packs = session->giftBoxStickersPacks();
  691. packs.load();
  692. return packs.lookup(data.months);
  693. }, [&](GiftTypeStars data) {
  694. return data.info.document.get();
  695. });
  696. }
  697. rpl::producer<not_null<DocumentData*>> GiftStickerValue(
  698. not_null<Main::Session*> session,
  699. const GiftDescriptor &descriptor) {
  700. return v::match(descriptor, [&](GiftTypePremium data) {
  701. const auto months = data.months;
  702. auto &packs = session->giftBoxStickersPacks();
  703. packs.load();
  704. if (const auto result = packs.lookup(months)) {
  705. return result->sticker()
  706. ? (rpl::single(not_null(result)) | rpl::type_erased())
  707. : rpl::never<not_null<DocumentData*>>();
  708. }
  709. return packs.updated(
  710. ) | rpl::map([=] {
  711. return session->giftBoxStickersPacks().lookup(data.months);
  712. }) | rpl::filter([](DocumentData *document) {
  713. return document && document->sticker();
  714. }) | rpl::take(1) | rpl::map([=](DocumentData *document) {
  715. return not_null(document);
  716. }) | rpl::type_erased();
  717. }, [&](GiftTypeStars data) {
  718. return rpl::single(data.info.document) | rpl::type_erased();
  719. });
  720. }
  721. QImage ValidateRotatedBadge(const GiftBadge &badge, int added) {
  722. const auto &font = badge.small
  723. ? st::giftBoxGiftBadgeFont
  724. : st::semiboldFont;
  725. const auto twidth = font->width(badge.text) + 2 * added;
  726. const auto skip = int(std::ceil(twidth / M_SQRT2));
  727. const auto ratio = style::DevicePixelRatio();
  728. const auto multiplier = ratio * 3;
  729. const auto size = (twidth + font->height * 2);
  730. const auto textpos = QPoint(size - skip, added);
  731. auto image = QImage(
  732. QSize(size, size) * multiplier,
  733. QImage::Format_ARGB32_Premultiplied);
  734. image.fill(Qt::transparent);
  735. image.setDevicePixelRatio(multiplier);
  736. {
  737. auto p = QPainter(&image);
  738. auto hq = PainterHighQualityEnabler(p);
  739. p.translate(textpos);
  740. p.rotate(45.);
  741. p.setFont(font);
  742. p.setPen(badge.fg);
  743. p.drawText(QPoint(added, font->ascent), badge.text);
  744. }
  745. auto scaled = image.scaled(
  746. QSize(size, size) * ratio,
  747. Qt::IgnoreAspectRatio,
  748. Qt::SmoothTransformation);
  749. scaled.setDevicePixelRatio(ratio);
  750. auto result = QImage(
  751. QSize(size, size) * ratio,
  752. QImage::Format_ARGB32_Premultiplied);
  753. result.setDevicePixelRatio(ratio);
  754. result.fill(Qt::transparent);
  755. {
  756. auto p = QPainter(&result);
  757. auto hq = PainterHighQualityEnabler(p);
  758. p.setPen(Qt::NoPen);
  759. p.save();
  760. p.translate(textpos);
  761. p.rotate(45.);
  762. const auto rect = QRect(-5 * twidth, 0, twidth * 12, font->height);
  763. if (badge.gradient) {
  764. const auto skip = font->height / M_SQRT2;
  765. auto gradient = QLinearGradient(
  766. QPointF(-twidth - skip, 0),
  767. QPointF(twidth + skip, 0));
  768. gradient.setStops({
  769. { 0., badge.bg1 },
  770. { 1., badge.bg2 },
  771. });
  772. p.setBrush(gradient);
  773. p.drawRect(rect);
  774. } else {
  775. p.setBrush(badge.bg1);
  776. p.drawRect(rect);
  777. if (badge.bg2.alpha() > 0) {
  778. p.setOpacity(0.5);
  779. p.setBrush(badge.bg2);
  780. p.drawRect(rect);
  781. p.setOpacity(1.);
  782. }
  783. }
  784. p.restore();
  785. p.drawImage(0, 0, scaled);
  786. }
  787. return result;
  788. }
  789. void SelectGiftToUnpin(
  790. std::shared_ptr<ChatHelpers::Show> show,
  791. const std::vector<Data::CreditsHistoryEntry> &pinned,
  792. Fn<void(Data::SavedStarGiftId)> chosen) {
  793. show->show(Box([=](not_null<Ui::GenericBox*> box) {
  794. struct State {
  795. explicit State(not_null<Main::Session*> session)
  796. : delegate(session, GiftButtonMode::Minimal) {
  797. }
  798. Delegate delegate;
  799. rpl::variable<int> selected = -1;
  800. std::vector<not_null<GiftButton*>> buttons;
  801. };
  802. const auto session = &show->session();
  803. const auto state = box->lifetime().make_state<State>(session);
  804. box->setStyle(st::giftTooManyPinnedBox);
  805. box->setWidth(st::boxWideWidth);
  806. box->addRow(
  807. object_ptr<Ui::FlatLabel>(
  808. box,
  809. tr::lng_gift_many_pinned_title(),
  810. st::giftBoxSubtitle),
  811. st::giftBoxSubtitleMargin);
  812. box->addRow(
  813. object_ptr<Ui::FlatLabel>(
  814. box,
  815. tr::lng_gift_many_pinned_choose(),
  816. st::giftTooManyPinnedChoose),
  817. st::giftBoxAboutMargin);
  818. const auto gifts = box->addRow(
  819. object_ptr<Ui::RpWidget>(box),
  820. QMargins(
  821. st::giftBoxPadding.left(),
  822. st::giftTooManyPinnedBox.buttonPadding.top(),
  823. st::giftBoxPadding.right(),
  824. 0));
  825. for (const auto &entry : pinned) {
  826. const auto index = int(state->buttons.size());
  827. state->buttons.push_back(
  828. Ui::CreateChild<GiftButton>(gifts, &state->delegate));
  829. const auto button = state->buttons.back();
  830. button->setDescriptor(GiftTypeStars{
  831. .info = {
  832. .id = entry.stargiftId,
  833. .unique = entry.uniqueGift,
  834. .document = entry.uniqueGift->model.document,
  835. },
  836. .pinnedSelection = true,
  837. }, GiftButton::Mode::Minimal);
  838. button->setClickedCallback([=] {
  839. const auto now = state->selected.current();
  840. state->selected = (now == index) ? -1 : index;
  841. });
  842. }
  843. state->selected.value(
  844. ) | rpl::combine_previous(
  845. ) | rpl::start_with_next([=](int old, int now) {
  846. if (old >= 0) state->buttons[old]->toggleSelected(false);
  847. if (now >= 0) state->buttons[now]->toggleSelected(true);
  848. }, gifts->lifetime());
  849. gifts->widthValue() | rpl::start_with_next([=](int width) {
  850. const auto singleMin = state->delegate.buttonSize();
  851. if (width < singleMin.width()) {
  852. return;
  853. }
  854. const auto count = int(state->buttons.size());
  855. const auto skipw = st::giftBoxGiftSkip.x();
  856. const auto skiph = st::giftBoxGiftSkip.y();
  857. const auto perRow = std::min(
  858. (width + skipw) / (singleMin.width() + skipw),
  859. std::max(count, 1));
  860. if (perRow <= 0) {
  861. return;
  862. }
  863. const auto single = (width - (perRow - 1) * skipw) / perRow;
  864. const auto height = singleMin.height();
  865. const auto rows = (count + perRow - 1) / perRow;
  866. for (auto row = 0; row != rows; ++row) {
  867. const auto y = row * (height + skiph);
  868. for (auto column = 0; column != perRow; ++column) {
  869. const auto index = row * perRow + column;
  870. if (index >= count) {
  871. break;
  872. }
  873. const auto &button = state->buttons[index];
  874. const auto x = column * (single + skipw);
  875. button->setGeometry(
  876. QRect(x, y, single, height),
  877. state->delegate.buttonExtend());
  878. }
  879. }
  880. gifts->resize(width, rows * (height + skiph) - skiph);
  881. }, gifts->lifetime());
  882. const auto button = box->addButton(rpl::single(QString()), [=] {
  883. const auto index = state->selected.current();
  884. if (index < 0) {
  885. return;
  886. }
  887. Assert(index < int(pinned.size()));
  888. const auto &entry = pinned[index];
  889. const auto weak = Ui::MakeWeak(box);
  890. chosen(::Settings::EntryToSavedStarGiftId(session, entry));
  891. if (const auto strong = weak.data()) {
  892. strong->closeBox();
  893. }
  894. });
  895. const auto label = Ui::SetButtonMarkedLabel(
  896. button,
  897. tr::lng_context_unpin_from_top(Ui::Text::WithEntities),
  898. &show->session(),
  899. st::creditsBoxButtonLabel,
  900. &st::giftTooManyPinnedBox.button.textFg);
  901. state->selected.value() | rpl::start_with_next([=](int value) {
  902. const auto has = (value >= 0);
  903. label->setOpacity(has ? 1. : 0.5);
  904. button->setAttribute(Qt::WA_TransparentForMouseEvents, !has);
  905. }, box->lifetime());
  906. const auto buttonPadding = st::giftTooManyPinnedBox.buttonPadding;
  907. const auto buttonWidth = st::boxWideWidth
  908. - buttonPadding.left()
  909. - buttonPadding.right();
  910. button->resizeToWidth(buttonWidth);
  911. button->widthValue() | rpl::start_with_next([=](int width) {
  912. if (width != buttonWidth) {
  913. button->resizeToWidth(buttonWidth);
  914. }
  915. }, button->lifetime());
  916. }));
  917. }
  918. } // namespace Info::PeerGifts