premium_bubble.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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_bubble.h"
  8. #include "base/debug_log.h"
  9. #include "base/object_ptr.h"
  10. #include "lang/lang_keys.h"
  11. #include "ui/effects/gradient.h"
  12. #include "ui/effects/premium_graphics.h"
  13. #include "ui/wrap/padding_wrap.h"
  14. #include "ui/wrap/vertical_layout.h"
  15. #include "ui/painter.h"
  16. #include "styles/style_layers.h"
  17. #include "styles/style_premium.h"
  18. namespace Ui::Premium {
  19. namespace {
  20. constexpr auto kBubbleRadiusSubtractor = 2;
  21. constexpr auto kDeflectionSmall = 20.;
  22. constexpr auto kDeflection = 30.;
  23. constexpr auto kStepBeforeDeflection = 0.75;
  24. constexpr auto kStepAfterDeflection = kStepBeforeDeflection
  25. + (1. - kStepBeforeDeflection) / 2.;
  26. constexpr auto kSlideDuration = crl::time(1000);
  27. } // namespace
  28. TextFactory ProcessTextFactory(
  29. std::optional<tr::phrase<lngtag_count>> phrase) {
  30. return phrase
  31. ? TextFactory([=](int n) { return (*phrase)(tr::now, lt_count, n); })
  32. : TextFactory([=](int n) { return QString::number(n); });
  33. }
  34. Bubble::Bubble(
  35. const style::PremiumBubble &st,
  36. Fn<void()> updateCallback,
  37. TextFactory textFactory,
  38. const style::icon *icon,
  39. bool hasTail)
  40. : _st(st)
  41. , _updateCallback(std::move(updateCallback))
  42. , _textFactory(std::move(textFactory))
  43. , _icon(icon)
  44. , _numberAnimation(_st.font, _updateCallback)
  45. , _height(_st.height + _st.tailSize.height())
  46. , _textTop((_height - _st.tailSize.height() - _st.font->height) / 2)
  47. , _hasTail(hasTail) {
  48. _numberAnimation.setDisabledMonospace(true);
  49. _numberAnimation.setWidthChangedCallback([=] {
  50. _widthChanges.fire({});
  51. });
  52. _numberAnimation.setText(_textFactory(0), 0);
  53. _numberAnimation.finishAnimating();
  54. }
  55. crl::time Bubble::SlideNoDeflectionDuration() {
  56. return kSlideDuration * kStepBeforeDeflection;
  57. }
  58. int Bubble::counter() const {
  59. return _counter;
  60. }
  61. int Bubble::height() const {
  62. return _height;
  63. }
  64. int Bubble::bubbleRadius() const {
  65. return (_height - _st.tailSize.height()) / 2 - kBubbleRadiusSubtractor;
  66. }
  67. int Bubble::filledWidth() const {
  68. return _st.padding.left()
  69. + _icon->width()
  70. + _st.textSkip
  71. + _st.padding.right();
  72. }
  73. int Bubble::width() const {
  74. return filledWidth() + _numberAnimation.countWidth();
  75. }
  76. int Bubble::countMaxWidth(int maxPossibleCounter) const {
  77. auto numbers = Ui::NumbersAnimation(_st.font, [] {});
  78. numbers.setDisabledMonospace(true);
  79. numbers.setDuration(0);
  80. numbers.setText(_textFactory(0), 0);
  81. numbers.setText(_textFactory(maxPossibleCounter), maxPossibleCounter);
  82. numbers.finishAnimating();
  83. return filledWidth() + numbers.maxWidth();
  84. }
  85. void Bubble::setCounter(int value) {
  86. if (_counter != value) {
  87. _counter = value;
  88. _numberAnimation.setText(_textFactory(_counter), _counter);
  89. }
  90. }
  91. void Bubble::setTailEdge(EdgeProgress edge) {
  92. _tailEdge = std::clamp(edge, 0., 1.);
  93. }
  94. void Bubble::setFlipHorizontal(bool value) {
  95. _flipHorizontal = value;
  96. }
  97. void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) {
  98. if (_counter < 0) {
  99. return;
  100. }
  101. const auto penWidth = _st.penWidth;
  102. const auto penWidthHalf = penWidth / 2;
  103. const auto bubbleRect = r - style::margins(
  104. penWidthHalf,
  105. penWidthHalf,
  106. penWidthHalf,
  107. _st.tailSize.height() + penWidthHalf);
  108. {
  109. const auto radius = bubbleRadius();
  110. auto pathTail = QPainterPath();
  111. const auto tailWHalf = _st.tailSize.width() / 2.;
  112. const auto progress = _tailEdge;
  113. const auto tailTop = bubbleRect.y() + bubbleRect.height();
  114. const auto tailLeftFull = bubbleRect.x()
  115. + (bubbleRect.width() * 0.5)
  116. - tailWHalf;
  117. const auto tailLeft = bubbleRect.x()
  118. + (bubbleRect.width() * 0.5 * (progress + 1.))
  119. - tailWHalf;
  120. const auto tailCenter = tailLeft + tailWHalf;
  121. const auto tailRight = [&] {
  122. const auto max = bubbleRect.x() + bubbleRect.width();
  123. const auto right = tailLeft + _st.tailSize.width();
  124. const auto bottomMax = max - radius;
  125. return (right > bottomMax)
  126. ? std::max(float64(tailCenter), float64(bottomMax))
  127. : right;
  128. }();
  129. if (_hasTail) {
  130. pathTail.moveTo(tailLeftFull, tailTop);
  131. pathTail.lineTo(tailLeft, tailTop);
  132. pathTail.lineTo(tailCenter, tailTop + _st.tailSize.height());
  133. pathTail.lineTo(tailRight, tailTop);
  134. pathTail.lineTo(tailRight, tailTop - radius);
  135. pathTail.moveTo(tailLeftFull, tailTop);
  136. }
  137. auto pathBubble = QPainterPath();
  138. pathBubble.setFillRule(Qt::WindingFill);
  139. pathBubble.addRoundedRect(bubbleRect, radius, radius);
  140. auto hq = PainterHighQualityEnabler(p);
  141. p.setPen(QPen(
  142. brush,
  143. penWidth,
  144. Qt::SolidLine,
  145. Qt::RoundCap,
  146. Qt::RoundJoin));
  147. p.setBrush(brush);
  148. if (_flipHorizontal) {
  149. auto m = QTransform();
  150. const auto center = QRectF(bubbleRect).center();
  151. m.translate(center.x(), center.y());
  152. m.scale(-1., 1.);
  153. m.translate(-center.x(), -center.y());
  154. p.drawPath(m.map(pathTail + pathBubble));
  155. } else {
  156. p.drawPath(pathTail + pathBubble);
  157. }
  158. }
  159. p.setPen(st::activeButtonFg);
  160. p.setFont(_st.font);
  161. const auto iconLeft = r.x() + _st.padding.left();
  162. _icon->paint(
  163. p,
  164. iconLeft,
  165. bubbleRect.y() + (bubbleRect.height() - _icon->height()) / 2,
  166. bubbleRect.width());
  167. _numberAnimation.paint(
  168. p,
  169. iconLeft + _icon->width() + _st.textSkip,
  170. r.y() + _textTop,
  171. width() / 2);
  172. }
  173. rpl::producer<> Bubble::widthChanges() const {
  174. return _widthChanges.events();
  175. }
  176. BubbleWidget::BubbleWidget(
  177. not_null<Ui::RpWidget*> parent,
  178. const style::PremiumBubble &st,
  179. TextFactory textFactory,
  180. rpl::producer<BubbleRowState> state,
  181. BubbleType type,
  182. rpl::producer<> showFinishes,
  183. const style::icon *icon,
  184. const style::margins &outerPadding)
  185. : RpWidget(parent)
  186. , _st(st)
  187. , _state(std::move(state))
  188. , _bubble(
  189. _st,
  190. [=] { update(); },
  191. std::move(textFactory),
  192. icon,
  193. (type != BubbleType::NoPremium))
  194. , _type(type)
  195. , _outerPadding(outerPadding)
  196. , _deflection(kDeflection)
  197. , _stepBeforeDeflection(kStepBeforeDeflection)
  198. , _stepAfterDeflection(kStepAfterDeflection) {
  199. const auto resizeTo = [=](int w, int h) {
  200. _deflection = (w > _st.widthLimit)
  201. ? kDeflectionSmall
  202. : kDeflection;
  203. _spaceForDeflection = QSize(_st.skip, _st.skip);
  204. resize(QSize(w, h) + 2 * _spaceForDeflection);
  205. };
  206. resizeTo(_bubble.width(), _bubble.height());
  207. _bubble.widthChanges(
  208. ) | rpl::start_with_next([=] {
  209. resizeTo(_bubble.width(), _bubble.height());
  210. }, lifetime());
  211. std::move(
  212. showFinishes
  213. ) | rpl::take(1) | rpl::start_with_next([=] {
  214. _state.value(
  215. ) | rpl::start_with_next([=](BubbleRowState state) {
  216. animateTo(state);
  217. }, lifetime());
  218. parent->widthValue() | rpl::start_with_next([=](int w) {
  219. if (!_appearanceAnimation.animating()) {
  220. const auto x = base::SafeRound(
  221. w * _state.current().ratio - width() / 2);
  222. const auto padding = _spaceForDeflection.width();
  223. moveToLeft(
  224. std::clamp(int(x), -padding, w - width() + padding),
  225. y());
  226. }
  227. }, lifetime());
  228. }, lifetime());
  229. }
  230. void BubbleWidget::animateTo(BubbleRowState state) {
  231. _maxBubbleWidth = _bubble.countMaxWidth(state.counter);
  232. const auto parent = parentWidget();
  233. const auto available = parent->width()
  234. - _outerPadding.left()
  235. - _outerPadding.right();
  236. const auto halfWidth = (_maxBubbleWidth / 2);
  237. const auto computeLeft = [=](float64 pointRatio, float64 animProgress) {
  238. const auto delta = (pointRatio - _animatingFromResultRatio);
  239. const auto center = available
  240. * (_animatingFromResultRatio + delta * animProgress);
  241. return center - halfWidth + _outerPadding.left();
  242. };
  243. const auto moveEndPoint = state.ratio;
  244. const auto computeRightEdge = [=] {
  245. return parent->width()
  246. - _outerPadding.right()
  247. - _maxBubbleWidth;
  248. };
  249. struct Edge final {
  250. float64 goodPointRatio = 0.;
  251. float64 bubbleEdge = 0.;
  252. };
  253. const auto desiredFinish = computeLeft(moveEndPoint, 1.);
  254. const auto leftEdge = [&]() -> Edge {
  255. const auto edge = _outerPadding.left();
  256. if (desiredFinish < edge) {
  257. const auto goodPointRatio = float64(halfWidth) / available;
  258. const auto bubbleLeftEdge = (desiredFinish - edge)
  259. / float64(halfWidth);
  260. return { goodPointRatio, bubbleLeftEdge };
  261. }
  262. return {};
  263. }();
  264. const auto rightEdge = [&]() -> Edge {
  265. const auto edge = computeRightEdge();
  266. if (desiredFinish > edge) {
  267. const auto goodPointRatio = 1. - float64(halfWidth) / available;
  268. const auto bubbleRightEdge = (desiredFinish - edge)
  269. / float64(halfWidth);
  270. return { goodPointRatio, bubbleRightEdge };
  271. }
  272. return {};
  273. }();
  274. const auto finalEdge = (leftEdge.bubbleEdge < 0.)
  275. ? leftEdge.bubbleEdge
  276. : rightEdge.bubbleEdge;
  277. _ignoreDeflection = !_state.current().dynamic && (finalEdge != 0.);
  278. if (_ignoreDeflection) {
  279. _stepBeforeDeflection = 1.;
  280. _stepAfterDeflection = 1.;
  281. } else {
  282. _stepBeforeDeflection = kStepBeforeDeflection;
  283. _stepAfterDeflection = kStepAfterDeflection;
  284. }
  285. const auto resultMoveEndPoint = (finalEdge < 0)
  286. ? leftEdge.goodPointRatio
  287. : (finalEdge > 0)
  288. ? rightEdge.goodPointRatio
  289. : moveEndPoint;
  290. const auto duration = kSlideDuration
  291. * (_ignoreDeflection ? kStepBeforeDeflection : 1.)
  292. * ((_state.current().ratio < 0.001) ? 0.5 : 1.);
  293. if (state.animateFromZero) {
  294. _animatingFrom.ratio = 0.;
  295. _animatingFrom.counter = 0;
  296. _animatingFromResultRatio = 0.;
  297. _animatingFromBubbleEdge = 0.;
  298. }
  299. _appearanceAnimation.start([=](float64 value) {
  300. if (!_appearanceAnimation.animating()) {
  301. _animatingFrom = state;
  302. _animatingFromResultRatio = resultMoveEndPoint;
  303. _animatingFromBubbleEdge = finalEdge;
  304. }
  305. value = std::abs(value);
  306. const auto moveProgress = std::clamp(
  307. (value / _stepBeforeDeflection),
  308. 0.,
  309. 1.);
  310. const auto counterProgress = std::clamp(
  311. (value / _stepAfterDeflection),
  312. 0.,
  313. 1.);
  314. const auto nowBubbleEdge = _animatingFromBubbleEdge
  315. + (finalEdge - _animatingFromBubbleEdge) * moveProgress;
  316. moveToLeft(-_spaceForDeflection.width()
  317. + std::max(
  318. int(base::SafeRound(
  319. computeLeft(resultMoveEndPoint, moveProgress))),
  320. 0),
  321. 0);
  322. const auto now = _animatingFrom.counter
  323. + counterProgress * (state.counter - _animatingFrom.counter);
  324. _bubble.setCounter(int(base::SafeRound(now)));
  325. _bubble.setFlipHorizontal(nowBubbleEdge < 0);
  326. _bubble.setTailEdge(std::abs(nowBubbleEdge));
  327. update();
  328. },
  329. 0.,
  330. (state.ratio >= _animatingFrom.ratio) ? 1. : -1.,
  331. duration,
  332. anim::easeOutCirc);
  333. }
  334. void BubbleWidget::paintEvent(QPaintEvent *e) {
  335. if (_bubble.counter() < 0) {
  336. return;
  337. }
  338. auto p = QPainter(this);
  339. const auto padding = QMargins(
  340. _spaceForDeflection.width(),
  341. _spaceForDeflection.height(),
  342. _spaceForDeflection.width(),
  343. _spaceForDeflection.height());
  344. const auto bubbleRect = rect() - padding;
  345. const auto params = GradientParams{
  346. .left = x() + _spaceForDeflection.width(),
  347. .width = bubbleRect.width(),
  348. .outer = parentWidget()->parentWidget()->width(),
  349. };
  350. if (_cachedGradientParams != params) {
  351. _cachedGradient = ComputeGradient(
  352. parentWidget(),
  353. params.left,
  354. params.width);
  355. _cachedGradientParams = params;
  356. }
  357. if (_appearanceAnimation.animating()) {
  358. const auto value = _appearanceAnimation.value(1.);
  359. const auto progress = std::abs(value);
  360. const auto finalScale = (_animatingFromResultRatio > 0.)
  361. || (_state.current().ratio < 0.001);
  362. const auto scaleProgress = finalScale
  363. ? 1.
  364. : std::clamp((progress / _stepBeforeDeflection), 0., 1.);
  365. const auto scale = scaleProgress;
  366. const auto rotationProgress = std::clamp(
  367. (progress - _stepBeforeDeflection) / (1. - _stepBeforeDeflection),
  368. 0.,
  369. 1.);
  370. const auto rotationProgressReverse = std::clamp(
  371. (progress - _stepAfterDeflection) / (1. - _stepAfterDeflection),
  372. 0.,
  373. 1.);
  374. const auto offsetX = bubbleRect.x() + bubbleRect.width() / 2;
  375. const auto offsetY = bubbleRect.y() + bubbleRect.height();
  376. p.translate(offsetX, offsetY);
  377. p.scale(scale, scale);
  378. if (!_ignoreDeflection) {
  379. p.rotate((rotationProgress - rotationProgressReverse)
  380. * _deflection
  381. * (value < 0. ? -1. : 1.));
  382. }
  383. p.translate(-offsetX, -offsetY);
  384. }
  385. _bubble.paintBubble(p, bubbleRect, [&] {
  386. switch (_type) {
  387. case BubbleType::NoPremium: return st::windowBgActive->b;
  388. case BubbleType::Premium: return QBrush(_cachedGradient);
  389. case BubbleType::Credits: return st::creditsBg3->b;
  390. }
  391. Unexpected("Type in Premium::BubbleWidget.");
  392. }());
  393. }
  394. void AddBubbleRow(
  395. not_null<Ui::VerticalLayout*> parent,
  396. const style::PremiumBubble &st,
  397. rpl::producer<> showFinishes,
  398. int min,
  399. int current,
  400. int max,
  401. BubbleType type,
  402. std::optional<tr::phrase<lngtag_count>> phrase,
  403. const style::icon *icon) {
  404. AddBubbleRow(
  405. parent,
  406. st,
  407. std::move(showFinishes),
  408. rpl::single(BubbleRowState{
  409. .counter = current,
  410. .ratio = (current - min) / float64(max - min),
  411. }),
  412. type,
  413. ProcessTextFactory(phrase),
  414. icon,
  415. st::boxRowPadding);
  416. }
  417. void AddBubbleRow(
  418. not_null<Ui::VerticalLayout*> parent,
  419. const style::PremiumBubble &st,
  420. rpl::producer<> showFinishes,
  421. rpl::producer<BubbleRowState> state,
  422. BubbleType type,
  423. Fn<QString(int)> text,
  424. const style::icon *icon,
  425. const style::margins &outerPadding) {
  426. const auto container = parent->add(
  427. object_ptr<Ui::FixedHeightWidget>(parent, 0));
  428. const auto bubble = Ui::CreateChild<BubbleWidget>(
  429. container,
  430. st,
  431. text ? std::move(text) : ProcessTextFactory(std::nullopt),
  432. std::move(state),
  433. type,
  434. std::move(showFinishes),
  435. icon,
  436. outerPadding);
  437. rpl::combine(
  438. container->sizeValue(),
  439. bubble->sizeValue()
  440. ) | rpl::start_with_next([=](const QSize &parentSize, const QSize &size) {
  441. container->resize(parentSize.width(), size.height());
  442. }, bubble->lifetime());
  443. bubble->show();
  444. }
  445. } // namespace Ui::Premium