premium_graphics.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096
  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 "ui/effects/premium_graphics.h"
  8. #include "data/data_premium_subscription_option.h"
  9. #include "lang/lang_keys.h"
  10. #include "ui/abstract_button.h"
  11. #include "ui/effects/animations.h"
  12. #include "ui/effects/credits_graphics.h"
  13. #include "ui/effects/gradient.h"
  14. #include "ui/effects/numbers_animation.h"
  15. #include "ui/effects/premium_bubble.h"
  16. #include "ui/text/text_utilities.h"
  17. #include "ui/layers/generic_box.h"
  18. #include "ui/text/text_options.h"
  19. #include "ui/widgets/checkbox.h"
  20. #include "ui/wrap/padding_wrap.h"
  21. #include "ui/wrap/vertical_layout.h"
  22. #include "ui/painter.h"
  23. #include "ui/rect.h"
  24. #include "styles/style_boxes.h"
  25. #include "styles/style_layers.h"
  26. #include "styles/style_premium.h"
  27. #include "styles/style_settings.h"
  28. #include "styles/style_window.h"
  29. #include <QtCore/QFile>
  30. #include <QtGui/QBrush>
  31. #include <QtSvg/QSvgRenderer>
  32. namespace Ui {
  33. namespace Premium {
  34. namespace {
  35. class GradientRadioView : public Ui::RadioView {
  36. public:
  37. GradientRadioView(
  38. const style::Radio &st,
  39. bool checked,
  40. Fn<void()> updateCallback = nullptr);
  41. void setBrush(std::optional<QBrush> brush);
  42. void paint(QPainter &p, int left, int top, int outerWidth) override;
  43. private:
  44. not_null<const style::Radio*> _st;
  45. std::optional<QBrush> _brushOverride;
  46. };
  47. GradientRadioView::GradientRadioView(
  48. const style::Radio &st,
  49. bool checked,
  50. Fn<void()> updateCallback)
  51. : Ui::RadioView(st, checked, updateCallback)
  52. , _st(&st) {
  53. }
  54. void GradientRadioView::paint(QPainter &p, int left, int top, int outerW) {
  55. auto hq = PainterHighQualityEnabler(p);
  56. const auto toggled = currentAnimationValue();
  57. const auto toggledFg = _brushOverride
  58. ? (*_brushOverride)
  59. : QBrush(_st->toggledFg);
  60. {
  61. const auto skip = (_st->outerSkip / 10.) + (_st->thickness / 2);
  62. const auto rect = QRectF(left, top, _st->diameter, _st->diameter)
  63. - Margins(skip);
  64. p.setBrush(_st->bg);
  65. if (toggled < 1) {
  66. p.setPen(QPen(_st->untoggledFg, _st->thickness));
  67. p.drawEllipse(style::rtlrect(rect, outerW));
  68. }
  69. if (toggled > 0) {
  70. p.setOpacity(toggled);
  71. p.setPen(QPen(toggledFg, _st->thickness));
  72. p.drawEllipse(style::rtlrect(rect, outerW));
  73. }
  74. }
  75. if (toggled > 0) {
  76. p.setPen(Qt::NoPen);
  77. p.setBrush(toggledFg);
  78. const auto skip0 = _st->diameter / 2.;
  79. const auto skip1 = _st->skip / 10.;
  80. const auto checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
  81. const auto rect = QRectF(left, top, _st->diameter, _st->diameter)
  82. - Margins(checkSkip);
  83. p.drawEllipse(style::rtlrect(rect, outerW));
  84. }
  85. }
  86. void GradientRadioView::setBrush(std::optional<QBrush> brush) {
  87. _brushOverride = brush;
  88. }
  89. class PartialGradient final {
  90. public:
  91. PartialGradient(int from, int to, QGradientStops stops);
  92. [[nodiscard]] QLinearGradient compute(int position, int size) const;
  93. private:
  94. const int _from;
  95. const int _to;
  96. QLinearGradient _gradient;
  97. };
  98. PartialGradient::PartialGradient(int from, int to, QGradientStops stops)
  99. : _from(from)
  100. , _to(to)
  101. , _gradient(0, 0, 0, to - from) {
  102. _gradient.setStops(std::move(stops));
  103. }
  104. QLinearGradient PartialGradient::compute(int position, int size) const {
  105. const auto pointTop = position - _from;
  106. const auto pointBottom = pointTop + size;
  107. const auto ratioTop = pointTop / float64(_to - _from);
  108. const auto ratioBottom = pointBottom / float64(_to - _from);
  109. auto resultGradient = QLinearGradient(
  110. QPointF(),
  111. QPointF(0, pointBottom - pointTop));
  112. resultGradient.setColorAt(
  113. .0,
  114. anim::gradient_color_at(_gradient, ratioTop));
  115. resultGradient.setColorAt(
  116. .1,
  117. anim::gradient_color_at(_gradient, ratioBottom));
  118. return resultGradient;
  119. }
  120. class Line final : public Ui::RpWidget {
  121. public:
  122. Line(
  123. not_null<Ui::RpWidget*> parent,
  124. const style::PremiumLimits &st,
  125. int max,
  126. TextFactory textFactory,
  127. int min,
  128. float64 ratio);
  129. Line(
  130. not_null<Ui::RpWidget*> parent,
  131. const style::PremiumLimits &st,
  132. QString max,
  133. QString min,
  134. float64 ratio);
  135. Line(
  136. not_null<Ui::RpWidget*> parent,
  137. const style::PremiumLimits &st,
  138. LimitRowLabels labels,
  139. rpl::producer<LimitRowState> state);
  140. void setColorOverride(QBrush brush);
  141. protected:
  142. void paintEvent(QPaintEvent *event) override;
  143. private:
  144. void recache(const QSize &s);
  145. const style::PremiumLimits &_st;
  146. QPixmap _leftPixmap;
  147. QPixmap _rightPixmap;
  148. float64 _ratio = 0.;
  149. Ui::Animations::Simple _animation;
  150. rpl::event_stream<> _recaches;
  151. Ui::Text::String _leftLabel;
  152. Ui::Text::String _leftText;
  153. Ui::Text::String _rightLabel;
  154. Ui::Text::String _rightText;
  155. bool _dynamic = false;
  156. std::optional<QBrush> _overrideBrush;
  157. };
  158. Line::Line(
  159. not_null<Ui::RpWidget*> parent,
  160. const style::PremiumLimits &st,
  161. int max,
  162. TextFactory textFactory,
  163. int min,
  164. float64 ratio)
  165. : Line(
  166. parent,
  167. st,
  168. max ? textFactory(max) : QString(),
  169. min ? textFactory(min) : QString(),
  170. ratio) {
  171. }
  172. Line::Line(
  173. not_null<Ui::RpWidget*> parent,
  174. const style::PremiumLimits &st,
  175. QString max,
  176. QString min,
  177. float64 ratio)
  178. : Line(parent, st, LimitRowLabels{
  179. .leftLabel = tr::lng_premium_free(),
  180. .leftCount = rpl::single(min),
  181. .rightLabel = tr::lng_premium(),
  182. .rightCount = rpl::single(max),
  183. }, rpl::single(LimitRowState{ ratio })) {
  184. }
  185. Line::Line(
  186. not_null<Ui::RpWidget*> parent,
  187. const style::PremiumLimits &st,
  188. LimitRowLabels labels,
  189. rpl::producer<LimitRowState> state)
  190. : Ui::RpWidget(parent)
  191. , _st(st) {
  192. resize(width(), st::requestsAcceptButton.height);
  193. const auto set = [&](
  194. Ui::Text::String &label,
  195. rpl::producer<QString> &text) {
  196. std::move(text) | rpl::start_with_next([=, &label](QString text) {
  197. label = { st::semiboldTextStyle, text };
  198. _recaches.fire({});
  199. }, lifetime());
  200. };
  201. set(_leftLabel, labels.leftLabel);
  202. set(_leftText, labels.leftCount);
  203. set(_rightLabel, labels.rightLabel);
  204. set(_rightText, labels.rightCount);
  205. std::move(state) | rpl::start_with_next([=](LimitRowState state) {
  206. _dynamic = state.dynamic;
  207. if (width() > 0) {
  208. const auto from = state.animateFromZero
  209. ? 0.
  210. : _animation.value(_ratio);
  211. const auto duration = Bubble::SlideNoDeflectionDuration();
  212. _animation.start([=] {
  213. update();
  214. }, from, state.ratio, duration, anim::easeOutCirc);
  215. }
  216. _ratio = state.ratio;
  217. }, lifetime());
  218. rpl::combine(
  219. sizeValue(),
  220. parent->widthValue(),
  221. _recaches.events_starting_with({})
  222. ) | rpl::filter([](const QSize &size, int parentWidth, auto) {
  223. return !size.isEmpty() && parentWidth;
  224. }) | rpl::start_with_next([=](const QSize &size, auto, auto) {
  225. recache(size);
  226. update();
  227. }, lifetime());
  228. }
  229. void Line::setColorOverride(QBrush brush) {
  230. if (brush.style() == Qt::NoBrush) {
  231. _overrideBrush = std::nullopt;
  232. } else {
  233. _overrideBrush = brush;
  234. }
  235. }
  236. void Line::paintEvent(QPaintEvent *event) {
  237. Painter p(this);
  238. const auto ratio = _animation.value(_ratio);
  239. const auto left = int(base::SafeRound(ratio * width()));
  240. const auto dpr = int(_leftPixmap.devicePixelRatio());
  241. const auto height = _leftPixmap.height() / dpr;
  242. p.drawPixmap(
  243. QRect(0, 0, left, height),
  244. _leftPixmap,
  245. QRect(0, 0, left * dpr, height * dpr));
  246. p.drawPixmap(
  247. QRect(left, 0, width() - left, height),
  248. _rightPixmap,
  249. QRect(left * dpr, 0, (width() - left) * dpr, height * dpr));
  250. p.setFont(st::normalFont);
  251. const auto textPadding = st::premiumLineTextSkip;
  252. const auto textTop = (height - _leftLabel.minHeight()) / 2;
  253. const auto leftMinWidth = _leftLabel.maxWidth()
  254. + _leftText.maxWidth()
  255. + 3 * textPadding;
  256. const auto pen = [&](bool gradient) {
  257. return gradient ? st::activeButtonFg : _st.nonPremiumFg;
  258. };
  259. if (!_dynamic && left >= leftMinWidth) {
  260. p.setPen(pen(_st.gradientFromLeft));
  261. _leftLabel.drawLeft(
  262. p,
  263. textPadding,
  264. textTop,
  265. left - textPadding,
  266. left);
  267. _leftText.drawRight(
  268. p,
  269. textPadding,
  270. textTop,
  271. left - textPadding,
  272. left,
  273. style::al_right);
  274. }
  275. const auto right = width() - left;
  276. const auto rightMinWidth = 2 * _rightText.maxWidth()
  277. + 3 * textPadding;
  278. if (!_dynamic && right >= rightMinWidth) {
  279. p.setPen(pen(!_st.gradientFromLeft));
  280. _rightLabel.drawLeftElided(
  281. p,
  282. left + textPadding,
  283. textTop,
  284. (right - _rightText.countWidth(right) - textPadding * 2),
  285. right);
  286. _rightText.drawRight(
  287. p,
  288. textPadding,
  289. textTop,
  290. right - textPadding,
  291. width(),
  292. style::al_right);
  293. }
  294. }
  295. void Line::recache(const QSize &s) {
  296. const auto r = [&](int width) {
  297. return QRect(0, 0, width, s.height());
  298. };
  299. const auto pixmap = [&](int width) {
  300. auto result = QPixmap(r(width).size() * style::DevicePixelRatio());
  301. result.setDevicePixelRatio(style::DevicePixelRatio());
  302. result.fill(Qt::transparent);
  303. return result;
  304. };
  305. const auto pathRound = [&](int width) {
  306. auto result = QPainterPath();
  307. result.addRoundedRect(
  308. r(width),
  309. st::premiumLineRadius,
  310. st::premiumLineRadius);
  311. return result;
  312. };
  313. const auto width = s.width();
  314. const auto fill = [&](QPainter &p, QPainterPath path, bool gradient) {
  315. if (!gradient) {
  316. p.fillPath(path, _st.nonPremiumBg);
  317. } else if (_overrideBrush) {
  318. p.fillPath(path, *_overrideBrush);
  319. } else {
  320. p.fillPath(path, QBrush(ComputeGradient(this, 0, width)));
  321. }
  322. };
  323. const auto textPadding = st::premiumLineTextSkip;
  324. const auto textTop = (s.height() - _leftLabel.minHeight()) / 2;
  325. const auto rwidth = _rightLabel.maxWidth();
  326. const auto pen = [&](bool gradient) {
  327. return gradient ? st::activeButtonFg : _st.nonPremiumFg;
  328. };
  329. {
  330. auto leftPixmap = pixmap(width);
  331. auto p = Painter(&leftPixmap);
  332. auto hq = PainterHighQualityEnabler(p);
  333. fill(p, pathRound(width), _st.gradientFromLeft);
  334. if (_dynamic) {
  335. p.setFont(st::normalFont);
  336. p.setPen(pen(_st.gradientFromLeft));
  337. _leftLabel.drawLeft(p, textPadding, textTop, width, width);
  338. _rightLabel.drawRight(p, textPadding, textTop, rwidth, width);
  339. }
  340. _leftPixmap = std::move(leftPixmap);
  341. }
  342. {
  343. auto rightPixmap = pixmap(width);
  344. auto p = Painter(&rightPixmap);
  345. auto hq = PainterHighQualityEnabler(p);
  346. fill(p, pathRound(width), !_st.gradientFromLeft);
  347. if (_dynamic) {
  348. p.setFont(st::normalFont);
  349. p.setPen(pen(!_st.gradientFromLeft));
  350. _leftLabel.drawLeft(p, textPadding, textTop, width, width);
  351. _rightLabel.drawRight(p, textPadding, textTop, rwidth, width);
  352. }
  353. _rightPixmap = std::move(rightPixmap);
  354. }
  355. }
  356. } // namespace
  357. QString Svg() {
  358. return u":/gui/icons/settings/star.svg"_q;
  359. }
  360. QByteArray ColorizedSvg(const QGradientStops &gradientStops) {
  361. auto f = QFile(Svg());
  362. if (!f.open(QIODevice::ReadOnly)) {
  363. return QByteArray();
  364. }
  365. auto content = QString::fromUtf8(f.readAll());
  366. auto stops = [&] {
  367. auto s = QString();
  368. for (const auto &stop : gradientStops) {
  369. s += QString("<stop offset='%1' stop-color='%2'/>")
  370. .arg(QString::number(stop.first), stop.second.name());
  371. }
  372. return s;
  373. }();
  374. const auto color = QString("<linearGradient id='Gradient2' "
  375. "x1='%1' x2='%2' y1='%3' y2='%4'>%5</linearGradient>")
  376. .arg(0)
  377. .arg(1)
  378. .arg(1)
  379. .arg(0)
  380. .arg(std::move(stops));
  381. content.replace(u"gradientPlaceholder"_q, color);
  382. content.replace(u"#fff"_q, u"url(#Gradient2)"_q);
  383. f.close();
  384. return content.toUtf8();
  385. }
  386. QImage GenerateStarForLightTopBar(QRectF rect) {
  387. auto svg = QSvgRenderer(Ui::Premium::Svg());
  388. const auto size = rect.size().toSize();
  389. auto frame = QImage(
  390. size * style::DevicePixelRatio(),
  391. QImage::Format_ARGB32_Premultiplied);
  392. frame.setDevicePixelRatio(style::DevicePixelRatio());
  393. auto mask = frame;
  394. mask.fill(Qt::transparent);
  395. {
  396. auto p = QPainter(&mask);
  397. auto gradient = QLinearGradient(
  398. 0,
  399. size.height(),
  400. size.width(),
  401. 0);
  402. gradient.setStops(Ui::Premium::ButtonGradientStops());
  403. p.setPen(Qt::NoPen);
  404. p.setBrush(gradient);
  405. p.drawRect(0, 0, size.width(), size.height());
  406. }
  407. frame.fill(Qt::transparent);
  408. {
  409. auto q = QPainter(&frame);
  410. svg.render(&q, QRect(QPoint(), size));
  411. q.setCompositionMode(QPainter::CompositionMode_SourceIn);
  412. q.drawImage(0, 0, mask);
  413. }
  414. return frame;
  415. }
  416. void AddLimitRow(
  417. not_null<Ui::VerticalLayout*> parent,
  418. const style::PremiumLimits &st,
  419. QString max,
  420. QString min,
  421. float64 ratio) {
  422. parent->add(
  423. object_ptr<Line>(parent, st, max, min, ratio),
  424. st::boxRowPadding);
  425. }
  426. void AddLimitRow(
  427. not_null<Ui::VerticalLayout*> parent,
  428. const style::PremiumLimits &st,
  429. int max,
  430. std::optional<tr::phrase<lngtag_count>> phrase,
  431. int min,
  432. float64 ratio) {
  433. const auto factory = ProcessTextFactory(phrase);
  434. AddLimitRow(
  435. parent,
  436. st,
  437. max ? factory(max) : QString(),
  438. min ? factory(min) : QString(),
  439. ratio);
  440. }
  441. void AddLimitRow(
  442. not_null<Ui::VerticalLayout*> parent,
  443. const style::PremiumLimits &st,
  444. LimitRowLabels labels,
  445. rpl::producer<LimitRowState> state,
  446. const style::margins &padding) {
  447. parent->add(
  448. object_ptr<Line>(parent, st, std::move(labels), std::move(state)),
  449. padding);
  450. }
  451. void AddAccountsRow(
  452. not_null<Ui::VerticalLayout*> parent,
  453. AccountsRowArgs &&args) {
  454. const auto container = parent->add(
  455. object_ptr<Ui::FixedHeightWidget>(parent, st::premiumAccountsHeight),
  456. st::boxRowPadding);
  457. struct Account {
  458. not_null<Ui::AbstractButton*> widget;
  459. Ui::RoundImageCheckbox checkbox;
  460. Ui::Text::String name;
  461. QPixmap badge;
  462. };
  463. struct State {
  464. std::vector<Account> accounts;
  465. };
  466. const auto state = container->lifetime().make_state<State>();
  467. const auto group = args.group;
  468. const auto imageRadius = args.st.imageRadius;
  469. const auto checkSelectWidth = args.st.selectWidth;
  470. const auto nameFg = args.stNameFg;
  471. const auto cacheBadge = [=](int center) {
  472. const auto &padding = st::premiumAccountsLabelPadding;
  473. const auto size = st::premiumAccountsLabelSize
  474. + QSize(
  475. padding.left() + padding.right(),
  476. padding.top() + padding.bottom());
  477. auto badge = QPixmap(size * style::DevicePixelRatio());
  478. badge.setDevicePixelRatio(style::DevicePixelRatio());
  479. badge.fill(Qt::transparent);
  480. auto p = QPainter(&badge);
  481. auto hq = PainterHighQualityEnabler(p);
  482. p.setPen(Qt::NoPen);
  483. const auto rectOut = QRect(QPoint(), size);
  484. const auto rectIn = rectOut - padding;
  485. const auto radius = st::premiumAccountsLabelRadius;
  486. p.setBrush(st::premiumButtonFg);
  487. p.drawRoundedRect(rectOut, radius, radius);
  488. const auto left = center - rectIn.width() / 2;
  489. p.setBrush(QBrush(ComputeGradient(container, left, rectIn.width())));
  490. p.drawRoundedRect(rectIn, radius / 2, radius / 2);
  491. p.setPen(st::premiumButtonFg);
  492. p.setFont(st::semiboldFont);
  493. p.drawText(rectIn, u"+1"_q, style::al_center);
  494. return badge;
  495. };
  496. for (auto &entry : args.entries) {
  497. const auto widget = Ui::CreateChild<Ui::AbstractButton>(container);
  498. auto name = Ui::Text::String(imageRadius * 2);
  499. name.setText(args.stName, entry.name, Ui::NameTextOptions());
  500. state->accounts.push_back(Account{
  501. .widget = widget,
  502. .checkbox = Ui::RoundImageCheckbox(
  503. args.st,
  504. [=] { widget->update(); },
  505. base::take(entry.paintRoundImage)),
  506. .name = std::move(name),
  507. });
  508. const auto index = int(state->accounts.size()) - 1;
  509. state->accounts[index].checkbox.setChecked(
  510. index == group->current(),
  511. anim::type::instant);
  512. widget->paintRequest(
  513. ) | rpl::start_with_next([=] {
  514. Painter p(widget);
  515. const auto width = widget->width();
  516. const auto photoLeft = (width - (imageRadius * 2)) / 2;
  517. const auto photoTop = checkSelectWidth;
  518. const auto &account = state->accounts[index];
  519. account.checkbox.paint(p, photoLeft, photoTop, width);
  520. const auto &badgeSize = account.badge.size()
  521. / style::DevicePixelRatio();
  522. p.drawPixmap(
  523. (width - badgeSize.width()) / 2,
  524. photoTop + (imageRadius * 2) - badgeSize.height() / 2,
  525. account.badge);
  526. p.setPen(nameFg);
  527. p.setBrush(Qt::NoBrush);
  528. account.name.drawLeftElided(
  529. p,
  530. 0,
  531. photoTop + imageRadius * 2 + st::premiumAccountsNameTop,
  532. width,
  533. width,
  534. 2,
  535. style::al_top,
  536. 0,
  537. -1,
  538. 0,
  539. true);
  540. }, widget->lifetime());
  541. widget->setClickedCallback([=] {
  542. group->setValue(index);
  543. });
  544. }
  545. container->sizeValue(
  546. ) | rpl::start_with_next([=](const QSize &size) {
  547. const auto count = state->accounts.size();
  548. const auto columnWidth = size.width() / count;
  549. for (auto i = 0; i < count; i++) {
  550. auto &account = state->accounts[i];
  551. account.widget->resize(columnWidth, size.height());
  552. const auto left = columnWidth * i;
  553. account.widget->moveToLeft(left, 0);
  554. account.badge = cacheBadge(left + columnWidth / 2);
  555. const auto photoWidth = ((imageRadius + checkSelectWidth) * 2);
  556. account.checkbox.setColorOverride(QBrush(
  557. ComputeGradient(
  558. container,
  559. left + (columnWidth - photoWidth) / 2,
  560. photoWidth)));
  561. }
  562. }, container->lifetime());
  563. group->setChangedCallback([=](int value) {
  564. for (auto i = 0; i < state->accounts.size(); i++) {
  565. state->accounts[i].checkbox.setChecked(i == value);
  566. }
  567. });
  568. }
  569. QGradientStops LimitGradientStops() {
  570. return {
  571. { 0.0, st::premiumButtonBg1->c },
  572. { .25, st::premiumButtonBg1->c },
  573. { .85, st::premiumButtonBg2->c },
  574. { 1.0, st::premiumButtonBg3->c },
  575. };
  576. }
  577. QGradientStops ButtonGradientStops() {
  578. return {
  579. { 0., st::premiumButtonBg1->c },
  580. { 0.6, st::premiumButtonBg2->c },
  581. { 1., st::premiumButtonBg3->c },
  582. };
  583. }
  584. QGradientStops LockGradientStops() {
  585. return ButtonGradientStops();
  586. }
  587. QGradientStops FullHeightGradientStops() {
  588. return {
  589. { 0.0, st::premiumIconBg1->c },
  590. { .28, st::premiumIconBg2->c },
  591. { .55, st::premiumButtonBg2->c },
  592. { .75, st::premiumButtonBg1->c },
  593. { 1.0, st::premiumIconBg3->c },
  594. };
  595. }
  596. QGradientStops GiftGradientStops() {
  597. return {
  598. { 0., st::premiumButtonBg1->c },
  599. { 1., st::premiumButtonBg2->c },
  600. };
  601. }
  602. QGradientStops StoriesIconsGradientStops() {
  603. return {
  604. { 0., st::premiumButtonBg1->c },
  605. { .33, st::premiumButtonBg2->c },
  606. { .66, st::premiumButtonBg3->c },
  607. { 1., st::premiumIconBg1->c },
  608. };
  609. }
  610. QGradientStops CreditsIconGradientStops() {
  611. return {
  612. { 0., st::creditsBg1->c },
  613. { 1., st::creditsBg2->c },
  614. };
  615. }
  616. QLinearGradient ComputeGradient(
  617. not_null<QWidget*> content,
  618. int left,
  619. int width) {
  620. // Take a full width of parent box without paddings.
  621. const auto fullGradientWidth = content->parentWidget()->width();
  622. auto fullGradient = QLinearGradient(0, 0, fullGradientWidth, 0);
  623. fullGradient.setStops(ButtonGradientStops());
  624. auto gradient = QLinearGradient(0, 0, width, 0);
  625. const auto fullFinal = float64(fullGradient.finalStop().x());
  626. left += ((fullGradientWidth - content->width()) / 2);
  627. gradient.setColorAt(
  628. .0,
  629. anim::gradient_color_at(fullGradient, left / fullFinal));
  630. gradient.setColorAt(
  631. 1.,
  632. anim::gradient_color_at(
  633. fullGradient,
  634. std::min(1., (left + width) / fullFinal)));
  635. return gradient;
  636. }
  637. void ShowListBox(
  638. not_null<Ui::GenericBox*> box,
  639. const style::PremiumLimits &st,
  640. std::vector<ListEntry> entries) {
  641. box->setWidth(st::boxWideWidth);
  642. const auto &stLabel = st::defaultFlatLabel;
  643. const auto &titlePadding = st::settingsPremiumPreviewTitlePadding;
  644. const auto &aboutPadding = st::settingsPremiumPreviewAboutPadding;
  645. const auto iconTitlePadding = st::settingsPremiumPreviewIconTitlePadding;
  646. const auto iconAboutPadding = st::settingsPremiumPreviewIconAboutPadding;
  647. auto lines = std::vector<Line*>();
  648. lines.reserve(int(entries.size()));
  649. auto icons = std::shared_ptr<std::vector<QColor>>();
  650. const auto content = box->verticalLayout();
  651. for (auto &entry : entries) {
  652. const auto title = content->add(
  653. object_ptr<Ui::FlatLabel>(
  654. content,
  655. base::take(entry.title) | Ui::Text::ToBold(),
  656. stLabel),
  657. entry.icon ? iconTitlePadding : titlePadding);
  658. content->add(
  659. object_ptr<Ui::FlatLabel>(
  660. content,
  661. base::take(entry.about),
  662. st::boxDividerLabel),
  663. entry.icon ? iconAboutPadding : aboutPadding);
  664. if (const auto outlined = entry.icon) {
  665. if (!icons) {
  666. icons = std::make_shared<std::vector<QColor>>();
  667. }
  668. const auto index = int(icons->size());
  669. icons->push_back(QColor());
  670. const auto icon = Ui::CreateChild<Ui::RpWidget>(content.get());
  671. icon->resize(outlined->size());
  672. title->topValue() | rpl::start_with_next([=](int y) {
  673. const auto shift = st::settingsPremiumPreviewIconPosition;
  674. icon->move(QPoint(0, y) + shift);
  675. }, icon->lifetime());
  676. icon->paintRequest() | rpl::start_with_next([=] {
  677. auto p = QPainter(icon);
  678. outlined->paintInCenter(p, icon->rect(), (*icons)[index]);
  679. }, icon->lifetime());
  680. }
  681. if (entry.leftNumber || entry.rightNumber) {
  682. auto factory = [=, text = ProcessTextFactory({})](int n) {
  683. if (entry.customRightText && (n == entry.rightNumber)) {
  684. return *entry.customRightText;
  685. } else {
  686. return text(n);
  687. }
  688. };
  689. const auto limitRow = content->add(
  690. object_ptr<Line>(
  691. content,
  692. st,
  693. entry.rightNumber,
  694. TextFactory(std::move(factory)),
  695. entry.leftNumber,
  696. kLimitRowRatio),
  697. st::settingsPremiumPreviewLinePadding);
  698. lines.push_back(limitRow);
  699. }
  700. }
  701. content->resizeToWidth(content->height());
  702. // Colors for icons.
  703. if (icons) {
  704. box->addSkip(st::settingsPremiumPreviewLinePadding.bottom());
  705. const auto stops = Ui::Premium::StoriesIconsGradientStops();
  706. for (auto i = 0, count = int(icons->size()); i != count; ++i) {
  707. (*icons)[i] = anim::gradient_color_at(
  708. stops,
  709. (count > 1) ? (i / float64(count - 1)) : 0.);
  710. }
  711. }
  712. // Color lines.
  713. if (!lines.empty()) {
  714. box->addSkip(st::settingsPremiumPreviewLinePadding.bottom());
  715. const auto from = lines.front()->y();
  716. const auto to = lines.back()->y() + lines.back()->height();
  717. const auto partialGradient = [&] {
  718. auto stops = Ui::Premium::FullHeightGradientStops();
  719. // Reverse.
  720. for (auto &stop : stops) {
  721. stop.first = std::abs(stop.first - 1.);
  722. }
  723. return PartialGradient(from, to, std::move(stops));
  724. }();
  725. for (auto i = 0; i < int(lines.size()); i++) {
  726. const auto &line = lines[i];
  727. const auto brush = QBrush(
  728. partialGradient.compute(line->y(), line->height()));
  729. line->setColorOverride(brush);
  730. }
  731. box->addSkip(st::settingsPremiumPreviewLinePadding.bottom());
  732. }
  733. }
  734. void AddGiftOptions(
  735. not_null<Ui::VerticalLayout*> parent,
  736. std::shared_ptr<Ui::RadiobuttonGroup> group,
  737. std::vector<Data::PremiumSubscriptionOption> gifts,
  738. const style::PremiumOption &st,
  739. bool topBadges) {
  740. struct Edges {
  741. Ui::RpWidget *top = nullptr;
  742. Ui::RpWidget *bottom = nullptr;
  743. };
  744. const auto edges = parent->lifetime().make_state<Edges>();
  745. struct Animation {
  746. int nowIndex = 0;
  747. Ui::Animations::Simple animation;
  748. };
  749. const auto wasGroupValue = group->current();
  750. const auto animation = parent->lifetime().make_state<Animation>();
  751. animation->nowIndex = wasGroupValue;
  752. const auto stops = GiftGradientStops();
  753. const auto addRow = [&](
  754. const Data::PremiumSubscriptionOption &info,
  755. int index) {
  756. const auto row = parent->add(
  757. object_ptr<Ui::AbstractButton>(parent),
  758. st.rowPadding);
  759. row->resize(row->width(), st.rowHeight);
  760. {
  761. if (!index) {
  762. edges->top = row;
  763. }
  764. edges->bottom = row;
  765. }
  766. const auto &stCheckbox = st::defaultBoxCheckbox;
  767. auto radioView = std::make_unique<GradientRadioView>(
  768. st::defaultRadio,
  769. (group->hasValue() && group->current() == index));
  770. const auto radioViewRaw = radioView.get();
  771. const auto radio = Ui::CreateChild<Ui::Radiobutton>(
  772. row,
  773. group,
  774. index,
  775. QString(),
  776. stCheckbox,
  777. std::move(radioView));
  778. radio->setAttribute(Qt::WA_TransparentForMouseEvents);
  779. radio->show();
  780. { // Paint the last frame instantly for the layer animation.
  781. group->setValue(0);
  782. group->setValue(wasGroupValue);
  783. radio->finishAnimating();
  784. }
  785. row->sizeValue(
  786. ) | rpl::start_with_next([=, margins = stCheckbox.margin](
  787. const QSize &s) {
  788. const auto radioHeight = radio->height()
  789. - margins.top()
  790. - margins.bottom();
  791. radio->moveToLeft(
  792. st.rowMargins.left(),
  793. (s.height() - radioHeight) / 2);
  794. }, radio->lifetime());
  795. {
  796. auto onceLifetime = std::make_shared<rpl::lifetime>();
  797. row->paintRequest(
  798. ) | rpl::take(
  799. 1
  800. ) | rpl::start_with_next([=]() mutable {
  801. const auto from = edges->top->y();
  802. const auto to = edges->bottom->y() + edges->bottom->height();
  803. auto partialGradient = PartialGradient(from, to, stops);
  804. radioViewRaw->setBrush(
  805. partialGradient.compute(row->y(), row->height()));
  806. if (onceLifetime) {
  807. base::take(onceLifetime)->destroy();
  808. }
  809. }, *onceLifetime);
  810. }
  811. constexpr auto kStar = QChar(0x2B50);
  812. const auto removedStar = [&](QString s) {
  813. return s.replace(kStar, QChar());
  814. };
  815. const auto &costPerMonthFont = st::shareBoxListItem.nameStyle.font;
  816. const auto &costTotalFont = st::normalFont;
  817. const auto costPerMonthIcon = info.costPerMonth.startsWith(kStar)
  818. ? GenerateStars(costPerMonthFont->height, 1)
  819. : QImage();
  820. const auto costPerMonthText = costPerMonthIcon.isNull()
  821. ? info.costPerMonth
  822. : removedStar(info.costPerMonth);
  823. const auto costTotalEntry = [&] {
  824. if (!info.costTotal.startsWith(kStar)) {
  825. return QImage();
  826. }
  827. const auto text = removedStar(info.costTotal);
  828. const auto icon = GenerateStars(costTotalFont->height, 1);
  829. auto result = QImage(
  830. QSize(costTotalFont->spacew + costTotalFont->width(text), 0)
  831. * style::DevicePixelRatio()
  832. + icon.size(),
  833. QImage::Format_ARGB32_Premultiplied);
  834. result.setDevicePixelRatio(style::DevicePixelRatio());
  835. result.fill(Qt::transparent);
  836. {
  837. auto p = QPainter(&result);
  838. p.drawImage(0, 0, icon);
  839. p.setPen(st::windowSubTextFg);
  840. p.setFont(costTotalFont);
  841. auto copy = info.costTotal;
  842. p.drawText(
  843. Rect(result.size() / style::DevicePixelRatio()),
  844. text,
  845. style::al_right);
  846. }
  847. return result;
  848. }();
  849. row->paintRequest(
  850. ) | rpl::start_with_next([=](const QRect &r) {
  851. auto p = QPainter(row);
  852. auto hq = PainterHighQualityEnabler(p);
  853. p.fillRect(r, Qt::transparent);
  854. const auto left = st.textLeft;
  855. const auto halfHeight = row->height() / 2;
  856. const auto titleFont = st::semiboldFont;
  857. p.setFont(titleFont);
  858. p.setPen(st::boxTextFg);
  859. if (info.costPerMonth.isEmpty() && info.discount.isEmpty()) {
  860. const auto r = row->rect().translated(
  861. -row->rect().left() + left,
  862. 0);
  863. p.drawText(r, info.duration, style::al_left);
  864. } else {
  865. p.drawText(
  866. left,
  867. st.subtitleTop + titleFont->ascent,
  868. info.duration);
  869. }
  870. const auto discountFont = st::windowFiltersButton.badgeStyle.font;
  871. const auto discountWidth = discountFont->width(info.discount);
  872. const auto &discountMargins = discountWidth
  873. ? st.badgeMargins
  874. : style::margins();
  875. const auto bottomLeftRect = QRect(
  876. left,
  877. halfHeight + discountMargins.top(),
  878. discountWidth
  879. + discountMargins.left()
  880. + discountMargins.right(),
  881. st.badgeHeight);
  882. const auto discountRect = topBadges
  883. ? bottomLeftRect.translated(
  884. titleFont->width(info.duration) + st.badgeShift.x(),
  885. -bottomLeftRect.top()
  886. + st.badgeShift.y()
  887. + st.subtitleTop
  888. + (titleFont->height - bottomLeftRect.height()) / 2)
  889. : bottomLeftRect;
  890. const auto from = edges->top->y();
  891. const auto to = edges->bottom->y() + edges->bottom->height();
  892. auto partialGradient = PartialGradient(from, to, stops);
  893. const auto partialGradientBrush = partialGradient.compute(
  894. row->y(),
  895. row->height());
  896. {
  897. p.setPen(Qt::NoPen);
  898. p.setBrush(partialGradientBrush);
  899. const auto round = st.badgeRadius;
  900. p.drawRoundedRect(discountRect, round, round);
  901. }
  902. if (st.borderWidth && (animation->nowIndex == index)) {
  903. const auto progress = animation->animation.value(1.);
  904. const auto w = row->width();
  905. auto gradient = QLinearGradient(
  906. w - w * progress,
  907. 0,
  908. w * 2,
  909. 0);
  910. gradient.setSpread(QGradient::Spread::RepeatSpread);
  911. gradient.setStops(stops);
  912. const auto pen = QPen(
  913. QBrush(partialGradientBrush),
  914. st.borderWidth);
  915. p.setPen(pen);
  916. p.setBrush(Qt::NoBrush);
  917. const auto borderRect = row->rect()
  918. - Margins(pen.width() / 2);
  919. const auto round = st.borderRadius;
  920. p.drawRoundedRect(borderRect, round, round);
  921. }
  922. p.setPen(st::premiumButtonFg);
  923. p.setFont(discountFont);
  924. p.drawText(discountRect, info.discount, style::al_center);
  925. const auto perRect = QMargins(0, 0, row->width(), 0)
  926. + bottomLeftRect.translated(
  927. topBadges
  928. ? 0
  929. : bottomLeftRect.width() + discountMargins.left(),
  930. 0);
  931. p.setPen(st::windowSubTextFg);
  932. p.setFont(costPerMonthFont);
  933. const auto perMonthLeft = costPerMonthFont->spacew
  934. + costPerMonthIcon.width() / style::DevicePixelRatio();
  935. p.drawText(
  936. perRect.translated(perMonthLeft, 0),
  937. costPerMonthText,
  938. style::al_left);
  939. p.drawImage(perRect.topLeft(), costPerMonthIcon);
  940. const auto totalRect = row->rect()
  941. - QMargins(0, 0, st.rowMargins.right(), 0);
  942. if (costTotalEntry.isNull()) {
  943. p.setFont(costTotalFont);
  944. p.drawText(totalRect, info.costTotal, style::al_right);
  945. } else {
  946. const auto size = costTotalEntry.size()
  947. / style::DevicePixelRatio();
  948. p.drawImage(
  949. totalRect.width() - size.width(),
  950. (row->height() - size.height()) / 2,
  951. costTotalEntry);
  952. }
  953. }, row->lifetime());
  954. row->setClickedCallback([=, duration = st::defaultCheck.duration] {
  955. group->setValue(index);
  956. animation->nowIndex = group->current();
  957. animation->animation.stop();
  958. animation->animation.start(
  959. [=] { parent->update(); },
  960. 0.,
  961. 1.,
  962. duration);
  963. });
  964. };
  965. for (auto i = 0; i < gifts.size(); i++) {
  966. addRow(gifts[i], i);
  967. }
  968. parent->resizeToWidth(parent->height());
  969. parent->update();
  970. }
  971. } // namespace Premium
  972. } // namespace Ui