fireworks_animation.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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/fireworks_animation.h"
  8. #include "base/random.h"
  9. #include "ui/painter.h"
  10. namespace Ui {
  11. namespace {
  12. constexpr auto kParticlesCount = 60;
  13. constexpr auto kFallCount = 30;
  14. constexpr auto kFirstUpdateTime = crl::time(16);
  15. constexpr auto kFireworkWidth = 480;
  16. constexpr auto kFireworkHeight = 320;
  17. QBrush Brush(int color) {
  18. return QBrush{ QColor(
  19. color & 0xFF,
  20. (color >> 8) & 0xFF,
  21. (color >> 16) & 0xFF)
  22. };
  23. }
  24. std::vector<QBrush> PrepareBrushes() {
  25. return {
  26. Brush(0xff2CBCE8),
  27. Brush(0xff9E04D0),
  28. Brush(0xffFECB02),
  29. Brush(0xffFD2357),
  30. Brush(0xff278CFE),
  31. Brush(0xff59B86C),
  32. };
  33. }
  34. [[nodiscard]] float64 RandomFloat01() {
  35. return base::RandomValue<uint32>()
  36. / float64(std::numeric_limits<uint32>::max());
  37. }
  38. } // namespace
  39. FireworksAnimation::FireworksAnimation(Fn<void()> repaint)
  40. : _brushes(PrepareBrushes())
  41. , _animation([=](crl::time now) { update(now); })
  42. , _repaint(std::move(repaint)) {
  43. _smallSide = style::ConvertScale(2);
  44. _particles.reserve(kParticlesCount + kFallCount);
  45. for (auto i = 0; i != kParticlesCount; ++i) {
  46. initParticle(_particles.emplace_back(), false);
  47. }
  48. _animation.start();
  49. }
  50. void FireworksAnimation::update(crl::time now) {
  51. const auto passed = _lastUpdate ? (now - _lastUpdate) : kFirstUpdateTime;
  52. _lastUpdate = now;
  53. auto allFinished = true;
  54. for (auto &particle : _particles) {
  55. updateParticle(particle, passed);
  56. if (!particle.finished) {
  57. allFinished = false;
  58. }
  59. }
  60. if (allFinished) {
  61. _animation.stop();
  62. } else if (_fallingDown >= kParticlesCount / 2 && _speedCoef > 0.2) {
  63. startFall();
  64. _speedCoef -= passed / 16.0 * 0.15;
  65. if (_speedCoef < 0.2) {
  66. _speedCoef = 0.2;
  67. }
  68. }
  69. _repaint();
  70. }
  71. bool FireworksAnimation::paint(QPainter &p, const QRect &rect) {
  72. if (rect.isEmpty()) {
  73. return false;
  74. }
  75. PainterHighQualityEnabler hq(p);
  76. p.setPen(Qt::NoPen);
  77. p.setClipRect(rect);
  78. for (auto &particle : _particles) {
  79. if (!particle.finished) {
  80. paintParticle(p, particle, rect);
  81. }
  82. }
  83. p.setClipping(false);
  84. return _animation.animating();
  85. }
  86. void FireworksAnimation::paintParticle(
  87. QPainter &p,
  88. const Particle &particle,
  89. const QRect &rect) {
  90. const auto size = particle.size;
  91. const auto x = rect.x() + (particle.x * rect.width() / kFireworkWidth);
  92. const auto y = rect.y() + (particle.y * rect.height() / kFireworkHeight);
  93. p.setBrush(_brushes[particle.color]);
  94. if (particle.type == Particle::Type::Circle) {
  95. p.drawEllipse(x, y, size, size);
  96. } else {
  97. const auto rect = QRect(-size, -_smallSide, size, _smallSide);
  98. p.save();
  99. p.translate(x, y);
  100. p.rotate(particle.rotation);
  101. p.drawRoundedRect(rect, _smallSide, _smallSide);
  102. p.restore();
  103. }
  104. }
  105. void FireworksAnimation::updateParticle(Particle &particle, crl::time dt) {
  106. if (particle.finished) {
  107. return;
  108. }
  109. const auto moveCoef = dt / 16.;
  110. particle.x += particle.moveX * moveCoef;
  111. particle.y += particle.moveY * moveCoef;
  112. if (particle.xFinished != 0) {
  113. const auto dp = 0.5;
  114. if (particle.xFinished == 1) {
  115. particle.moveX += dp * moveCoef * 0.05;
  116. if (particle.moveX >= dp) {
  117. particle.xFinished = 2;
  118. }
  119. } else {
  120. particle.moveX -= dp * moveCoef * 0.05f;
  121. if (particle.moveX <= -dp) {
  122. particle.xFinished = 1;
  123. }
  124. }
  125. } else {
  126. if (particle.right) {
  127. if (particle.moveX < 0) {
  128. particle.moveX += moveCoef * 0.05f;
  129. if (particle.moveX >= 0) {
  130. particle.moveX = 0;
  131. particle.xFinished = particle.finishedStart;
  132. }
  133. }
  134. } else {
  135. if (particle.moveX > 0) {
  136. particle.moveX -= moveCoef * 0.05f;
  137. if (particle.moveX <= 0) {
  138. particle.moveX = 0;
  139. particle.xFinished = particle.finishedStart;
  140. }
  141. }
  142. }
  143. }
  144. const auto yEdge = -0.5;
  145. const auto wasNegative = (particle.moveY < yEdge);
  146. if (particle.moveY > yEdge) {
  147. particle.moveY += (1. / 3.) * moveCoef * _speedCoef;
  148. } else {
  149. particle.moveY += (1. / 3.) * moveCoef;
  150. }
  151. if (wasNegative && particle.moveY > yEdge) {
  152. ++_fallingDown;
  153. }
  154. if (particle.type == Particle::Type::Rectangle) {
  155. particle.rotation += moveCoef * 10;
  156. if (particle.rotation > 360) {
  157. particle.rotation -= 360;
  158. }
  159. }
  160. if (particle.y >= kFireworkHeight) {
  161. particle.finished = true;
  162. }
  163. }
  164. void FireworksAnimation::startFall() {
  165. if (_startedFall) {
  166. return;
  167. }
  168. _startedFall = true;
  169. for (auto i = 0; i != kFallCount; ++i) {
  170. initParticle(_particles.emplace_back(), true);
  171. }
  172. }
  173. void FireworksAnimation::initParticle(Particle &particle, bool falling) {
  174. using Type = Particle::Type;
  175. using base::RandomIndex;
  176. particle.color = RandomIndex(_brushes.size());
  177. particle.type = RandomIndex(2) ? Type::Rectangle : Type::Circle;
  178. particle.right = (RandomIndex(2) == 1);
  179. particle.finishedStart = 1 + RandomIndex(2);
  180. if (particle.type == Type::Circle) {
  181. particle.size = style::ConvertScale(6 + RandomFloat01() * 3);
  182. } else {
  183. particle.size = style::ConvertScale(6 + RandomFloat01() * 6);
  184. particle.rotation = RandomIndex(360);
  185. }
  186. if (falling) {
  187. particle.y = -RandomFloat01() * kFireworkHeight * 1.2f;
  188. particle.x = 5 + RandomIndex(kFireworkWidth - 10);
  189. particle.xFinished = particle.finishedStart;
  190. } else {
  191. const auto xOffset = 4 + RandomIndex(10);
  192. const auto yOffset = kFireworkHeight / 4;
  193. if (particle.right) {
  194. particle.x = kFireworkWidth + xOffset;
  195. } else {
  196. particle.x = -xOffset;
  197. }
  198. particle.moveX = (particle.right ? -1 : 1) * (1.2 + RandomFloat01() * 4);
  199. particle.moveY = -(4 + RandomFloat01() * 4);
  200. particle.y = yOffset / 2 + RandomIndex(yOffset * 2);
  201. }
  202. }
  203. } // namespace Ui