369 lines
9.2 KiB
C++
369 lines
9.2 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/animated_icon.h"
|
|
|
|
#include "ui/image/image_prepare.h"
|
|
#include "ui/style/style_core.h"
|
|
#include "ui/effects/frame_generator.h"
|
|
|
|
#include <QtGui/QPainter>
|
|
#include <crl/crl_async.h>
|
|
#include <crl/crl_semaphore.h>
|
|
#include <crl/crl_on_main.h>
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
constexpr auto kDefaultDuration = crl::time(800);
|
|
|
|
} // namespace
|
|
|
|
struct AnimatedIcon::Frame {
|
|
FrameGenerator::Frame generated;
|
|
QImage resizedImage;
|
|
int index = 0;
|
|
};
|
|
|
|
class AnimatedIcon::Impl final : public std::enable_shared_from_this<Impl> {
|
|
public:
|
|
explicit Impl(base::weak_ptr<AnimatedIcon> weak);
|
|
|
|
void prepareFromAsync(
|
|
FnMut<std::unique_ptr<FrameGenerator>()> factory,
|
|
QSize sizeOverride);
|
|
void waitTillPrepared() const;
|
|
|
|
[[nodiscard]] bool valid() const;
|
|
[[nodiscard]] QSize size() const;
|
|
[[nodiscard]] int framesCount() const;
|
|
[[nodiscard]] Frame &frame();
|
|
[[nodiscard]] const Frame &frame() const;
|
|
|
|
[[nodiscard]] crl::time animationDuration() const;
|
|
void moveToFrame(int frame, QSize updatedDesiredSize);
|
|
|
|
private:
|
|
enum class PreloadState {
|
|
None,
|
|
Preloading,
|
|
Ready,
|
|
};
|
|
|
|
// Called from crl::async.
|
|
void renderPreloadFrame();
|
|
|
|
std::unique_ptr<FrameGenerator> _generator;
|
|
Frame _current;
|
|
QSize _desiredSize;
|
|
std::atomic<PreloadState> _preloadState = PreloadState::None;
|
|
|
|
Frame _preloaded; // Changed on main or async depending on _preloadState.
|
|
QSize _preloadImageSize;
|
|
|
|
base::weak_ptr<AnimatedIcon> _weak;
|
|
int _framesCount = 0;
|
|
mutable crl::semaphore _semaphore;
|
|
mutable bool _ready = false;
|
|
|
|
};
|
|
|
|
AnimatedIcon::Impl::Impl(base::weak_ptr<AnimatedIcon> weak)
|
|
: _weak(weak) {
|
|
}
|
|
|
|
void AnimatedIcon::Impl::prepareFromAsync(
|
|
FnMut<std::unique_ptr<FrameGenerator>()> factory,
|
|
QSize sizeOverride) {
|
|
const auto guard = gsl::finally([&] { _semaphore.release(); });
|
|
if (!_weak) {
|
|
return;
|
|
}
|
|
auto generator = factory ? factory() : nullptr;
|
|
if (!generator || !_weak) {
|
|
return;
|
|
}
|
|
_framesCount = generator->count();
|
|
_current.generated = generator->renderNext(QImage(), sizeOverride);
|
|
if (_current.generated.image.isNull()) {
|
|
return;
|
|
}
|
|
_generator = std::move(generator);
|
|
_desiredSize = sizeOverride.isEmpty()
|
|
? style::ConvertScale(_current.generated.image.size())
|
|
: sizeOverride;
|
|
}
|
|
|
|
void AnimatedIcon::Impl::waitTillPrepared() const {
|
|
if (!_ready) {
|
|
_semaphore.acquire();
|
|
_ready = true;
|
|
}
|
|
}
|
|
|
|
bool AnimatedIcon::Impl::valid() const {
|
|
waitTillPrepared();
|
|
return (_generator != nullptr);
|
|
}
|
|
|
|
QSize AnimatedIcon::Impl::size() const {
|
|
waitTillPrepared();
|
|
return _desiredSize;
|
|
}
|
|
|
|
int AnimatedIcon::Impl::framesCount() const {
|
|
waitTillPrepared();
|
|
return _framesCount;
|
|
}
|
|
|
|
AnimatedIcon::Frame &AnimatedIcon::Impl::frame() {
|
|
waitTillPrepared();
|
|
return _current;
|
|
}
|
|
|
|
const AnimatedIcon::Frame &AnimatedIcon::Impl::frame() const {
|
|
waitTillPrepared();
|
|
return _current;
|
|
}
|
|
|
|
crl::time AnimatedIcon::Impl::animationDuration() const {
|
|
waitTillPrepared();
|
|
const auto rate = _generator ? _generator->rate() : 0.;
|
|
const auto frames = _generator ? _generator->count() : 0;
|
|
return (frames && rate >= 1.)
|
|
? crl::time(base::SafeRound(frames / rate * 1000.))
|
|
: 0;
|
|
}
|
|
|
|
void AnimatedIcon::Impl::moveToFrame(int frame, QSize updatedDesiredSize) {
|
|
waitTillPrepared();
|
|
const auto state = _preloadState.load();
|
|
const auto shown = _current.index;
|
|
if (!updatedDesiredSize.isEmpty()) {
|
|
_desiredSize = updatedDesiredSize;
|
|
}
|
|
const auto desiredImageSize = _desiredSize * style::DevicePixelRatio();
|
|
if (!_generator
|
|
|| state == PreloadState::Preloading
|
|
|| (shown == frame
|
|
&& (_current.generated.image.size() == desiredImageSize))) {
|
|
return;
|
|
} else if (state == PreloadState::Ready) {
|
|
if (_preloaded.index == frame
|
|
&& (shown != frame
|
|
|| _preloaded.generated.image.size() == desiredImageSize)) {
|
|
std::swap(_current, _preloaded);
|
|
if (_current.generated.image.size() == desiredImageSize) {
|
|
return;
|
|
}
|
|
} else if ((shown < _preloaded.index && _preloaded.index < frame)
|
|
|| (shown > _preloaded.index && _preloaded.index > frame)) {
|
|
std::swap(_current, _preloaded);
|
|
}
|
|
}
|
|
_preloadImageSize = desiredImageSize;
|
|
_preloaded.index = frame;
|
|
_preloadState = PreloadState::Preloading;
|
|
crl::async([guard = shared_from_this()] {
|
|
guard->renderPreloadFrame();
|
|
});
|
|
}
|
|
|
|
void AnimatedIcon::Impl::renderPreloadFrame() {
|
|
if (!_weak) {
|
|
return;
|
|
}
|
|
if (_preloaded.index == 0) {
|
|
_generator->jumpToStart();
|
|
}
|
|
_preloaded.generated = (_preloaded.index && _preloaded.index == _current.index)
|
|
? _generator->renderCurrent(
|
|
std::move(_preloaded.generated.image),
|
|
_preloadImageSize)
|
|
: _generator->renderNext(
|
|
std::move(_preloaded.generated.image),
|
|
_preloadImageSize);
|
|
_preloaded.resizedImage = QImage();
|
|
_preloadState = PreloadState::Ready;
|
|
crl::on_main(_weak, [=] {
|
|
_weak->frameJumpFinished();
|
|
});
|
|
}
|
|
|
|
AnimatedIcon::AnimatedIcon(AnimatedIconDescriptor &&descriptor)
|
|
: _impl(std::make_shared<Impl>(base::make_weak(this))) {
|
|
crl::async([
|
|
impl = _impl,
|
|
factory = std::move(descriptor.generator),
|
|
sizeOverride = descriptor.sizeOverride
|
|
]() mutable {
|
|
impl->prepareFromAsync(std::move(factory), sizeOverride);
|
|
});
|
|
}
|
|
|
|
void AnimatedIcon::wait() const {
|
|
_impl->waitTillPrepared();
|
|
}
|
|
|
|
bool AnimatedIcon::valid() const {
|
|
return _impl->valid();
|
|
}
|
|
|
|
int AnimatedIcon::frameIndex() const {
|
|
return _impl->frame().index;
|
|
}
|
|
|
|
int AnimatedIcon::framesCount() const {
|
|
return _impl->framesCount();
|
|
}
|
|
|
|
QImage AnimatedIcon::frame() const {
|
|
return frame(QSize(), nullptr).image;
|
|
}
|
|
|
|
AnimatedIcon::ResizedFrame AnimatedIcon::frame(
|
|
QSize desiredSize,
|
|
Fn<void()> updateWithPerfect) const {
|
|
auto &frame = _impl->frame();
|
|
preloadNextFrame(crl::now(), &frame, desiredSize);
|
|
const auto desired = size() * style::DevicePixelRatio();
|
|
if (frame.generated.image.isNull()) {
|
|
return { frame.generated.image };
|
|
} else if (frame.generated.image.size() == desired) {
|
|
return { frame.generated.image };
|
|
} else if (frame.resizedImage.size() != desired) {
|
|
frame.resizedImage = frame.generated.image.scaled(
|
|
desired,
|
|
Qt::IgnoreAspectRatio,
|
|
Qt::SmoothTransformation);
|
|
}
|
|
if (updateWithPerfect) {
|
|
_repaint = std::move(updateWithPerfect);
|
|
}
|
|
return { frame.resizedImage, true };
|
|
}
|
|
|
|
int AnimatedIcon::width() const {
|
|
return size().width();
|
|
}
|
|
|
|
int AnimatedIcon::height() const {
|
|
return size().height();
|
|
}
|
|
|
|
QSize AnimatedIcon::size() const {
|
|
return _impl->size();
|
|
}
|
|
|
|
void AnimatedIcon::paint(QPainter &p, int x, int y) {
|
|
auto &frame = _impl->frame();
|
|
preloadNextFrame(crl::now(), &frame);
|
|
if (frame.generated.image.isNull()) {
|
|
return;
|
|
}
|
|
const auto rect = QRect{ QPoint(x, y), size() };
|
|
p.drawImage(rect, frame.generated.image);
|
|
}
|
|
|
|
void AnimatedIcon::paintInCenter(QPainter &p, QRect rect) {
|
|
const auto my = size();
|
|
paint(
|
|
p,
|
|
rect.x() + (rect.width() - my.width()) / 2,
|
|
rect.y() + (rect.height() - my.height()) / 2);
|
|
}
|
|
|
|
void AnimatedIcon::animate(Fn<void()> update) {
|
|
if (framesCount() != 1 && !anim::Disabled()) {
|
|
jumpToStart(std::move(update));
|
|
_animationDuration = _impl->animationDuration();
|
|
_animationCurrentStart = _animationStarted = crl::now();
|
|
continueAnimation(_animationCurrentStart);
|
|
}
|
|
}
|
|
|
|
void AnimatedIcon::continueAnimation(crl::time now) {
|
|
const auto callback = [=](float64 value) {
|
|
if (anim::Disabled()) {
|
|
return;
|
|
}
|
|
const auto elapsed = int(value);
|
|
const auto now = _animationStartTime + elapsed;
|
|
if (!_animationDuration && elapsed > kDefaultDuration / 2) {
|
|
auto animation = std::move(_animation);
|
|
continueAnimation(now);
|
|
}
|
|
preloadNextFrame(now);
|
|
if (_repaint) _repaint();
|
|
};
|
|
const auto duration = _animationDuration
|
|
? _animationDuration
|
|
: kDefaultDuration;
|
|
_animationStartTime = now;
|
|
_animation.start(callback, 0., 1. * duration, duration);
|
|
}
|
|
|
|
void AnimatedIcon::jumpToStart(Fn<void()> update) {
|
|
_repaint = std::move(update);
|
|
_animation.stop();
|
|
_animationCurrentIndex = 0;
|
|
_impl->moveToFrame(0, QSize());
|
|
}
|
|
|
|
void AnimatedIcon::frameJumpFinished() {
|
|
if (_repaint && !animating()) {
|
|
_repaint();
|
|
_repaint = nullptr;
|
|
}
|
|
}
|
|
|
|
int AnimatedIcon::wantedFrameIndex(
|
|
crl::time now,
|
|
const Frame *resolvedCurrent) const {
|
|
const auto frame = resolvedCurrent ? resolvedCurrent : &_impl->frame();
|
|
if (frame->index == _animationCurrentIndex + 1) {
|
|
++_animationCurrentIndex;
|
|
_animationCurrentStart = _animationNextStart;
|
|
}
|
|
if (!_animation.animating()) {
|
|
return _animationCurrentIndex;
|
|
}
|
|
if (frame->index == _animationCurrentIndex) {
|
|
const auto duration = frame->generated.duration;
|
|
const auto next = _animationCurrentStart + duration;
|
|
if (frame->generated.last) {
|
|
_animation.stop();
|
|
return _animationCurrentIndex;
|
|
} else if (now < next) {
|
|
return _animationCurrentIndex;
|
|
}
|
|
_animationNextStart = next;
|
|
return _animationCurrentIndex + 1;
|
|
}
|
|
Assert(!_animationCurrentIndex);
|
|
return 0;
|
|
}
|
|
|
|
void AnimatedIcon::preloadNextFrame(
|
|
crl::time now,
|
|
const Frame *resolvedCurrent,
|
|
QSize updatedDesiredSize) const {
|
|
_impl->moveToFrame(
|
|
wantedFrameIndex(now, resolvedCurrent),
|
|
updatedDesiredSize);
|
|
}
|
|
|
|
bool AnimatedIcon::animating() const {
|
|
return _animation.animating();
|
|
}
|
|
|
|
std::unique_ptr<AnimatedIcon> MakeAnimatedIcon(
|
|
AnimatedIconDescriptor &&descriptor) {
|
|
return std::make_unique<AnimatedIcon>(std::move(descriptor));
|
|
}
|
|
|
|
} // namespace Lottie
|