snowflakes.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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/snowflakes.h"
  8. #include "base/random.h"
  9. #include "ui/effects/animation_value_f.h"
  10. #include "ui/painter.h"
  11. #include <QtCore/QtMath>
  12. namespace Ui {
  13. namespace {
  14. [[nodiscard]] QImage PrepareSnowflake(QBrush brush) {
  15. constexpr auto kPenWidth = 1.;
  16. constexpr auto kTailCount = 6;
  17. constexpr auto kAngle = (-M_PI / 2.);
  18. constexpr auto kTailSize = 6.;
  19. constexpr auto kSubtailPositionRatio = 2 / 3.;
  20. constexpr auto kSubtailSize = kTailSize / 3;
  21. constexpr auto kSubtailAngle1 = -M_PI / 6.;
  22. constexpr auto kSubtailAngle2 = -M_PI - kSubtailAngle1;
  23. constexpr auto kSpriteSize = (kTailSize + kPenWidth / 2.) * 2;
  24. const auto x = float64(style::ConvertScaleExact(kSpriteSize / 2.));
  25. const auto y = float64(style::ConvertScaleExact(kSpriteSize / 2.));
  26. const auto tailSize = style::ConvertScaleExact(kTailSize);
  27. const auto subtailSize = style::ConvertScaleExact(kSubtailSize);
  28. const auto endTail = QPointF(
  29. std::cos(kAngle) * tailSize,
  30. std::sin(kAngle) * tailSize);
  31. const auto startSubtail = endTail * kSubtailPositionRatio;
  32. const auto endSubtail1 = startSubtail + QPointF(
  33. subtailSize * std::cos(kSubtailAngle1),
  34. subtailSize * std::sin(kSubtailAngle1));
  35. const auto endSubtail2 = startSubtail + QPointF(
  36. subtailSize * std::cos(kSubtailAngle2),
  37. subtailSize * std::sin(kSubtailAngle2));
  38. const auto pen = QPen(
  39. std::move(brush),
  40. style::ConvertScaleExact(kPenWidth),
  41. Qt::SolidLine,
  42. Qt::RoundCap,
  43. Qt::RoundJoin);
  44. const auto s = style::ConvertScaleExact(kSpriteSize)
  45. * style::DevicePixelRatio();
  46. auto result = QImage(QSize(s, s), QImage::Format_ARGB32_Premultiplied);
  47. result.setDevicePixelRatio(style::DevicePixelRatio());
  48. result.fill(Qt::transparent);
  49. {
  50. auto p = QPainter(&result);
  51. PainterHighQualityEnabler hq(p);
  52. p.setPen(pen);
  53. p.setBrush(Qt::NoBrush);
  54. p.translate(x, y);
  55. const auto step = 360. / kTailCount;
  56. for (auto i = 0; i < kTailCount; i++) {
  57. p.rotate(step);
  58. p.drawLine(QPointF(), endTail);
  59. p.drawLine(startSubtail, endSubtail1);
  60. p.drawLine(startSubtail, endSubtail2);
  61. }
  62. }
  63. return result;
  64. }
  65. } // namespace
  66. Snowflakes::Snowflakes(Fn<void(const QRect &r)> updateCallback)
  67. : _lifeLength({ 300 * 2, 100 * 2 })
  68. , _deathTime({ 2000 * 5, 100 * 5 })
  69. , _scale({ 60, 100 })
  70. , _velocity({ 20 * 7, 4 * 7 })
  71. , _angle({ 70, 40 })
  72. , _relativeX({ 0, 100 })
  73. , _relativeY({ -10, 70 })
  74. , _appearProgressTill(200. / _deathTime.from)
  75. , _disappearProgressAfter(_appearProgressTill)
  76. , _dotMargins(3., 3., 3., 3.)
  77. , _renderMargins(1., 1., 1., 1.)
  78. , _animation([=](crl::time now) {
  79. if (now > _nextBirthTime && !_paused.at) {
  80. createParticle(now);
  81. }
  82. if (_rectToUpdate.isValid()) {
  83. updateCallback(base::take(_rectToUpdate));
  84. }
  85. }) {
  86. {
  87. const auto from = _deathTime.from + _deathTime.length;
  88. auto r = bytes::vector(from + 1);
  89. base::RandomFill(r.data(), r.size());
  90. const auto now = crl::now();
  91. for (auto i = -from; i < 0; i += randomInterval(_lifeLength, r[-i])) {
  92. createParticle(now + i);
  93. }
  94. updateCallback(_rectToUpdate);
  95. }
  96. if (!anim::Disabled()) {
  97. _animation.start();
  98. }
  99. }
  100. int Snowflakes::randomInterval(
  101. const Interval &interval,
  102. const bytes::type &random) const {
  103. return interval.from + (uchar(random) % interval.length);
  104. }
  105. crl::time Snowflakes::timeNow() const {
  106. return _paused.at ? _paused.at : (crl::now() - _paused.diff);
  107. }
  108. void Snowflakes::paint(QPainter &p, const QRectF &rect) {
  109. const auto opacity = p.opacity();
  110. PainterHighQualityEnabler hq(p);
  111. p.setPen(Qt::NoPen);
  112. p.setBrush(_brush);
  113. const auto now = timeNow();
  114. for (const auto &particle : _particles) {
  115. const auto progress = (now - particle.birthTime)
  116. / float64(particle.deathTime - particle.birthTime);
  117. if (progress > 1.) {
  118. continue;
  119. }
  120. const auto appearProgress = std::clamp(
  121. progress / _appearProgressTill,
  122. 0.,
  123. 1.);
  124. const auto dissappearProgress = 1.
  125. - (std::clamp(progress - _disappearProgressAfter, 0., 1.)
  126. / (1. - _disappearProgressAfter));
  127. p.setOpacity(appearProgress * dissappearProgress * opacity);
  128. const auto startX = rect.x() + rect.width() * particle.relativeX;
  129. const auto startY = rect.y() + rect.height() * particle.relativeY;
  130. const auto endX = startX + particle.velocityX;
  131. const auto endY = startY + particle.velocityY;
  132. const auto x = anim::interpolateF(startX, endX, progress);
  133. const auto y = anim::interpolateF(startY, endY, progress);
  134. if (particle.type == Type::Dot) {
  135. const auto renderRect = QRectF(x, y, 0., 0.)
  136. + _dotMargins * particle.scale;
  137. p.drawEllipse(renderRect);
  138. _rectToUpdate |= renderRect.toRect() + _renderMargins;
  139. } else if (particle.type == Type::Snowflake) {
  140. const auto s = _sprite.size() / style::DevicePixelRatio();
  141. const auto h = s.height() / 2.;
  142. const auto pos = QPointF(x - h, y - h);
  143. p.drawImage(pos, _sprite);
  144. _rectToUpdate |= QRectF(pos, s).toRect() + _renderMargins;
  145. }
  146. }
  147. p.setOpacity(opacity);
  148. }
  149. void Snowflakes::setPaused(bool paused) {
  150. paused |= anim::Disabled();
  151. if (paused) {
  152. _paused.diff = 0;
  153. _paused.at = crl::now();
  154. } else {
  155. _paused.diff = _paused.at ? (crl::now() - _paused.at) : 0;
  156. _paused.at = 0;
  157. }
  158. }
  159. void Snowflakes::setBrush(QBrush brush) {
  160. _brush = std::move(brush);
  161. _sprite = PrepareSnowflake(_brush);
  162. }
  163. void Snowflakes::createParticle(crl::time now) {
  164. constexpr auto kRandomSize = 9;
  165. auto random = bytes::vector(kRandomSize);
  166. base::RandomFill(random.data(), random.size());
  167. auto i = 0;
  168. auto next = [&] { return random[i++]; };
  169. _nextBirthTime = now + randomInterval(_lifeLength, next());
  170. const auto angle = randomInterval(_angle, next());
  171. const auto velocity = randomInterval(_velocity, next());
  172. auto particle = Particle{
  173. .birthTime = now,
  174. .deathTime = now + randomInterval(_deathTime, next()),
  175. .scale = float64(randomInterval(_scale, next())) / 100.,
  176. .relativeX = float64(randomInterval(_relativeX, next())) / 100.,
  177. .relativeY = float64(randomInterval(_relativeY, next())) / 100.,
  178. .velocityX = std::cos(M_PI / 180. * angle) * velocity,
  179. .velocityY = std::sin(M_PI / 180. * angle) * velocity,
  180. .type = ((uchar(next()) % 2) == 1 ? Type::Snowflake : Type::Dot),
  181. };
  182. for (auto i = 0; i < _particles.size(); i++) {
  183. if (particle.birthTime > _particles[i].deathTime) {
  184. _particles[i] = particle;
  185. return;
  186. }
  187. }
  188. _particles.push_back(particle);
  189. }
  190. } // namespace Ui