Files
AyuGramDesktop/Telegram/SourceFiles/ui/effects/fireworks_animation.cpp
T

218 lines
5.8 KiB
C++

/*
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/fireworks_animation.h"
#include "base/random.h"
#include "ui/painter.h"
namespace Ui {
namespace {
constexpr auto kParticlesCount = 60;
constexpr auto kFallCount = 30;
constexpr auto kFirstUpdateTime = crl::time(16);
constexpr auto kFireworkWidth = 480;
constexpr auto kFireworkHeight = 320;
std::vector<QBrush> PrepareBrushes() {
return {
QBrush{ Ui::ColorFromSerialized(0xE8BC2C) },
QBrush{ Ui::ColorFromSerialized(0xD0049E) },
QBrush{ Ui::ColorFromSerialized(0x02CBFE) },
QBrush{ Ui::ColorFromSerialized(0x5723FD) },
QBrush{ Ui::ColorFromSerialized(0xFE8C27) },
QBrush{ Ui::ColorFromSerialized(0x6CB859) },
};
}
} // namespace
FireworksAnimation::FireworksAnimation(Fn<void()> repaint)
: _brushes(PrepareBrushes())
, _animation([=](crl::time now) { update(now); })
, _repaint(std::move(repaint)) {
_smallSide = style::ConvertScale(2);
_particles.reserve(kParticlesCount + kFallCount);
for (auto i = 0; i != kParticlesCount; ++i) {
initParticle(_particles.emplace_back(), false);
}
_animation.start();
}
void FireworksAnimation::update(crl::time now) {
const auto passed = _lastUpdate ? (now - _lastUpdate) : kFirstUpdateTime;
_lastUpdate = now;
auto allFinished = true;
for (auto &particle : _particles) {
updateParticle(particle, passed);
if (!particle.finished) {
allFinished = false;
}
}
if (allFinished) {
_animation.stop();
} else if (_fallingDown >= kParticlesCount / 2 && _speedCoef > 0.2) {
startFall();
_speedCoef -= passed / 16.0 * 0.15;
if (_speedCoef < 0.2) {
_speedCoef = 0.2;
}
}
_repaint();
}
bool FireworksAnimation::paint(QPainter &p, const QRect &rect) {
if (rect.isEmpty()) {
return false;
}
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setClipRect(rect);
for (auto &particle : _particles) {
if (!particle.finished) {
paintParticle(p, particle, rect);
}
}
p.setClipping(false);
return _animation.animating();
}
void FireworksAnimation::paintParticle(
QPainter &p,
const Particle &particle,
const QRect &rect) {
const auto size = particle.size;
const auto x = rect.x() + (particle.x * rect.width() / kFireworkWidth);
const auto y = rect.y() + (particle.y * rect.height() / kFireworkHeight);
p.setBrush(_brushes[particle.color]);
if (particle.type == Particle::Type::Circle) {
p.drawEllipse(x, y, size, size);
} else {
const auto rect = QRect(-size, -_smallSide, size, _smallSide);
p.save();
p.translate(x, y);
p.rotate(particle.rotation);
p.drawRoundedRect(rect, _smallSide, _smallSide);
p.restore();
}
}
void FireworksAnimation::updateParticle(Particle &particle, crl::time dt) {
if (particle.finished) {
return;
}
const auto moveCoef = dt / 16.;
particle.x += particle.moveX * moveCoef;
particle.y += particle.moveY * moveCoef;
if (particle.xFinished != 0) {
const auto dp = 0.5;
if (particle.xFinished == 1) {
particle.moveX += dp * moveCoef * 0.05;
if (particle.moveX >= dp) {
particle.xFinished = 2;
}
} else {
particle.moveX -= dp * moveCoef * 0.05f;
if (particle.moveX <= -dp) {
particle.xFinished = 1;
}
}
} else {
if (particle.right) {
if (particle.moveX < 0) {
particle.moveX += moveCoef * 0.05f;
if (particle.moveX >= 0) {
particle.moveX = 0;
particle.xFinished = particle.finishedStart;
}
}
} else {
if (particle.moveX > 0) {
particle.moveX -= moveCoef * 0.05f;
if (particle.moveX <= 0) {
particle.moveX = 0;
particle.xFinished = particle.finishedStart;
}
}
}
}
const auto yEdge = -0.5;
const auto wasNegative = (particle.moveY < yEdge);
if (particle.moveY > yEdge) {
particle.moveY += (1. / 3.) * moveCoef * _speedCoef;
} else {
particle.moveY += (1. / 3.) * moveCoef;
}
if (wasNegative && particle.moveY > yEdge) {
++_fallingDown;
}
if (particle.type == Particle::Type::Rectangle) {
particle.rotation += moveCoef * 10;
if (particle.rotation > 360) {
particle.rotation -= 360;
}
}
if (particle.y >= kFireworkHeight) {
particle.finished = true;
}
}
void FireworksAnimation::startFall() {
if (_startedFall) {
return;
}
_startedFall = true;
for (auto i = 0; i != kFallCount; ++i) {
initParticle(_particles.emplace_back(), true);
}
}
void FireworksAnimation::initParticle(Particle &particle, bool falling) {
using Type = Particle::Type;
constexpr auto kRandomPerParticle = 10;
auto random = std::array<uint32, kRandomPerParticle>();
base::RandomFill(random.data(), random.size() * sizeof(uint32));
auto index = 0;
const auto nextIndex = [&](int count) {
return int(random[index++] % count);
};
const auto nextFloat = [&] {
return random[index++] / float64(std::numeric_limits<uint32>::max());
};
particle.color = nextIndex(_brushes.size());
particle.type = nextIndex(2) ? Type::Rectangle : Type::Circle;
particle.right = (nextIndex(2) == 1);
particle.finishedStart = 1 + nextIndex(2);
if (particle.type == Type::Circle) {
particle.size = style::ConvertScale(6 + nextFloat() * 3);
} else {
particle.size = style::ConvertScale(6 + nextFloat() * 6);
particle.rotation = nextIndex(360);
}
if (falling) {
particle.y = -nextFloat() * kFireworkHeight * 1.2f;
particle.x = 5 + nextIndex(kFireworkWidth - 10);
particle.xFinished = particle.finishedStart;
} else {
const auto xOffset = 4 + nextIndex(10);
const auto yOffset = kFireworkHeight / 4;
if (particle.right) {
particle.x = kFireworkWidth + xOffset;
} else {
particle.x = -xOffset;
}
particle.moveX = (particle.right ? -1 : 1) * (1.2 + nextFloat() * 4);
particle.moveY = -(4 + nextFloat() * 4);
particle.y = yOffset / 2 + nextIndex(yOffset * 2);
}
}
} // namespace Ui