Initial SpoilerMess implementation.
This commit is contained in:
parent
4d2fc25d03
commit
00a668c3f3
3 changed files with 261 additions and 0 deletions
|
|
@ -67,6 +67,8 @@ PRIVATE
|
||||||
ui/effects/show_animation.h
|
ui/effects/show_animation.h
|
||||||
ui/effects/slide_animation.cpp
|
ui/effects/slide_animation.cpp
|
||||||
ui/effects/slide_animation.h
|
ui/effects/slide_animation.h
|
||||||
|
ui/effects/spoiler_mess.cpp
|
||||||
|
ui/effects/spoiler_mess.h
|
||||||
ui/gl/gl_detection.cpp
|
ui/gl/gl_detection.cpp
|
||||||
ui/gl/gl_detection.h
|
ui/gl/gl_detection.h
|
||||||
ui/gl/gl_image.cpp
|
ui/gl/gl_image.cpp
|
||||||
|
|
|
||||||
202
ui/effects/spoiler_mess.cpp
Normal file
202
ui/effects/spoiler_mess.cpp
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
|
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "base/random.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kFramesPerRow = 10;
|
||||||
|
|
||||||
|
struct Particle {
|
||||||
|
crl::time start = 0;
|
||||||
|
int spriteIndex = 0;
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Particle GenerateParticle(
|
||||||
|
const SpoilerMessDescriptor &descriptor,
|
||||||
|
int index,
|
||||||
|
base::BufferedRandom<uint32> &random) {
|
||||||
|
return {
|
||||||
|
.start = (index * descriptor.framesCount * descriptor.frameDuration
|
||||||
|
/ descriptor.particlesCount),
|
||||||
|
.spriteIndex = RandomIndex(descriptor.particleSpritesCount, random),
|
||||||
|
.x = RandomIndex(descriptor.canvasSize, random),
|
||||||
|
.y = RandomIndex(descriptor.canvasSize, random),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QImage GenerateSprite(
|
||||||
|
const SpoilerMessDescriptor &descriptor,
|
||||||
|
int index,
|
||||||
|
int size,
|
||||||
|
base::BufferedRandom<uint32> &random) {
|
||||||
|
Expects(index >= 0 && index < descriptor.particleSpritesCount);
|
||||||
|
|
||||||
|
const auto count = descriptor.particleSpritesCount;
|
||||||
|
const auto middle = count / 2;
|
||||||
|
const auto min = descriptor.particleSizeMin;
|
||||||
|
const auto delta = descriptor.particleSizeMax - min;
|
||||||
|
const auto width = (index < middle)
|
||||||
|
? (min + delta * (middle - index) / float64(middle))
|
||||||
|
: min;
|
||||||
|
const auto height = (index > middle)
|
||||||
|
? (min + delta * (index - middle) / float64(count - 1 - middle))
|
||||||
|
: min;
|
||||||
|
const auto radius = min / 2.;
|
||||||
|
|
||||||
|
auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.fill(Qt::transparent);
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(Qt::white);
|
||||||
|
p.drawRoundedRect(1., 1., width, height, radius, radius);
|
||||||
|
p.end();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SpoilerMessCached GenerateSpoilerMess(
|
||||||
|
const SpoilerMessDescriptor &descriptor) {
|
||||||
|
Expects(descriptor.framesCount > 0);
|
||||||
|
Expects(descriptor.frameDuration > 0);
|
||||||
|
Expects(descriptor.particlesCount > 0);
|
||||||
|
Expects(descriptor.canvasSize > 0);
|
||||||
|
Expects(descriptor.particleSizeMax >= descriptor.particleSizeMin);
|
||||||
|
Expects(descriptor.particleSizeMin > 0.);
|
||||||
|
|
||||||
|
const auto frames = descriptor.framesCount;
|
||||||
|
const auto rows = (frames + kFramesPerRow - 1) / kFramesPerRow;
|
||||||
|
const auto columns = std::min(frames, kFramesPerRow);
|
||||||
|
const auto size = descriptor.canvasSize;
|
||||||
|
const auto count = descriptor.particlesCount;
|
||||||
|
const auto width = size * columns;
|
||||||
|
const auto height = size * rows;
|
||||||
|
const auto spriteSize = 2 + int(std::ceil(descriptor.particleSizeMax));
|
||||||
|
const auto singleDuration = descriptor.particleFadeInDuration
|
||||||
|
+ descriptor.particleShownDuration
|
||||||
|
+ descriptor.particleFadeOutDuration;
|
||||||
|
const auto fullDuration = frames * descriptor.frameDuration;
|
||||||
|
Assert(fullDuration > singleDuration);
|
||||||
|
|
||||||
|
auto random = base::BufferedRandom<uint32>(count * 3);
|
||||||
|
|
||||||
|
auto particles = std::vector<Particle>();
|
||||||
|
particles.reserve(descriptor.particlesCount);
|
||||||
|
for (auto i = 0; i != descriptor.particlesCount; ++i) {
|
||||||
|
particles.push_back(GenerateParticle(descriptor, i, random));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sprites = std::vector<QImage>();
|
||||||
|
sprites.reserve(descriptor.particleSpritesCount);
|
||||||
|
for (auto i = 0; i != descriptor.particleSpritesCount; ++i) {
|
||||||
|
sprites.push_back(GenerateSprite(descriptor, i, spriteSize, random));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto frame = 0;
|
||||||
|
auto image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
image.fill(Qt::transparent);
|
||||||
|
auto p = QPainter(&image);
|
||||||
|
const auto paintOneAt = [&](const Particle &particle, crl::time time) {
|
||||||
|
if (time <= 0 || time >= singleDuration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto opacity = (time < descriptor.particleFadeInDuration)
|
||||||
|
? (time / float64(descriptor.particleFadeInDuration))
|
||||||
|
: (time > singleDuration - descriptor.particleFadeOutDuration)
|
||||||
|
? ((singleDuration - time)
|
||||||
|
/ float64(descriptor.particleFadeOutDuration))
|
||||||
|
: 1.;
|
||||||
|
p.setOpacity(opacity);
|
||||||
|
const auto &sprite = sprites[particle.spriteIndex];
|
||||||
|
p.drawImage(particle.x, particle.y, sprite);
|
||||||
|
if (particle.x + spriteSize > size) {
|
||||||
|
p.drawImage(particle.x - size, particle.y, sprite);
|
||||||
|
if (particle.y + spriteSize > size) {
|
||||||
|
p.drawImage(particle.x, particle.y - size, sprite);
|
||||||
|
p.drawImage(particle.x - size, particle.y - size, sprite);
|
||||||
|
}
|
||||||
|
} else if (particle.y + spriteSize > size) {
|
||||||
|
p.drawImage(particle.x, particle.y - size, sprite);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto paintOne = [&](const Particle &particle, crl::time now) {
|
||||||
|
paintOneAt(particle, now - particle.start);
|
||||||
|
paintOneAt(particle, now + fullDuration - particle.start);
|
||||||
|
};
|
||||||
|
for (auto y = 0; y != rows; ++y) {
|
||||||
|
for (auto x = 0; x != columns; ++x) {
|
||||||
|
const auto rect = QRect(x * size, y * size, size, size);
|
||||||
|
p.setClipRect(rect);
|
||||||
|
p.translate(rect.topLeft());
|
||||||
|
const auto time = frame * descriptor.frameDuration;
|
||||||
|
for (auto index = 0; index != count; ++index) {
|
||||||
|
paintOne(particles[index], time);
|
||||||
|
}
|
||||||
|
p.translate(-rect.topLeft());
|
||||||
|
if (++frame >= frames) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SpoilerMessCached(
|
||||||
|
std::move(image),
|
||||||
|
frames,
|
||||||
|
descriptor.frameDuration,
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpoilerMessCached::SpoilerMessCached(
|
||||||
|
QImage image,
|
||||||
|
int framesCount,
|
||||||
|
crl::time frameDuration,
|
||||||
|
int canvasSize)
|
||||||
|
: _image(std::move(image))
|
||||||
|
, _frameDuration(frameDuration)
|
||||||
|
, _framesCount(framesCount)
|
||||||
|
, _canvasSize(canvasSize) {
|
||||||
|
Expects(_frameDuration > 0);
|
||||||
|
Expects(_framesCount > 0);
|
||||||
|
Expects(_canvasSize > 0);
|
||||||
|
Expects(_image.size() == QSize(
|
||||||
|
std::min(_framesCount, kFramesPerRow) * _canvasSize,
|
||||||
|
((_framesCount + kFramesPerRow - 1) / kFramesPerRow) * _canvasSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
SpoilerMessFrame SpoilerMessCached::frame(int index) const {
|
||||||
|
const auto row = index / kFramesPerRow;
|
||||||
|
const auto column = index - row * kFramesPerRow;
|
||||||
|
return {
|
||||||
|
.image = &_image,
|
||||||
|
.source = QRect(
|
||||||
|
column * _canvasSize,
|
||||||
|
row * _canvasSize,
|
||||||
|
_canvasSize,
|
||||||
|
_canvasSize),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SpoilerMessFrame SpoilerMessCached::frame() const {
|
||||||
|
return frame((crl::now() / _frameDuration) % _framesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray SpoilerMessCached::serialize() const {
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SpoilerMessCached> SpoilerMessCached::FromSerialized(
|
||||||
|
const QByteArray &data) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
57
ui/effects/spoiler_mess.h
Normal file
57
ui/effects/spoiler_mess.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <crl/crl_time.h>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
struct SpoilerMessDescriptor {
|
||||||
|
crl::time particleFadeInDuration = 0;
|
||||||
|
crl::time particleShownDuration = 0;
|
||||||
|
crl::time particleFadeOutDuration = 0;
|
||||||
|
float64 particleSizeMin = 0.;
|
||||||
|
float64 particleSizeMax = 0.;
|
||||||
|
int particleSpritesCount = 0;
|
||||||
|
int particlesCount = 0;
|
||||||
|
int canvasSize = 0;
|
||||||
|
int framesCount = 0;
|
||||||
|
crl::time frameDuration = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpoilerMessFrame {
|
||||||
|
not_null<const QImage*> image;
|
||||||
|
QRect source;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpoilerMessCached final {
|
||||||
|
public:
|
||||||
|
SpoilerMessCached(
|
||||||
|
QImage image,
|
||||||
|
int framesCount,
|
||||||
|
crl::time frameDuration,
|
||||||
|
int size);
|
||||||
|
|
||||||
|
[[nodiscard]] SpoilerMessFrame frame(int index) const;
|
||||||
|
[[nodiscard]] SpoilerMessFrame frame() const; // Current by time.
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray serialize() const;
|
||||||
|
[[nodiscard]] static std::optional<SpoilerMessCached> FromSerialized(
|
||||||
|
const QByteArray &data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QImage _image;
|
||||||
|
crl::time _frameDuration = 0;
|
||||||
|
int _framesCount = 0;
|
||||||
|
int _canvasSize = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] SpoilerMessCached GenerateSpoilerMess(
|
||||||
|
const SpoilerMessDescriptor &descriptor);
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
Loading…
Add table
Reference in a new issue