mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-01 00:08:02 +00:00
Added fireworks animation for right quiz answer.
This commit is contained in:
parent
66e3b529b7
commit
f1d155c3f6
@ -845,6 +845,8 @@ PRIVATE
|
||||
support/support_helper.h
|
||||
support/support_templates.cpp
|
||||
support/support_templates.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
ui/effects/round_checkbox.h
|
||||
ui/effects/send_action_animations.cpp
|
||||
|
@ -505,6 +505,10 @@ void Message::draw(
|
||||
const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
|
||||
drawRightAction(p, fastShareLeft, fastShareTop, width());
|
||||
}
|
||||
|
||||
if (media) {
|
||||
media->paintBubbleFireworks(p, g, ms);
|
||||
}
|
||||
} else if (media && media->isDisplayed()) {
|
||||
p.translate(g.topLeft());
|
||||
media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
|
||||
|
@ -247,6 +247,11 @@ public:
|
||||
[[nodiscard]] virtual QMargins bubbleRollRepaintMargins() const {
|
||||
return QMargins();
|
||||
}
|
||||
virtual void paintBubbleFireworks(
|
||||
Painter &p,
|
||||
const QRect &bubble,
|
||||
crl::time ms) const {
|
||||
}
|
||||
|
||||
virtual void unloadHeavyPart() {
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/effects/fireworks_animation.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_user.h"
|
||||
@ -36,6 +37,7 @@ constexpr auto kRotateSegments = 8;
|
||||
constexpr auto kRotateAmplitude = 3.;
|
||||
constexpr auto kScaleSegments = 2;
|
||||
constexpr auto kScaleAmplitude = 0.03;
|
||||
constexpr auto kRollDuration = crl::time(400);
|
||||
|
||||
struct PercentCounterItem {
|
||||
int index = 0;
|
||||
@ -380,26 +382,30 @@ void Poll::updateTexts() {
|
||||
if (willStartAnimation) {
|
||||
startAnswersAnimation();
|
||||
if (!voted) {
|
||||
checkQuizAnsweredWrong();
|
||||
checkQuizAnswered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Poll::checkQuizAnsweredWrong() {
|
||||
if (!_voted || !_poll->quiz()) {
|
||||
void Poll::checkQuizAnswered() {
|
||||
if (!_voted || !_votedFromHere || !_poll->quiz() || anim::Disabled()) {
|
||||
return;
|
||||
}
|
||||
const auto i = ranges::find(_answers, true, &Answer::chosen);
|
||||
if (i == end(_answers) || i->correct) {
|
||||
if (i == end(_answers)) {
|
||||
return;
|
||||
}
|
||||
constexpr auto kDuration = crl::time(400);
|
||||
_wrongAnswerAnimation.start(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||
0.,
|
||||
1.,
|
||||
kDuration,
|
||||
anim::linear);
|
||||
if (i->correct) {
|
||||
_fireworksAnimation = std::make_unique<Ui::FireworksAnimation>(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); });
|
||||
} else {
|
||||
_wrongAnswerAnimation.start(
|
||||
[=] { history()->owner().requestViewRepaint(_parent); },
|
||||
0.,
|
||||
1.,
|
||||
kRollDuration,
|
||||
anim::linear);
|
||||
}
|
||||
}
|
||||
|
||||
void Poll::updateRecentVoters() {
|
||||
@ -451,6 +457,7 @@ ClickHandlerPtr Poll::createAnswerClickHandler(
|
||||
}));
|
||||
}
|
||||
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||
_votedFromHere = true;
|
||||
history()->session().api().sendPollVotes(
|
||||
_parent->data()->fullId(),
|
||||
{ option });
|
||||
@ -490,9 +497,7 @@ void Poll::sendMultiOptions() {
|
||||
&Answer::option
|
||||
) | ranges::to_vector;
|
||||
if (!chosen.empty()) {
|
||||
for (auto &answer : _answers) {
|
||||
answer.selected = false;
|
||||
}
|
||||
_votedFromHere = true;
|
||||
history()->session().api().sendPollVotes(
|
||||
_parent->data()->fullId(),
|
||||
std::move(chosen));
|
||||
@ -506,7 +511,17 @@ void Poll::showResults() {
|
||||
}
|
||||
|
||||
void Poll::updateVotes() {
|
||||
_voted = _poll->voted();
|
||||
const auto voted = _poll->voted();
|
||||
if (_voted != voted) {
|
||||
_voted = voted;
|
||||
if (_voted) {
|
||||
for (auto &answer : _answers) {
|
||||
answer.selected = false;
|
||||
}
|
||||
} else {
|
||||
_votedFromHere = false;
|
||||
}
|
||||
}
|
||||
updateAnswerVotes();
|
||||
updateTotalVotes();
|
||||
}
|
||||
@ -1179,6 +1194,16 @@ QMargins Poll::bubbleRollRepaintMargins() const {
|
||||
return QMargins(kAdd, kAdd, kAdd, kAdd);
|
||||
}
|
||||
|
||||
void Poll::paintBubbleFireworks(
|
||||
Painter &p,
|
||||
const QRect &bubble,
|
||||
crl::time ms) const {
|
||||
if (!_fireworksAnimation || _fireworksAnimation->paint(p, bubble)) {
|
||||
return;
|
||||
}
|
||||
_fireworksAnimation = nullptr;
|
||||
}
|
||||
|
||||
void Poll::clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &handler,
|
||||
bool pressed) {
|
||||
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Ui {
|
||||
class RippleAnimation;
|
||||
class FireworksAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
@ -43,6 +44,10 @@ public:
|
||||
|
||||
BubbleRoll bubbleRoll() const override;
|
||||
QMargins bubbleRollRepaintMargins() const override;
|
||||
void paintBubbleFireworks(
|
||||
Painter &p,
|
||||
const QRect &bubble,
|
||||
crl::time ms) const override;
|
||||
|
||||
void clickHandlerPressedChanged(
|
||||
const ClickHandlerPtr &handler,
|
||||
@ -149,7 +154,7 @@ private:
|
||||
void toggleMultiOption(const QByteArray &option);
|
||||
void sendMultiOptions();
|
||||
void showResults();
|
||||
void checkQuizAnsweredWrong();
|
||||
void checkQuizAnswered();
|
||||
|
||||
[[nodiscard]] int bottomButtonHeight() const;
|
||||
|
||||
@ -172,10 +177,12 @@ private:
|
||||
|
||||
mutable std::unique_ptr<AnswersAnimation> _answersAnimation;
|
||||
mutable std::unique_ptr<SendingAnimation> _sendingAnimation;
|
||||
mutable std::unique_ptr<Ui::FireworksAnimation> _fireworksAnimation;
|
||||
Ui::Animations::Simple _wrongAnswerAnimation;
|
||||
mutable QPoint _lastLinkPoint;
|
||||
|
||||
bool _hasSelected = false;
|
||||
bool _votedFromHere = false;
|
||||
mutable bool _wrongAnswerAnimated = false;
|
||||
|
||||
};
|
||||
|
221
Telegram/SourceFiles/ui/effects/fireworks_animation.cpp
Normal file
221
Telegram/SourceFiles/ui/effects/fireworks_animation.cpp
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
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/openssl_help.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;
|
||||
|
||||
QBrush Brush(int color) {
|
||||
return QBrush{ QColor(
|
||||
color & 0xFF,
|
||||
(color >> 8) & 0xFF,
|
||||
(color >> 16) & 0xFF)
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<QBrush> PrepareBrushes() {
|
||||
return {
|
||||
Brush(0xff2CBCE8),
|
||||
Brush(0xff9E04D0),
|
||||
Brush(0xffFECB02),
|
||||
Brush(0xffFD2357),
|
||||
Brush(0xff278CFE),
|
||||
Brush(0xff59B86C),
|
||||
};
|
||||
}
|
||||
|
||||
int RandomInt(uint32 till) {
|
||||
return int(openssl::RandomValue<uint32>() % till);
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 RandomFloat01() {
|
||||
return openssl::RandomValue<uint32>()
|
||||
/ float64(std::numeric_limits<uint32>::max());
|
||||
}
|
||||
|
||||
} // 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;
|
||||
particle.color = RandomInt(_brushes.size());
|
||||
particle.type = RandomInt(2) ? Type::Rectangle : Type::Circle;
|
||||
particle.right = (RandomInt(2) == 1);
|
||||
particle.finishedStart = 1 + RandomInt(2);
|
||||
if (particle.type == Type::Circle) {
|
||||
particle.size = style::ConvertScale(6 + RandomFloat01() * 3);
|
||||
} else {
|
||||
particle.size = style::ConvertScale(6 + RandomFloat01() * 6);
|
||||
particle.rotation = RandomInt(360);
|
||||
}
|
||||
if (falling) {
|
||||
particle.y = -RandomFloat01() * kFireworkHeight * 1.2f;
|
||||
particle.x = 5 + RandomInt(kFireworkWidth - 10);
|
||||
particle.xFinished = particle.finishedStart;
|
||||
} else {
|
||||
const auto xOffset = 4 + RandomInt(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 + RandomFloat01() * 4);
|
||||
particle.moveY = -(4 + RandomFloat01() * 4);
|
||||
particle.y = yOffset / 2 + RandomInt(yOffset * 2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
63
Telegram/SourceFiles/ui/effects/fireworks_animation.h
Normal file
63
Telegram/SourceFiles/ui/effects/fireworks_animation.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class FireworksAnimation final {
|
||||
public:
|
||||
explicit FireworksAnimation(Fn<void()> repaint);
|
||||
|
||||
bool paint(QPainter &p, const QRect &rect);
|
||||
|
||||
private:
|
||||
struct Particle {
|
||||
enum class Type : uchar {
|
||||
Circle,
|
||||
Rectangle
|
||||
};
|
||||
|
||||
float64 x = 0.;
|
||||
float64 y = 0.;
|
||||
float64 moveX = 0.;
|
||||
float64 moveY = 0.;
|
||||
uint16 rotation = 0;
|
||||
|
||||
Type type = Type::Circle;
|
||||
uchar color = 0;
|
||||
bool right = false;
|
||||
uchar size = 0;
|
||||
uchar xFinished = 0;
|
||||
uchar finishedStart = 0;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
void update(crl::time now);
|
||||
void startFall();
|
||||
void paintParticle(
|
||||
QPainter &p,
|
||||
const Particle &particle,
|
||||
const QRect &rect);
|
||||
void initParticle(Particle &particle, bool falling);
|
||||
void updateParticle(Particle &particle, crl::time dt);
|
||||
|
||||
std::vector<Particle> _particles;
|
||||
std::vector<QBrush> _brushes;
|
||||
Ui::Animations::Basic _animation;
|
||||
Fn<void()> _repaint;
|
||||
crl::time _lastUpdate = 0;
|
||||
float64 _speedCoef = 1.;
|
||||
int _fallingDown = 0;
|
||||
int _smallSide = 0;
|
||||
bool _startedFall = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
Loading…
Reference in New Issue
Block a user