reaction_fly_animation.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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/reaction_fly_animation.h"
  8. #include "ui/text/text_custom_emoji.h"
  9. #include "ui/animated_icon.h"
  10. #include "ui/painter.h"
  11. #include "data/data_message_reactions.h"
  12. #include "data/data_session.h"
  13. #include "data/data_document.h"
  14. #include "data/data_document_media.h"
  15. #include "base/random.h"
  16. #include "styles/style_chat.h"
  17. namespace Ui {
  18. namespace {
  19. constexpr auto kFlyDuration = crl::time(300);
  20. constexpr auto kMiniCopies = 7;
  21. constexpr auto kMiniCopiesDurationMax = crl::time(1400);
  22. constexpr auto kMiniCopiesDurationMin = crl::time(700);
  23. constexpr auto kMiniCopiesScaleInDuration = crl::time(200);
  24. constexpr auto kMiniCopiesScaleOutDuration = crl::time(200);
  25. constexpr auto kMiniCopiesMaxScaleMin = 0.6;
  26. constexpr auto kMiniCopiesMaxScaleMax = 0.9;
  27. } // namespace
  28. ReactionFlyAnimationArgs ReactionFlyAnimationArgs::translated(QPoint point) const {
  29. return {
  30. .id = id,
  31. .flyIcon = flyIcon,
  32. .flyFrom = flyFrom.translated(point),
  33. };
  34. }
  35. auto ReactionFlyAnimation::flyCallback() {
  36. return [=] {
  37. if (!_fly.animating()) {
  38. _flyIcon = QImage();
  39. startAnimations();
  40. }
  41. if (_repaint) {
  42. _repaint();
  43. }
  44. };
  45. }
  46. auto ReactionFlyAnimation::callback() {
  47. return [=] {
  48. if (_repaint) {
  49. _repaint();
  50. }
  51. };
  52. }
  53. ReactionFlyAnimation::ReactionFlyAnimation(
  54. not_null<::Data::Reactions*> owner,
  55. ReactionFlyAnimationArgs &&args,
  56. Fn<void()> repaint,
  57. int size,
  58. Data::CustomEmojiSizeTag customSizeTag)
  59. : _owner(owner)
  60. , _repaint(std::move(repaint))
  61. , _flyFrom(args.flyFrom)
  62. , _scaleOutDuration(args.scaleOutDuration)
  63. , _scaleOutTarget(args.scaleOutTarget)
  64. , _forceFirstFrame(args.forceFirstFrame) {
  65. const auto &list = owner->list(::Data::Reactions::Type::All);
  66. auto centerIcon = (DocumentData*)nullptr;
  67. auto aroundAnimation = (DocumentData*)nullptr;
  68. if (const auto customId = args.id.custom()) {
  69. const auto esize = Data::FrameSizeFromTag(customSizeTag)
  70. / style::DevicePixelRatio();
  71. const auto data = &owner->owner();
  72. const auto document = data->document(customId);
  73. _custom = data->customEmojiManager().create(
  74. document,
  75. callback(),
  76. customSizeTag);
  77. _customSize = esize;
  78. _centerSizeMultiplier = _customSize / float64(size);
  79. aroundAnimation = owner->chooseGenericAnimation(document);
  80. } else if (args.id.paid()) {
  81. const auto fake = owner->lookupPaid();
  82. centerIcon = fake->centerIcon;
  83. aroundAnimation = owner->choosePaidReactionAnimation();
  84. _centerSizeMultiplier = 0.5;
  85. } else {
  86. const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
  87. if (i == end(list)/* || !i->centerIcon*/) {
  88. return;
  89. }
  90. centerIcon = i->centerIcon
  91. ? not_null(i->centerIcon)
  92. : i->selectAnimation;
  93. aroundAnimation = i->aroundAnimation;
  94. _centerSizeMultiplier = i->centerIcon ? 1. : 0.5;
  95. }
  96. const auto resolve = [&](
  97. std::unique_ptr<AnimatedIcon> &icon,
  98. DocumentData *document,
  99. int size) {
  100. if (!document) {
  101. return false;
  102. }
  103. const auto media = document->activeMediaView();
  104. if (!media || !media->loaded()) {
  105. return false;
  106. }
  107. icon = MakeAnimatedIcon({
  108. .generator = DocumentIconFrameGenerator(media),
  109. .sizeOverride = QSize(size, size),
  110. .colorized = media->owner()->emojiUsesTextColor(),
  111. });
  112. return true;
  113. };
  114. generateMiniCopies(size + size / 2, args.miniCopyMultiplier);
  115. if (args.effectOnly) {
  116. _effectOnly = true;
  117. } else if (!_custom && !resolve(_center, centerIcon, size)) {
  118. return;
  119. }
  120. resolve(_effect, aroundAnimation, size * 2);
  121. if (!args.flyIcon.isNull()) {
  122. _flyIcon = std::move(args.flyIcon);
  123. _fly.start(flyCallback(), 0., 1., kFlyDuration);
  124. } else if (!_center && !_effect && _miniCopies.empty()) {
  125. return;
  126. } else {
  127. startAnimations();
  128. }
  129. _valid = true;
  130. }
  131. ReactionFlyAnimation::~ReactionFlyAnimation() = default;
  132. QRect ReactionFlyAnimation::paintGetArea(
  133. QPainter &p,
  134. QPoint origin,
  135. QRect target,
  136. const QColor &colored,
  137. QRect clip,
  138. crl::time now) const {
  139. const auto scale = [&] {
  140. if (!_scaleOutDuration
  141. || (!_effect && !_noEffectScaleStarted)) {
  142. return 1.;
  143. }
  144. auto progress = _noEffectScaleAnimation.value(0.);
  145. if (_effect) {
  146. const auto rate = _effect->frameRate();
  147. if (!rate) {
  148. return 1.;
  149. }
  150. const auto left = _effect->framesCount() - _effect->frameIndex();
  151. const auto duration = left * 1000. / rate;
  152. progress = (duration < _scaleOutDuration)
  153. ? (duration / double(_scaleOutDuration))
  154. : 1.;
  155. }
  156. return (1. * progress + _scaleOutTarget * (1. - progress));
  157. }();
  158. auto hq = std::optional<PainterHighQualityEnabler>();
  159. if (scale < 1.) {
  160. hq.emplace(p);
  161. const auto shift = QRectF(target).center();
  162. p.translate(shift);
  163. p.scale(scale, scale);
  164. p.translate(-shift);
  165. }
  166. if (!_valid) {
  167. return QRect();
  168. } else if (_flyIcon.isNull()) {
  169. const auto wide = QRect(
  170. target.topLeft() - QPoint(target.width(), target.height()) / 2,
  171. target.size() * 2);
  172. const auto area = _miniCopies.empty()
  173. ? wide
  174. : QRect(
  175. target.topLeft() - QPoint(target.width(), target.height()),
  176. target.size() * 3);
  177. if (clip.isEmpty() || area.intersects(clip)) {
  178. paintCenterFrame(p, target, colored, now);
  179. if (const auto effect = _effect.get()) {
  180. if (effect->animating()) {
  181. // Must not be colored to text.
  182. p.drawImage(wide, effect->frame(QColor()));
  183. }
  184. }
  185. paintMiniCopies(p, target.center(), colored, now);
  186. }
  187. return area;
  188. }
  189. const auto from = _flyFrom.translated(origin);
  190. const auto lshift = target.width() / 4;
  191. const auto rshift = target.width() / 2 - lshift;
  192. const auto margins = QMargins{ lshift, lshift, rshift, rshift };
  193. target = target.marginsRemoved(margins);
  194. const auto progress = _fly.value(1.);
  195. const auto rect = QRect(
  196. anim::interpolate(from.x(), target.x(), progress),
  197. computeParabolicTop(
  198. _cached,
  199. from.y(),
  200. target.y(),
  201. st::reactionFlyUp,
  202. progress),
  203. anim::interpolate(from.width(), target.width(), progress),
  204. anim::interpolate(from.height(), target.height(), progress));
  205. const auto wide = rect.marginsAdded(margins);
  206. if (clip.isEmpty() || wide.intersects(clip)) {
  207. if (progress < 1.) {
  208. p.setOpacity(1. - progress);
  209. p.drawImage(rect, _flyIcon);
  210. }
  211. if (progress > 0.) {
  212. p.setOpacity(progress);
  213. paintCenterFrame(p, wide, colored, now);
  214. }
  215. p.setOpacity(1.);
  216. }
  217. return wide;
  218. }
  219. void ReactionFlyAnimation::paintCenterFrame(
  220. QPainter &p,
  221. QRect target,
  222. const QColor &colored,
  223. crl::time now) const {
  224. if (_effectOnly) {
  225. return;
  226. }
  227. const auto size = QSize(
  228. int(base::SafeRound(target.width() * _centerSizeMultiplier)),
  229. int(base::SafeRound(target.height() * _centerSizeMultiplier)));
  230. if (_center) {
  231. const auto rect = QRect(
  232. target.x() + (target.width() - size.width()) / 2,
  233. target.y() + (target.height() - size.height()) / 2,
  234. size.width(),
  235. size.height());
  236. p.drawImage(rect, _center->frame(st::windowFg->c));
  237. } else if (_custom) {
  238. const auto scaled = (size.width() != _customSize);
  239. _custom->paint(p, {
  240. .textColor = colored,
  241. .size = { _customSize, _customSize },
  242. .now = now,
  243. .scale = (scaled ? (size.width() / float64(_customSize)) : 1.),
  244. .position = QPoint(
  245. target.x() + (target.width() - _customSize) / 2,
  246. target.y() + (target.height() - _customSize) / 2),
  247. .scaled = scaled,
  248. .internal = { .forceFirstFrame = _forceFirstFrame },
  249. });
  250. }
  251. }
  252. void ReactionFlyAnimation::paintMiniCopies(
  253. QPainter &p,
  254. QPoint center,
  255. const QColor &colored,
  256. crl::time now) const {
  257. Expects(_miniCopies.empty() || _custom != nullptr);
  258. if (!_minis.animating()) {
  259. return;
  260. }
  261. auto hq = PainterHighQualityEnabler(p);
  262. const auto size = QSize(_customSize, _customSize);
  263. const auto progress = _minis.value(1.);
  264. const auto middle = center - QPoint(_customSize / 2, _customSize / 2);
  265. const auto scaleIn = kMiniCopiesScaleInDuration
  266. / float64(kMiniCopiesDurationMax);
  267. const auto scaleOut = kMiniCopiesScaleOutDuration
  268. / float64(kMiniCopiesDurationMax);
  269. auto context = Text::CustomEmoji::Context{
  270. .textColor = colored,
  271. .size = size,
  272. .now = now,
  273. .scaled = true,
  274. .internal = { .forceFirstFrame = _forceFirstFrame },
  275. };
  276. for (const auto &mini : _miniCopies) {
  277. if (progress >= mini.duration) {
  278. continue;
  279. }
  280. const auto value = progress / mini.duration;
  281. context.scale = (progress < scaleIn)
  282. ? (mini.maxScale * progress / scaleIn)
  283. : (progress <= mini.duration - scaleOut)
  284. ? mini.maxScale
  285. : (mini.maxScale * (mini.duration - progress) / scaleOut);
  286. context.position = middle + QPoint(
  287. anim::interpolate(0, mini.finalX, value),
  288. computeParabolicTop(
  289. mini.cached,
  290. 0,
  291. mini.finalY,
  292. mini.flyUp,
  293. value));
  294. _custom->paint(p, context);
  295. }
  296. }
  297. void ReactionFlyAnimation::generateMiniCopies(
  298. int size,
  299. float64 miniCopyMultiplier) {
  300. if (!_custom) {
  301. return;
  302. }
  303. const auto random = [] {
  304. constexpr auto count = 16384;
  305. return base::RandomIndex(count) / float64(count - 1);
  306. };
  307. const auto between = [](int a, int b) {
  308. return (a > b)
  309. ? (b + base::RandomIndex(a - b + 1))
  310. : (a + base::RandomIndex(b - a + 1));
  311. };
  312. _miniCopies.reserve(kMiniCopies);
  313. for (auto i = 0; i != kMiniCopies; ++i) {
  314. const auto scale = kMiniCopiesMaxScaleMin
  315. + (kMiniCopiesMaxScaleMax - kMiniCopiesMaxScaleMin) * random();
  316. const auto maxScale = scale * miniCopyMultiplier;
  317. const auto duration = between(
  318. kMiniCopiesDurationMin,
  319. kMiniCopiesDurationMax);
  320. const auto maxSize = int(std::ceil(maxScale * _customSize));
  321. const auto maxHalf = (maxSize + 1) / 2;
  322. const auto flyUpTill = std::max(size - maxHalf, size / 4 + 1);
  323. _miniCopies.push_back({
  324. .maxScale = maxScale,
  325. .duration = duration / float64(kMiniCopiesDurationMax),
  326. .flyUp = between(size / 4, flyUpTill),
  327. .finalX = between(-size, size),
  328. .finalY = between(size - (size / 4), size),
  329. });
  330. }
  331. }
  332. int ReactionFlyAnimation::computeParabolicTop(
  333. Parabolic &cache,
  334. int from,
  335. int to,
  336. int top,
  337. float64 progress) const {
  338. const auto t = progress;
  339. // result = a * t * t + b * t + c
  340. // y = a * t * t + b * t
  341. // shift = y_1 = y(1) = a + b
  342. // y_0 = y(t_0) = a * t_0 * t_0 + b * t_0
  343. // 0 = 2 * a * t_0 + b
  344. // b = y_1 - a
  345. // a = y_1 / (1 - 2 * t_0)
  346. // b = 2 * t_0 * y_1 / (2 * t_0 - 1)
  347. // t_0 = (y_0 / y_1) +- sqrt((y_0 / y_1) * (y_0 / y_1 - 1))
  348. const auto y_1 = to - from;
  349. if (cache.key != y_1) {
  350. const auto y_0 = std::min(0, y_1) - top;
  351. const auto ratio = y_1 ? (float64(y_0) / y_1) : 0.;
  352. const auto root = y_1 ? sqrt(ratio * (ratio - 1)) : 0.;
  353. const auto t_0 = !y_1
  354. ? 0.5
  355. : (y_1 > 0)
  356. ? (ratio + root)
  357. : (ratio - root);
  358. const auto a = y_1 ? (y_1 / (1 - 2 * t_0)) : (-4 * y_0);
  359. const auto b = y_1 - a;
  360. cache.key = y_1;
  361. cache.a = a;
  362. cache.b = b;
  363. }
  364. return int(base::SafeRound(cache.a * t * t + cache.b * t + from));
  365. }
  366. void ReactionFlyAnimation::startAnimations() {
  367. if (const auto center = _center.get()) {
  368. _center->animate(callback());
  369. }
  370. if (const auto effect = _effect.get()) {
  371. _effect->animate(callback());
  372. } else if (_scaleOutDuration > 0) {
  373. _noEffectScaleStarted = true;
  374. _noEffectScaleAnimation.start(callback(), 1, 0, _scaleOutDuration);
  375. }
  376. if (!_miniCopies.empty()) {
  377. _minis.start(callback(), 0., 1., kMiniCopiesDurationMax);
  378. }
  379. }
  380. void ReactionFlyAnimation::setRepaintCallback(Fn<void()> repaint) {
  381. _repaint = std::move(repaint);
  382. }
  383. bool ReactionFlyAnimation::flying() const {
  384. return !_flyIcon.isNull();
  385. }
  386. float64 ReactionFlyAnimation::flyingProgress() const {
  387. return _fly.value(1.);
  388. }
  389. bool ReactionFlyAnimation::finished() const {
  390. return !_valid
  391. || (_flyIcon.isNull()
  392. && (!_center || !_center->animating())
  393. && (!_effect || !_effect->animating())
  394. && !_noEffectScaleAnimation.animating()
  395. && !_minis.animating());
  396. }
  397. ReactionFlyCenter ReactionFlyAnimation::takeCenter() {
  398. _valid = false;
  399. return {
  400. .custom = std::move(_custom),
  401. .icon = std::move(_center),
  402. .scale = (_scaleOutDuration > 0) ? _scaleOutTarget : 1.,
  403. .centerSizeMultiplier = _centerSizeMultiplier,
  404. .customSize = _customSize,
  405. };
  406. }
  407. } // namespace HistoryView::Reactions