From 00a668c3f3471151791bf5b46decd61ff4eaa96e Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 8 Sep 2022 13:13:03 +0400 Subject: [PATCH] Initial SpoilerMess implementation. --- CMakeLists.txt | 2 + ui/effects/spoiler_mess.cpp | 202 ++++++++++++++++++++++++++++++++++++ ui/effects/spoiler_mess.h | 57 ++++++++++ 3 files changed, 261 insertions(+) create mode 100644 ui/effects/spoiler_mess.cpp create mode 100644 ui/effects/spoiler_mess.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4190fa6..6f237f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,8 @@ PRIVATE ui/effects/show_animation.h ui/effects/slide_animation.cpp ui/effects/slide_animation.h + ui/effects/spoiler_mess.cpp + ui/effects/spoiler_mess.h ui/gl/gl_detection.cpp ui/gl/gl_detection.h ui/gl/gl_image.cpp diff --git a/ui/effects/spoiler_mess.cpp b/ui/effects/spoiler_mess.cpp new file mode 100644 index 0000000..6f191c1 --- /dev/null +++ b/ui/effects/spoiler_mess.cpp @@ -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 &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 &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(count * 3); + + auto particles = std::vector(); + particles.reserve(descriptor.particlesCount); + for (auto i = 0; i != descriptor.particlesCount; ++i) { + particles.push_back(GenerateParticle(descriptor, i, random)); + } + + auto sprites = std::vector(); + 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::FromSerialized( + const QByteArray &data) { + return std::nullopt; +} + +} // namespace Ui diff --git a/ui/effects/spoiler_mess.h b/ui/effects/spoiler_mess.h new file mode 100644 index 0000000..380ac84 --- /dev/null +++ b/ui/effects/spoiler_mess.h @@ -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 + +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 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 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