| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "ui/effects/snowflakes.h"
- #include "base/random.h"
- #include "ui/effects/animation_value_f.h"
- #include "ui/painter.h"
- #include <QtCore/QtMath>
- namespace Ui {
- namespace {
- [[nodiscard]] QImage PrepareSnowflake(QBrush brush) {
- constexpr auto kPenWidth = 1.;
- constexpr auto kTailCount = 6;
- constexpr auto kAngle = (-M_PI / 2.);
- constexpr auto kTailSize = 6.;
- constexpr auto kSubtailPositionRatio = 2 / 3.;
- constexpr auto kSubtailSize = kTailSize / 3;
- constexpr auto kSubtailAngle1 = -M_PI / 6.;
- constexpr auto kSubtailAngle2 = -M_PI - kSubtailAngle1;
- constexpr auto kSpriteSize = (kTailSize + kPenWidth / 2.) * 2;
- const auto x = float64(style::ConvertScaleExact(kSpriteSize / 2.));
- const auto y = float64(style::ConvertScaleExact(kSpriteSize / 2.));
- const auto tailSize = style::ConvertScaleExact(kTailSize);
- const auto subtailSize = style::ConvertScaleExact(kSubtailSize);
- const auto endTail = QPointF(
- std::cos(kAngle) * tailSize,
- std::sin(kAngle) * tailSize);
- const auto startSubtail = endTail * kSubtailPositionRatio;
- const auto endSubtail1 = startSubtail + QPointF(
- subtailSize * std::cos(kSubtailAngle1),
- subtailSize * std::sin(kSubtailAngle1));
- const auto endSubtail2 = startSubtail + QPointF(
- subtailSize * std::cos(kSubtailAngle2),
- subtailSize * std::sin(kSubtailAngle2));
- const auto pen = QPen(
- std::move(brush),
- style::ConvertScaleExact(kPenWidth),
- Qt::SolidLine,
- Qt::RoundCap,
- Qt::RoundJoin);
- const auto s = style::ConvertScaleExact(kSpriteSize)
- * style::DevicePixelRatio();
- auto result = QImage(QSize(s, s), QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(style::DevicePixelRatio());
- result.fill(Qt::transparent);
- {
- auto p = QPainter(&result);
- PainterHighQualityEnabler hq(p);
- p.setPen(pen);
- p.setBrush(Qt::NoBrush);
- p.translate(x, y);
- const auto step = 360. / kTailCount;
- for (auto i = 0; i < kTailCount; i++) {
- p.rotate(step);
- p.drawLine(QPointF(), endTail);
- p.drawLine(startSubtail, endSubtail1);
- p.drawLine(startSubtail, endSubtail2);
- }
- }
- return result;
- }
- } // namespace
- Snowflakes::Snowflakes(Fn<void(const QRect &r)> updateCallback)
- : _lifeLength({ 300 * 2, 100 * 2 })
- , _deathTime({ 2000 * 5, 100 * 5 })
- , _scale({ 60, 100 })
- , _velocity({ 20 * 7, 4 * 7 })
- , _angle({ 70, 40 })
- , _relativeX({ 0, 100 })
- , _relativeY({ -10, 70 })
- , _appearProgressTill(200. / _deathTime.from)
- , _disappearProgressAfter(_appearProgressTill)
- , _dotMargins(3., 3., 3., 3.)
- , _renderMargins(1., 1., 1., 1.)
- , _animation([=](crl::time now) {
- if (now > _nextBirthTime && !_paused.at) {
- createParticle(now);
- }
- if (_rectToUpdate.isValid()) {
- updateCallback(base::take(_rectToUpdate));
- }
- }) {
- {
- const auto from = _deathTime.from + _deathTime.length;
- auto r = bytes::vector(from + 1);
- base::RandomFill(r.data(), r.size());
- const auto now = crl::now();
- for (auto i = -from; i < 0; i += randomInterval(_lifeLength, r[-i])) {
- createParticle(now + i);
- }
- updateCallback(_rectToUpdate);
- }
- if (!anim::Disabled()) {
- _animation.start();
- }
- }
- int Snowflakes::randomInterval(
- const Interval &interval,
- const bytes::type &random) const {
- return interval.from + (uchar(random) % interval.length);
- }
- crl::time Snowflakes::timeNow() const {
- return _paused.at ? _paused.at : (crl::now() - _paused.diff);
- }
- void Snowflakes::paint(QPainter &p, const QRectF &rect) {
- const auto opacity = p.opacity();
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(_brush);
- const auto now = timeNow();
- for (const auto &particle : _particles) {
- const auto progress = (now - particle.birthTime)
- / float64(particle.deathTime - particle.birthTime);
- if (progress > 1.) {
- continue;
- }
- const auto appearProgress = std::clamp(
- progress / _appearProgressTill,
- 0.,
- 1.);
- const auto dissappearProgress = 1.
- - (std::clamp(progress - _disappearProgressAfter, 0., 1.)
- / (1. - _disappearProgressAfter));
- p.setOpacity(appearProgress * dissappearProgress * opacity);
- const auto startX = rect.x() + rect.width() * particle.relativeX;
- const auto startY = rect.y() + rect.height() * particle.relativeY;
- const auto endX = startX + particle.velocityX;
- const auto endY = startY + particle.velocityY;
- const auto x = anim::interpolateF(startX, endX, progress);
- const auto y = anim::interpolateF(startY, endY, progress);
- if (particle.type == Type::Dot) {
- const auto renderRect = QRectF(x, y, 0., 0.)
- + _dotMargins * particle.scale;
- p.drawEllipse(renderRect);
- _rectToUpdate |= renderRect.toRect() + _renderMargins;
- } else if (particle.type == Type::Snowflake) {
- const auto s = _sprite.size() / style::DevicePixelRatio();
- const auto h = s.height() / 2.;
- const auto pos = QPointF(x - h, y - h);
- p.drawImage(pos, _sprite);
- _rectToUpdate |= QRectF(pos, s).toRect() + _renderMargins;
- }
- }
- p.setOpacity(opacity);
- }
- void Snowflakes::setPaused(bool paused) {
- paused |= anim::Disabled();
- if (paused) {
- _paused.diff = 0;
- _paused.at = crl::now();
- } else {
- _paused.diff = _paused.at ? (crl::now() - _paused.at) : 0;
- _paused.at = 0;
- }
- }
- void Snowflakes::setBrush(QBrush brush) {
- _brush = std::move(brush);
- _sprite = PrepareSnowflake(_brush);
- }
- void Snowflakes::createParticle(crl::time now) {
- constexpr auto kRandomSize = 9;
- auto random = bytes::vector(kRandomSize);
- base::RandomFill(random.data(), random.size());
- auto i = 0;
- auto next = [&] { return random[i++]; };
- _nextBirthTime = now + randomInterval(_lifeLength, next());
- const auto angle = randomInterval(_angle, next());
- const auto velocity = randomInterval(_velocity, next());
- auto particle = Particle{
- .birthTime = now,
- .deathTime = now + randomInterval(_deathTime, next()),
- .scale = float64(randomInterval(_scale, next())) / 100.,
- .relativeX = float64(randomInterval(_relativeX, next())) / 100.,
- .relativeY = float64(randomInterval(_relativeY, next())) / 100.,
- .velocityX = std::cos(M_PI / 180. * angle) * velocity,
- .velocityY = std::sin(M_PI / 180. * angle) * velocity,
- .type = ((uchar(next()) % 2) == 1 ? Type::Snowflake : Type::Dot),
- };
- for (auto i = 0; i < _particles.size(); i++) {
- if (particle.birthTime > _particles[i].deathTime) {
- _particles[i] = particle;
- return;
- }
- }
- _particles.push_back(particle);
- }
- } // namespace Ui
|