init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
429
Telegram/lib_ui/ui/animated_icon.cpp
Normal file
429
Telegram/lib_ui/ui/animated_icon.cpp
Normal file
@@ -0,0 +1,429 @@
|
||||
// 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/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]] double frameRate() 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;
|
||||
double _frameRate = 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();
|
||||
_frameRate = generator->rate();
|
||||
_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;
|
||||
}
|
||||
|
||||
double AnimatedIcon::Impl::frameRate() const {
|
||||
waitTillPrepared();
|
||||
return _frameRate;
|
||||
}
|
||||
|
||||
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)))
|
||||
, _colorized(descriptor.colorized) {
|
||||
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();
|
||||
}
|
||||
|
||||
double AnimatedIcon::frameRate() const {
|
||||
return _impl->frameRate();
|
||||
}
|
||||
|
||||
QImage AnimatedIcon::frame(const QColor &textColor) const {
|
||||
return frame(textColor, QSize(), nullptr).image;
|
||||
}
|
||||
|
||||
QImage AnimatedIcon::notColorizedFrame() const {
|
||||
return notColorizedFrame(QSize(), nullptr).image;
|
||||
}
|
||||
|
||||
AnimatedIcon::ResizedFrame AnimatedIcon::frame(
|
||||
const QColor &textColor,
|
||||
QSize desiredSize,
|
||||
Fn<void()> updateWithPerfect) const {
|
||||
auto result = notColorizedFrame(
|
||||
desiredSize,
|
||||
std::move(updateWithPerfect));
|
||||
if (_colorized) {
|
||||
auto &image = result.image;
|
||||
style::colorizeImage(image, textColor, &image, {}, {}, true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AnimatedIcon::ResizedFrame AnimatedIcon::notColorizedFrame(
|
||||
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 (effectiveFramesCount() != 1 && !anim::Disabled()) {
|
||||
_repaint = std::move(update);
|
||||
_animation.stop();
|
||||
_animationCurrentIndex = (_customStartFrame >= 0)
|
||||
? _customStartFrame
|
||||
: 0;
|
||||
_impl->moveToFrame(_animationCurrentIndex, QSize());
|
||||
_animationDuration = _impl->animationDuration();
|
||||
if (_customEndFrame >= 0) {
|
||||
const auto rate = frameRate();
|
||||
if (rate > 0) {
|
||||
_animationDuration = crl::time(base::SafeRound(
|
||||
effectiveFramesCount() / rate * 1000.));
|
||||
}
|
||||
}
|
||||
_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::setCustomEndFrame(int frame) {
|
||||
_customEndFrame = (frame >= 0 && frame < framesCount()) ? frame : -1;
|
||||
}
|
||||
|
||||
void AnimatedIcon::setCustomStartFrame(int frame) {
|
||||
_customStartFrame = (frame >= 0 && frame < framesCount()) ? frame : -1;
|
||||
}
|
||||
|
||||
int AnimatedIcon::effectiveFramesCount() const {
|
||||
const auto total = framesCount();
|
||||
return (_customEndFrame >= 0 && _customEndFrame < total)
|
||||
? (_customEndFrame + 1)
|
||||
: total;
|
||||
}
|
||||
|
||||
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;
|
||||
const auto effectiveCount = effectiveFramesCount();
|
||||
const auto isLastFrame = (_animationCurrentIndex >= effectiveCount - 1);
|
||||
if (isLastFrame) {
|
||||
_animation.stop();
|
||||
if (_repaint) _repaint();
|
||||
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
|
||||
Reference in New Issue
Block a user