animated_icon.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/animated_icon.h"
  8. #include "ui/image/image_prepare.h"
  9. #include "ui/style/style_core.h"
  10. #include "ui/effects/frame_generator.h"
  11. #include <QtGui/QPainter>
  12. #include <crl/crl_async.h>
  13. #include <crl/crl_semaphore.h>
  14. #include <crl/crl_on_main.h>
  15. namespace Ui {
  16. namespace {
  17. constexpr auto kDefaultDuration = crl::time(800);
  18. } // namespace
  19. struct AnimatedIcon::Frame {
  20. FrameGenerator::Frame generated;
  21. QImage resizedImage;
  22. int index = 0;
  23. };
  24. class AnimatedIcon::Impl final : public std::enable_shared_from_this<Impl> {
  25. public:
  26. explicit Impl(base::weak_ptr<AnimatedIcon> weak);
  27. void prepareFromAsync(
  28. FnMut<std::unique_ptr<FrameGenerator>()> factory,
  29. QSize sizeOverride);
  30. void waitTillPrepared() const;
  31. [[nodiscard]] bool valid() const;
  32. [[nodiscard]] QSize size() const;
  33. [[nodiscard]] int framesCount() const;
  34. [[nodiscard]] double frameRate() const;
  35. [[nodiscard]] Frame &frame();
  36. [[nodiscard]] const Frame &frame() const;
  37. [[nodiscard]] crl::time animationDuration() const;
  38. void moveToFrame(int frame, QSize updatedDesiredSize);
  39. private:
  40. enum class PreloadState {
  41. None,
  42. Preloading,
  43. Ready,
  44. };
  45. // Called from crl::async.
  46. void renderPreloadFrame();
  47. std::unique_ptr<FrameGenerator> _generator;
  48. Frame _current;
  49. QSize _desiredSize;
  50. std::atomic<PreloadState> _preloadState = PreloadState::None;
  51. Frame _preloaded; // Changed on main or async depending on _preloadState.
  52. QSize _preloadImageSize;
  53. base::weak_ptr<AnimatedIcon> _weak;
  54. int _framesCount = 0;
  55. double _frameRate = 0.;
  56. mutable crl::semaphore _semaphore;
  57. mutable bool _ready = false;
  58. };
  59. AnimatedIcon::Impl::Impl(base::weak_ptr<AnimatedIcon> weak)
  60. : _weak(weak) {
  61. }
  62. void AnimatedIcon::Impl::prepareFromAsync(
  63. FnMut<std::unique_ptr<FrameGenerator>()> factory,
  64. QSize sizeOverride) {
  65. const auto guard = gsl::finally([&] { _semaphore.release(); });
  66. if (!_weak) {
  67. return;
  68. }
  69. auto generator = factory ? factory() : nullptr;
  70. if (!generator || !_weak) {
  71. return;
  72. }
  73. _framesCount = generator->count();
  74. _frameRate = generator->rate();
  75. _current.generated = generator->renderNext(QImage(), sizeOverride);
  76. if (_current.generated.image.isNull()) {
  77. return;
  78. }
  79. _generator = std::move(generator);
  80. _desiredSize = sizeOverride.isEmpty()
  81. ? style::ConvertScale(_current.generated.image.size())
  82. : sizeOverride;
  83. }
  84. void AnimatedIcon::Impl::waitTillPrepared() const {
  85. if (!_ready) {
  86. _semaphore.acquire();
  87. _ready = true;
  88. }
  89. }
  90. bool AnimatedIcon::Impl::valid() const {
  91. waitTillPrepared();
  92. return (_generator != nullptr);
  93. }
  94. QSize AnimatedIcon::Impl::size() const {
  95. waitTillPrepared();
  96. return _desiredSize;
  97. }
  98. int AnimatedIcon::Impl::framesCount() const {
  99. waitTillPrepared();
  100. return _framesCount;
  101. }
  102. double AnimatedIcon::Impl::frameRate() const {
  103. waitTillPrepared();
  104. return _frameRate;
  105. }
  106. AnimatedIcon::Frame &AnimatedIcon::Impl::frame() {
  107. waitTillPrepared();
  108. return _current;
  109. }
  110. const AnimatedIcon::Frame &AnimatedIcon::Impl::frame() const {
  111. waitTillPrepared();
  112. return _current;
  113. }
  114. crl::time AnimatedIcon::Impl::animationDuration() const {
  115. waitTillPrepared();
  116. const auto rate = _generator ? _generator->rate() : 0.;
  117. const auto frames = _generator ? _generator->count() : 0;
  118. return (frames && rate >= 1.)
  119. ? crl::time(base::SafeRound(frames / rate * 1000.))
  120. : 0;
  121. }
  122. void AnimatedIcon::Impl::moveToFrame(int frame, QSize updatedDesiredSize) {
  123. waitTillPrepared();
  124. const auto state = _preloadState.load();
  125. const auto shown = _current.index;
  126. if (!updatedDesiredSize.isEmpty()) {
  127. _desiredSize = updatedDesiredSize;
  128. }
  129. const auto desiredImageSize = _desiredSize * style::DevicePixelRatio();
  130. if (!_generator
  131. || state == PreloadState::Preloading
  132. || (shown == frame
  133. && (_current.generated.image.size() == desiredImageSize))) {
  134. return;
  135. } else if (state == PreloadState::Ready) {
  136. if (_preloaded.index == frame
  137. && (shown != frame
  138. || _preloaded.generated.image.size() == desiredImageSize)) {
  139. std::swap(_current, _preloaded);
  140. if (_current.generated.image.size() == desiredImageSize) {
  141. return;
  142. }
  143. } else if ((shown < _preloaded.index && _preloaded.index < frame)
  144. || (shown > _preloaded.index && _preloaded.index > frame)) {
  145. std::swap(_current, _preloaded);
  146. }
  147. }
  148. _preloadImageSize = desiredImageSize;
  149. _preloaded.index = frame;
  150. _preloadState = PreloadState::Preloading;
  151. crl::async([guard = shared_from_this()] {
  152. guard->renderPreloadFrame();
  153. });
  154. }
  155. void AnimatedIcon::Impl::renderPreloadFrame() {
  156. if (!_weak) {
  157. return;
  158. }
  159. if (_preloaded.index == 0) {
  160. _generator->jumpToStart();
  161. }
  162. _preloaded.generated = (_preloaded.index && _preloaded.index == _current.index)
  163. ? _generator->renderCurrent(
  164. std::move(_preloaded.generated.image),
  165. _preloadImageSize)
  166. : _generator->renderNext(
  167. std::move(_preloaded.generated.image),
  168. _preloadImageSize);
  169. _preloaded.resizedImage = QImage();
  170. _preloadState = PreloadState::Ready;
  171. crl::on_main(_weak, [=] {
  172. _weak->frameJumpFinished();
  173. });
  174. }
  175. AnimatedIcon::AnimatedIcon(AnimatedIconDescriptor &&descriptor)
  176. : _impl(std::make_shared<Impl>(base::make_weak(this)))
  177. , _colorized(descriptor.colorized) {
  178. crl::async([
  179. impl = _impl,
  180. factory = std::move(descriptor.generator),
  181. sizeOverride = descriptor.sizeOverride
  182. ]() mutable {
  183. impl->prepareFromAsync(std::move(factory), sizeOverride);
  184. });
  185. }
  186. void AnimatedIcon::wait() const {
  187. _impl->waitTillPrepared();
  188. }
  189. bool AnimatedIcon::valid() const {
  190. return _impl->valid();
  191. }
  192. int AnimatedIcon::frameIndex() const {
  193. return _impl->frame().index;
  194. }
  195. int AnimatedIcon::framesCount() const {
  196. return _impl->framesCount();
  197. }
  198. double AnimatedIcon::frameRate() const {
  199. return _impl->frameRate();
  200. }
  201. QImage AnimatedIcon::frame(const QColor &textColor) const {
  202. return frame(textColor, QSize(), nullptr).image;
  203. }
  204. QImage AnimatedIcon::notColorizedFrame() const {
  205. return notColorizedFrame(QSize(), nullptr).image;
  206. }
  207. AnimatedIcon::ResizedFrame AnimatedIcon::frame(
  208. const QColor &textColor,
  209. QSize desiredSize,
  210. Fn<void()> updateWithPerfect) const {
  211. auto result = notColorizedFrame(
  212. desiredSize,
  213. std::move(updateWithPerfect));
  214. if (_colorized) {
  215. auto &image = result.image;
  216. style::colorizeImage(image, textColor, &image, {}, {}, true);
  217. }
  218. return result;
  219. }
  220. AnimatedIcon::ResizedFrame AnimatedIcon::notColorizedFrame(
  221. QSize desiredSize,
  222. Fn<void()> updateWithPerfect) const {
  223. auto &frame = _impl->frame();
  224. preloadNextFrame(crl::now(), &frame, desiredSize);
  225. const auto desired = size() * style::DevicePixelRatio();
  226. if (frame.generated.image.isNull()) {
  227. return { frame.generated.image };
  228. } else if (frame.generated.image.size() == desired) {
  229. return { frame.generated.image };
  230. } else if (frame.resizedImage.size() != desired) {
  231. frame.resizedImage = frame.generated.image.scaled(
  232. desired,
  233. Qt::IgnoreAspectRatio,
  234. Qt::SmoothTransformation);
  235. }
  236. if (updateWithPerfect) {
  237. _repaint = std::move(updateWithPerfect);
  238. }
  239. return { frame.resizedImage, true };
  240. }
  241. int AnimatedIcon::width() const {
  242. return size().width();
  243. }
  244. int AnimatedIcon::height() const {
  245. return size().height();
  246. }
  247. QSize AnimatedIcon::size() const {
  248. return _impl->size();
  249. }
  250. void AnimatedIcon::paint(QPainter &p, int x, int y) {
  251. auto &frame = _impl->frame();
  252. preloadNextFrame(crl::now(), &frame);
  253. if (frame.generated.image.isNull()) {
  254. return;
  255. }
  256. const auto rect = QRect{ QPoint(x, y), size() };
  257. p.drawImage(rect, frame.generated.image);
  258. }
  259. void AnimatedIcon::paintInCenter(QPainter &p, QRect rect) {
  260. const auto my = size();
  261. paint(
  262. p,
  263. rect.x() + (rect.width() - my.width()) / 2,
  264. rect.y() + (rect.height() - my.height()) / 2);
  265. }
  266. void AnimatedIcon::animate(Fn<void()> update) {
  267. if (framesCount() != 1 && !anim::Disabled()) {
  268. jumpToStart(std::move(update));
  269. _animationDuration = _impl->animationDuration();
  270. _animationCurrentStart = _animationStarted = crl::now();
  271. continueAnimation(_animationCurrentStart);
  272. }
  273. }
  274. void AnimatedIcon::continueAnimation(crl::time now) {
  275. const auto callback = [=](float64 value) {
  276. if (anim::Disabled()) {
  277. return;
  278. }
  279. const auto elapsed = int(value);
  280. const auto now = _animationStartTime + elapsed;
  281. if (!_animationDuration && elapsed > kDefaultDuration / 2) {
  282. auto animation = std::move(_animation);
  283. continueAnimation(now);
  284. }
  285. preloadNextFrame(now);
  286. if (_repaint) _repaint();
  287. };
  288. const auto duration = _animationDuration
  289. ? _animationDuration
  290. : kDefaultDuration;
  291. _animationStartTime = now;
  292. _animation.start(callback, 0., 1. * duration, duration);
  293. }
  294. void AnimatedIcon::jumpToStart(Fn<void()> update) {
  295. _repaint = std::move(update);
  296. _animation.stop();
  297. _animationCurrentIndex = 0;
  298. _impl->moveToFrame(0, QSize());
  299. }
  300. void AnimatedIcon::frameJumpFinished() {
  301. if (_repaint && !animating()) {
  302. _repaint();
  303. _repaint = nullptr;
  304. }
  305. }
  306. int AnimatedIcon::wantedFrameIndex(
  307. crl::time now,
  308. const Frame *resolvedCurrent) const {
  309. const auto frame = resolvedCurrent ? resolvedCurrent : &_impl->frame();
  310. if (frame->index == _animationCurrentIndex + 1) {
  311. ++_animationCurrentIndex;
  312. _animationCurrentStart = _animationNextStart;
  313. }
  314. if (!_animation.animating()) {
  315. return _animationCurrentIndex;
  316. }
  317. if (frame->index == _animationCurrentIndex) {
  318. const auto duration = frame->generated.duration;
  319. const auto next = _animationCurrentStart + duration;
  320. if (frame->generated.last) {
  321. _animation.stop();
  322. if (_repaint) _repaint();
  323. return _animationCurrentIndex;
  324. } else if (now < next) {
  325. return _animationCurrentIndex;
  326. }
  327. _animationNextStart = next;
  328. return _animationCurrentIndex + 1;
  329. }
  330. Assert(!_animationCurrentIndex);
  331. return 0;
  332. }
  333. void AnimatedIcon::preloadNextFrame(
  334. crl::time now,
  335. const Frame *resolvedCurrent,
  336. QSize updatedDesiredSize) const {
  337. _impl->moveToFrame(
  338. wantedFrameIndex(now, resolvedCurrent),
  339. updatedDesiredSize);
  340. }
  341. bool AnimatedIcon::animating() const {
  342. return _animation.animating();
  343. }
  344. std::unique_ptr<AnimatedIcon> MakeAnimatedIcon(
  345. AnimatedIconDescriptor &&descriptor) {
  346. return std::make_unique<AnimatedIcon>(std::move(descriptor));
  347. }
  348. } // namespace Lottie