| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- // 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 (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();
- 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
|