Add 'Loop animated stickers' setting.

This commit is contained in:
John Preston 2019-08-01 12:42:24 +01:00
parent abf49e1672
commit 708b1d7ad4
31 changed files with 285 additions and 105 deletions

View file

@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_performance" = "Performance"; "lng_settings_performance" = "Performance";
"lng_settings_enable_animations" = "Enable animations"; "lng_settings_enable_animations" = "Enable animations";
"lng_settings_autoplay_gifs" = "Autoplay GIFs"; "lng_settings_autoplay_gifs" = "Autoplay GIFs";
"lng_settings_loop_stickers" = "Loop animated stickers";
"lng_backgrounds_header" = "Choose your new chat background"; "lng_backgrounds_header" = "Choose your new chat background";
"lng_theme_sure_keep" = "Keep this theme?"; "lng_theme_sure_keep" = "Keep this theme?";

View file

@ -548,6 +548,17 @@ bool InnerWidget::elementIntersectsRange(
return (top < till && bottom > from); return (top < till && bottom > from);
} }
bool InnerWidget::elementStartStickerLoop(not_null<const Element*> view) {
if (_controller->session().settings().loopAnimatedStickers()) {
return true;
}
return !_animatedStickersPlayed.contains(view->data()->fullId());
}
void InnerWidget::elementStickerLoopStarted(not_null<const Element*> view) {
_animatedStickersPlayed.emplace(view->data()->fullId());
}
void InnerWidget::saveState(not_null<SectionMemento*> memento) { void InnerWidget::saveState(not_null<SectionMemento*> memento) {
memento->setFilter(std::move(_filter)); memento->setFilter(std::move(_filter));
memento->setAdmins(std::move(_admins)); memento->setAdmins(std::move(_admins));
@ -1024,15 +1035,15 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
cancelContextDownload(document); cancelContextDownload(document);
}); });
} else { } else {
if (document->loaded() && document->isGifv()) { if (document->loaded()
if (!cAutoPlayGif()) { && document->isGifv()
const auto itemId = view && !document->session().settings().autoplayGifs()) {
? view->data()->fullId() const auto itemId = view
: FullMsgId(); ? view->data()->fullId()
_menu->addAction(tr::lng_context_open_gif(tr::now), [=] { : FullMsgId();
openContextGif(itemId); _menu->addAction(tr::lng_context_open_gif(tr::now), [=] {
}); openContextGif(itemId);
} });
} }
if (!document->filepath(DocumentData::FilePathResolve::Checked).isEmpty()) { if (!document->filepath(DocumentData::FilePathResolve::Checked).isEmpty()) {
_menu->addAction(Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), [=] { _menu->addAction(Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), [=] {

View file

@ -99,6 +99,10 @@ public:
not_null<const HistoryView::Element*> view, not_null<const HistoryView::Element*> view,
int from, int from,
int till) override; int till) override;
bool elementStartStickerLoop(
not_null<const HistoryView::Element*> view) override;
void elementStickerLoopStarted(
not_null<const HistoryView::Element*> view) override;
~InnerWidget(); ~InnerWidget();
@ -219,6 +223,7 @@ private:
std::vector<OwnedItem> _items; std::vector<OwnedItem> _items;
std::set<uint64> _eventIds; std::set<uint64> _eventIds;
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData; std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
base::flat_set<FullMsgId> _animatedStickersPlayed;
int _itemsTop = 0; int _itemsTop = 0;
int _itemsWidth = 0; int _itemsWidth = 0;
int _itemsHeight = 0; int _itemsHeight = 0;

View file

@ -1555,7 +1555,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto lnkIsVoice = document->isVoiceMessage(); const auto lnkIsVoice = document->isVoiceMessage();
const auto lnkIsAudio = document->isAudioFile(); const auto lnkIsAudio = document->isAudioFile();
if (document->loaded() && document->isGifv()) { if (document->loaded() && document->isGifv()) {
if (!cAutoPlayGif()) { if (!document->session().settings().autoplayGifs()) {
_menu->addAction(tr::lng_context_open_gif(tr::now), [=] { _menu->addAction(tr::lng_context_open_gif(tr::now), [=] {
openContextGif(itemId); openContextGif(itemId);
}); });
@ -2384,6 +2384,18 @@ bool HistoryInner::elementIntersectsRange(
return (top < till && bottom > from); return (top < till && bottom > from);
} }
bool HistoryInner::elementStartStickerLoop(
not_null<const Element*> view) const {
return _controller->session().settings().loopAnimatedStickers()
|| !_animatedStickersPlayed.contains(view->data()->fullId());
}
void HistoryInner::elementStickerLoopStarted(not_null<const Element*> view) {
if (!_controller->session().settings().loopAnimatedStickers()) {
_animatedStickersPlayed.emplace(view->data()->fullId());
}
}
auto HistoryInner::getSelectionState() const auto HistoryInner::getSelectionState() const
-> HistoryView::TopBarWidget::SelectedState { -> HistoryView::TopBarWidget::SelectedState {
auto result = HistoryView::TopBarWidget::SelectedState {}; auto result = HistoryView::TopBarWidget::SelectedState {};
@ -3234,6 +3246,18 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
? Instance->elementIntersectsRange(view, from, till) ? Instance->elementIntersectsRange(view, from, till)
: false; : false;
} }
bool elementStartStickerLoop(
not_null<const Element*> view) override {
return Instance
? Instance->elementStartStickerLoop(view)
: true;
}
void elementStickerLoopStarted(
not_null<const Element*> view) override {
if (Instance) {
Instance->elementStickerLoopStarted(view);
}
}
}; };

View file

@ -79,6 +79,8 @@ public:
not_null<const Element*> view, not_null<const Element*> view,
int from, int from,
int till) const; int till) const;
bool elementStartStickerLoop(not_null<const Element*> view) const;
void elementStickerLoopStarted(not_null<const Element*> view);
void updateBotInfo(bool recount = true); void updateBotInfo(bool recount = true);
@ -330,6 +332,8 @@ private:
style::cursor _cursor = style::cur_default; style::cursor _cursor = style::cur_default;
SelectedItems _selected; SelectedItems _selected;
base::flat_set<FullMsgId> _animatedStickersPlayed;
MouseAction _mouseAction = MouseAction::None; MouseAction _mouseAction = MouseAction::None;
TextSelectType _mouseSelectType = TextSelectType::Letters; TextSelectType _mouseSelectType = TextSelectType::Letters;
QPoint _dragStartPosition; QPoint _dragStartPosition;

View file

@ -1639,7 +1639,7 @@ void HistoryWidget::showHistory(
cancelTypingAction(); cancelTypingAction();
} }
if (!cAutoPlayGif()) { if (!session().settings().autoplayGifs()) {
session().data().stopAutoplayAnimations(); session().data().stopAutoplayAnimations();
} }
clearReplyReturns(); clearReplyReturns();

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "layout.h" #include "layout.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "main/main_session.h"
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
@ -228,6 +229,10 @@ QSize HistoryGif::videoSize() const {
} }
} }
bool HistoryGif::autoplayEnabled() const {
return history()->session().settings().autoplayGifs();
}
void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const { void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
@ -238,7 +243,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, crl::
auto selected = (selection == FullSelection); auto selected = (selection == FullSelection);
if (loaded if (loaded
&& cAutoPlayGif() && autoplayEnabled()
&& !_gif && !_gif
&& !_gif.isBad() && !_gif.isBad()
&& !activeRoundPlayer()) { && !activeRoundPlayer()) {
@ -370,7 +375,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, crl::
App::complexOverlayRect(p, rthumb, roundRadius, roundCorners); App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
} }
if (radial || (!reader && !player && (_gif.isBad() || (!loaded && !_data->loading()) || !cAutoPlayGif()))) { if (radial || (!reader && !player && (_gif.isBad() || (!loaded && !_data->loading()) || !autoplayEnabled()))) {
auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.; auto radialOpacity = (radial && loaded && item->id > 0) ? _animation->radial.opacity() : 1.;
auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
@ -852,7 +857,7 @@ void HistoryGif::playAnimation(bool autoplay) {
return; return;
} else if (_gif && autoplay) { } else if (_gif && autoplay) {
return; return;
} else if (_gif && cAutoPlayGif()) { } else if (_gif && autoplayEnabled()) {
Core::App().showDocument(_data, _parent->data()); Core::App().showDocument(_data, _parent->data());
return; return;
} }
@ -860,7 +865,7 @@ void HistoryGif::playAnimation(bool autoplay) {
if (_gif) { if (_gif) {
stopAnimation(); stopAnimation();
} else if (_data->loaded(DocumentData::FilePathResolve::Checked)) { } else if (_data->loaded(DocumentData::FilePathResolve::Checked)) {
if (!cAutoPlayGif()) { if (!autoplayEnabled()) {
history()->owner().stopAutoplayAnimations(); history()->owner().stopAutoplayAnimations();
} }
setClipReader(Media::Clip::MakeReader( setClipReader(Media::Clip::MakeReader(

View file

@ -85,6 +85,7 @@ protected:
} }
private: private:
[[nodiscard]] bool autoplayEnabled() const;
void playAnimation(bool autoplay) override; void playAnimation(bool autoplay) override;
QSize countOptimalSize() override; QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override; QSize countCurrentSize(int newWidth) override;

View file

@ -122,7 +122,11 @@ void HistorySticker::unloadLottie() {
_parent->data()->history()->owner().unregisterHeavyViewPart(_parent); _parent->data()->history()->owner().unregisterHeavyViewPart(_parent);
} }
void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const { void HistorySticker::draw(
Painter &p,
const QRect &r,
TextSelection selection,
crl::time ms) const {
auto sticker = _data->sticker(); auto sticker = _data->sticker();
if (!sticker) return; if (!sticker) return;
@ -197,19 +201,24 @@ void HistorySticker::draw(Painter &p, const QRect &r, TextSelection selection, c
if (selected) { if (selected) {
request.colored = st::msgStickerOverlay->c; request.colored = st::msgStickerOverlay->c;
} }
const auto paused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any); const auto frame = _lottie->frameInfo(request);
if (!paused) { const auto size = frame.image.size() / cIntRetinaFactor();
_lottie->markFrameShown();
}
const auto frame = _lottie->frame(request);
const auto size = frame.size() / cIntRetinaFactor();
p.drawImage( p.drawImage(
QRect( QRect(
QPoint( QPoint(
usex + (usew - size.width()) / 2, usex + (usew - size.width()) / 2,
(minHeight() - size.height()) / 2), (minHeight() - size.height()) / 2),
size), size),
frame); frame.image);
const auto paused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
if (!paused
&& (frame.index != 0
|| _parent->delegate()->elementStartStickerLoop(_parent))
&& _lottie->markFrameShown()
&& !frame.index) {
_parent->delegate()->elementStickerLoopStarted(_parent);
}
} }
if (!inWebPage) { if (!inWebPage) {
auto fullRight = usex + usew; auto fullRight = usex + usew;

View file

@ -162,12 +162,12 @@ void AddDocumentActions(
}); });
return; return;
} }
if (document->loaded() && document->isGifv()) { if (document->loaded()
if (!cAutoPlayGif()) { && document->isGifv()
menu->addAction(tr::lng_context_open_gif(tr::now), [=] { && !document->session().settings().autoplayGifs()) {
OpenGif(contextId); menu->addAction(tr::lng_context_open_gif(tr::now), [=] {
}); OpenGif(contextId);
} });
} }
if (document->sticker() if (document->sticker()
&& document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) {

View file

@ -84,6 +84,15 @@ bool SimpleElementDelegate::elementIntersectsRange(
return true; return true;
} }
bool SimpleElementDelegate::elementStartStickerLoop(
not_null<const Element*> view) {
return true;
}
void SimpleElementDelegate::elementStickerLoopStarted(
not_null<const Element*> view) {
}
TextSelection UnshiftItemSelection( TextSelection UnshiftItemSelection(
TextSelection selection, TextSelection selection,
uint16 byLength) { uint16 byLength) {

View file

@ -50,6 +50,9 @@ public:
not_null<const Element*> view, not_null<const Element*> view,
int from, int from,
int till) = 0; int till) = 0;
virtual bool elementStartStickerLoop(not_null<const Element*> view) = 0;
virtual void elementStickerLoopStarted(
not_null<const Element*> view) = 0;
}; };
@ -69,6 +72,10 @@ public:
not_null<const Element*> view, not_null<const Element*> view,
int from, int from,
int till) override; int till) override;
bool elementStartStickerLoop(
not_null<const Element*> view) override;
void elementStickerLoopStarted(not_null<const Element*> view) override;
}; };
TextSelection UnshiftItemSelection( TextSelection UnshiftItemSelection(

View file

@ -1141,6 +1141,17 @@ bool ListWidget::elementIntersectsRange(
return (top < till && bottom > from); return (top < till && bottom > from);
} }
bool ListWidget::elementStartStickerLoop(not_null<const Element*> view) {
return _controller->session().settings().loopAnimatedStickers()
|| !_animatedStickersPlayed.contains(view->data()->fullId());
}
void ListWidget::elementStickerLoopStarted(not_null<const Element*> view) {
if (!_controller->session().settings().loopAnimatedStickers()) {
_animatedStickersPlayed.emplace(view->data()->fullId());
}
}
void ListWidget::saveState(not_null<ListMemento*> memento) { void ListWidget::saveState(not_null<ListMemento*> memento) {
memento->setAroundPosition(_aroundPosition); memento->setAroundPosition(_aroundPosition);
auto state = countScrollState(); auto state = countScrollState();

View file

@ -184,12 +184,15 @@ public:
bool elementUnderCursor(not_null<const Element*> view) override; bool elementUnderCursor(not_null<const Element*> view) override;
void elementAnimationAutoplayAsync( void elementAnimationAutoplayAsync(
not_null<const Element*> view) override; not_null<const Element*> view) override;
crl::time elementHighlightTime(not_null<const Element*> element) override; crl::time elementHighlightTime(
not_null<const Element*> element) override;
bool elementInSelectionMode() override; bool elementInSelectionMode() override;
bool elementIntersectsRange( bool elementIntersectsRange(
not_null<const Element*> view, not_null<const Element*> view,
int from, int from,
int till) override; int till) override;
bool elementStartStickerLoop(not_null<const Element*> view) override;
void elementStickerLoopStarted(not_null<const Element*> view) override;
~ListWidget(); ~ListWidget();
@ -441,6 +444,7 @@ private:
int _itemsWidth = 0; int _itemsWidth = 0;
int _itemsHeight = 0; int _itemsHeight = 0;
int _itemAverageHeight = 0; int _itemAverageHeight = 0;
base::flat_set<FullMsgId> _animatedStickersPlayed;
int _minHeight = 0; int _minHeight = 0;
int _visibleTop = 0; int _visibleTop = 0;

View file

@ -54,7 +54,6 @@ void PrepareSupportMode() {
Global::SetDesktopNotify(false); Global::SetDesktopNotify(false);
Global::SetSoundNotify(false); Global::SetSoundNotify(false);
Auth().settings().autoDownload() = Full::FullDisabled(); Auth().settings().autoDownload() = Full::FullDisabled();
cSetAutoPlayGif(false);
Local::writeUserSettings(); Local::writeUserSettings();
} }

View file

@ -228,4 +228,19 @@ QImage Animation::frame(const FrameRequest &request) const {
return PrepareFrameByRequest(frame, !changed); return PrepareFrameByRequest(frame, !changed);
} }
auto Animation::frameInfo(const FrameRequest &request) const -> FrameInfo {
Expects(_state != nullptr);
const auto frame = _state->frameForPaint();
const auto changed = (frame->request != request);
if (changed) {
frame->request = request;
_player->updateFrameRequest(this, request);
}
return {
PrepareFrameByRequest(frame, !changed),
frame->index % _state->framesCount()
};
}
} // namespace Lottie } // namespace Lottie

View file

@ -10,7 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_common.h" #include "lottie/lottie_common.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
class QImage; #include <QtGui/QImage>
class QString; class QString;
class QByteArray; class QByteArray;
@ -39,6 +40,11 @@ std::unique_ptr<rlottie::Animation> CreateFromContent(
class Animation final : public base::has_weak_ptr { class Animation final : public base::has_weak_ptr {
public: public:
struct FrameInfo {
QImage image;
int index = 0;
};
Animation( Animation(
not_null<Player*> player, not_null<Player*> player,
const QByteArray &content, const QByteArray &content,
@ -55,6 +61,7 @@ public:
[[nodiscard]] bool ready() const; [[nodiscard]] bool ready() const;
[[nodiscard]] QImage frame() const; [[nodiscard]] QImage frame() const;
[[nodiscard]] QImage frame(const FrameRequest &request) const; [[nodiscard]] QImage frame(const FrameRequest &request) const;
[[nodiscard]] FrameInfo frameInfo(const FrameRequest &request) const;
private: private:
void initDone(details::InitData &&data); void initDone(details::InitData &&data);

View file

@ -210,12 +210,46 @@ void FrameRendererObject::queueGenerateFrames() {
}); });
} }
Information SharedState::CalculateInformation(
Quality quality,
rlottie::Animation *animation,
Cache *cache) {
Expects(animation != nullptr || cache != nullptr);
auto width = size_t(0);
auto height = size_t(0);
if (animation) {
animation->size(width, height);
} else {
width = cache->originalSize().width();
height = cache->originalSize().height();
}
const auto rate = animation
? GetLottieFrameRate(animation, quality)
: cache->frameRate();
const auto count = animation
? GetLottieFramesCount(animation, quality)
: cache->framesCount();
auto result = Information();
result.size = QSize(
(width > 0 && width <= kMaxSize) ? int(width) : 0,
(height > 0 && height <= kMaxSize) ? int(height) : 0);
result.frameRate = (rate > 0 && rate <= kMaxFrameRate) ? int(rate) : 0;
result.framesCount = (count > 0 && count <= kMaxFramesCount)
? int(count)
: 0;
return result;
}
SharedState::SharedState( SharedState::SharedState(
std::unique_ptr<rlottie::Animation> animation, std::unique_ptr<rlottie::Animation> animation,
const FrameRequest &request, const FrameRequest &request,
Quality quality) Quality quality)
: _animation(std::move(animation)) : _info(CalculateInformation(quality, animation.get(), nullptr))
, _quality(quality) { , _quality(quality)
, _animation(std::move(animation)) {
construct(request); construct(request);
} }
@ -225,15 +259,15 @@ SharedState::SharedState(
std::unique_ptr<Cache> cache, std::unique_ptr<Cache> cache,
const FrameRequest &request, const FrameRequest &request,
Quality quality) Quality quality)
: _content(content) : _info(CalculateInformation(quality, animation.get(), cache.get()))
, _animation(std::move(animation))
, _quality(quality) , _quality(quality)
, _cache(std::move(cache)) { , _cache(std::move(cache))
, _animation(std::move(animation))
, _content(content) {
construct(request); construct(request);
} }
void SharedState::construct(const FrameRequest &request) { void SharedState::construct(const FrameRequest &request) {
calculateProperties();
if (!isValid()) { if (!isValid()) {
return; return;
} }
@ -243,39 +277,20 @@ void SharedState::construct(const FrameRequest &request) {
return; return;
} }
if (_cache) { if (_cache) {
_cache->init(_size, _frameRate, _framesCount, request); _cache->init(
_info.size,
_info.frameRate,
_info.framesCount,
request);
} }
renderFrame(cover, request, 0); renderFrame(cover, request, 0);
init(std::move(cover), request); init(std::move(cover), request);
} }
void SharedState::calculateProperties() {
Expects(_animation != nullptr || _cache != nullptr);
auto width = size_t(0);
auto height = size_t(0);
if (_animation) {
_animation->size(width, height);
} else {
width = _cache->originalSize().width();
height = _cache->originalSize().height();
}
const auto rate = _animation
? GetLottieFrameRate(_animation.get(), _quality)
: _cache->frameRate();
const auto count = _animation
? GetLottieFramesCount(_animation.get(), _quality)
: _cache->framesCount();
_size = QSize(
(width > 0 && width <= kMaxSize) ? int(width) : 0,
(height > 0 && height <= kMaxSize) ? int(height) : 0);
_frameRate = (rate > 0 && rate <= kMaxFrameRate) ? int(rate) : 0;
_framesCount = (count > 0 && count <= kMaxFramesCount) ? int(count) : 0;
}
bool SharedState::isValid() const { bool SharedState::isValid() const {
return (_framesCount > 0) && (_frameRate > 0) && !_size.isEmpty(); return (_info.framesCount > 0)
&& (_info.frameRate > 0)
&& !_info.size.isEmpty();
} }
void SharedState::renderFrame( void SharedState::renderFrame(
@ -286,7 +301,9 @@ void SharedState::renderFrame(
return; return;
} }
const auto size = request.box.isEmpty() ? _size : request.size(_size); const auto size = request.box.isEmpty()
? _info.size
: request.size(_info.size);
if (!GoodStorageForFrame(image, size)) { if (!GoodStorageForFrame(image, size)) {
image = CreateFrameStorage(size); image = CreateFrameStorage(size);
} }
@ -339,9 +356,12 @@ bool IsRendered(not_null<const Frame*> frame) {
void SharedState::renderNextFrame( void SharedState::renderNextFrame(
not_null<Frame*> frame, not_null<Frame*> frame,
const FrameRequest &request) { const FrameRequest &request) {
Expects(_framesCount > 0); Expects(_info.framesCount > 0);
renderFrame(frame->original, request, (++_frameIndex) % _framesCount); renderFrame(
frame->original,
request,
(++_frameIndex) % _info.framesCount);
frame->request = request; frame->request = request;
PrepareFrameByRequest(frame); PrepareFrameByRequest(frame);
frame->index = _frameIndex; frame->index = _frameIndex;
@ -392,7 +412,7 @@ auto SharedState::renderNextFrame(const FrameRequest &request)
crl::time SharedState::countFrameDisplayTime(int index) const { crl::time SharedState::countFrameDisplayTime(int index) const {
return _started return _started
+ _delay + _delay
+ crl::time(1000) * (_skippedFrames + index) / _frameRate; + crl::time(1000) * (_skippedFrames + index) / _info.frameRate;
} }
int SharedState::counter() const { int SharedState::counter() const {
@ -416,14 +436,7 @@ not_null<const Frame*> SharedState::getFrame(int index) const {
} }
Information SharedState::information() const { Information SharedState::information() const {
if (!isValid()) { return isValid() ? _info : Information();
return {};
}
auto result = Information();
result.frameRate = _frameRate;
result.size = _size;
result.framesCount = _framesCount;
return result;
} }
not_null<Frame*> SharedState::frameForPaint() { not_null<Frame*> SharedState::frameForPaint() {
@ -434,6 +447,10 @@ not_null<Frame*> SharedState::frameForPaint() {
return result; return result;
} }
int SharedState::framesCount() const {
return _info.framesCount;
}
crl::time SharedState::nextFrameDisplayTime() const { crl::time SharedState::nextFrameDisplayTime() const {
const auto frameDisplayTime = [&](int counter) { const auto frameDisplayTime = [&](int counter) {
const auto next = (counter + 1) % (2 * kFramesCount); const auto next = (counter + 1) % (2 * kFramesCount);

View file

@ -72,6 +72,7 @@ public:
[[nodiscard]] bool initialized() const; [[nodiscard]] bool initialized() const;
[[nodiscard]] not_null<Frame*> frameForPaint(); [[nodiscard]] not_null<Frame*> frameForPaint();
[[nodiscard]] int framesCount() const;
[[nodiscard]] crl::time nextFrameDisplayTime() const; [[nodiscard]] crl::time nextFrameDisplayTime() const;
void addTimelineDelay(crl::time delayed, int skippedFrames = 0); void addTimelineDelay(crl::time delayed, int skippedFrames = 0);
void markFrameDisplayed(crl::time now); void markFrameDisplayed(crl::time now);
@ -88,8 +89,12 @@ public:
~SharedState(); ~SharedState();
private: private:
static Information CalculateInformation(
Quality quality,
rlottie::Animation *animation,
Cache *cache);
void construct(const FrameRequest &request); void construct(const FrameRequest &request);
void calculateProperties();
bool isValid() const; bool isValid() const;
void init(QImage cover, const FrameRequest &request); void init(QImage cover, const FrameRequest &request);
void renderNextFrame( void renderNextFrame(
@ -100,10 +105,6 @@ private:
[[nodiscard]] not_null<const Frame*> getFrame(int index) const; [[nodiscard]] not_null<const Frame*> getFrame(int index) const;
[[nodiscard]] int counter() const; [[nodiscard]] int counter() const;
QByteArray _content;
std::unique_ptr<rlottie::Animation> _animation;
Quality _quality = Quality::Default;
// crl::queue changes 0,2,4,6 to 1,3,5,7. // crl::queue changes 0,2,4,6 to 1,3,5,7.
// main thread changes 1,3,5,7 to 2,4,6,0. // main thread changes 1,3,5,7 to 2,4,6,0.
static constexpr auto kCounterUninitialized = -1; static constexpr auto kCounterUninitialized = -1;
@ -121,11 +122,13 @@ private:
int _frameIndex = 0; int _frameIndex = 0;
int _skippedFrames = 0; int _skippedFrames = 0;
int _framesCount = 0; const Information _info;
int _frameRate = 0; const Quality _quality = Quality::Default;
QSize _size;
std::unique_ptr<Cache> _cache; const std::unique_ptr<Cache> _cache;
std::unique_ptr<rlottie::Animation> _animation;
const QByteArray _content;
}; };

View file

@ -369,7 +369,7 @@ void MultiPlayer::addTimelineDelay(crl::time delayed) {
_delay += delayed; _delay += delayed;
} }
void MultiPlayer::markFrameShown() { bool MultiPlayer::markFrameShown() {
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
_nextFrameTime = kTimeUnknown; _nextFrameTime = kTimeUnknown;
} }
@ -381,7 +381,9 @@ void MultiPlayer::markFrameShown() {
} }
if (count) { if (count) {
_renderer->frameShown(); _renderer->frameShown();
return true;
} }
return false;
} }
} // namespace Lottie } // namespace Lottie

View file

@ -41,7 +41,7 @@ public:
void updateFrameRequest( void updateFrameRequest(
not_null<const Animation*> animation, not_null<const Animation*> animation,
const FrameRequest &request) override; const FrameRequest &request) override;
void markFrameShown() override; bool markFrameShown() override;
void checkStep() override; void checkStep() override;
not_null<Animation*> append( not_null<Animation*> append(

View file

@ -25,7 +25,7 @@ public:
virtual void updateFrameRequest( virtual void updateFrameRequest(
not_null<const Animation*> animation, not_null<const Animation*> animation,
const FrameRequest &request) = 0; const FrameRequest &request) = 0;
virtual void markFrameShown() = 0; virtual bool markFrameShown() = 0;
virtual void checkStep() = 0; virtual void checkStep() = 0;
virtual ~Player() = default; virtual ~Player() = default;

View file

@ -79,6 +79,11 @@ QImage SinglePlayer::frame(const FrameRequest &request) const {
return _animation.frame(request); return _animation.frame(request);
} }
Animation::FrameInfo SinglePlayer::frameInfo(
const FrameRequest &request) const {
return _animation.frameInfo(request);
}
void SinglePlayer::checkStep() { void SinglePlayer::checkStep() {
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
return; return;
@ -128,7 +133,7 @@ void SinglePlayer::updateFrameRequest(
_renderer->updateFrameRequest(_state, request); _renderer->updateFrameRequest(_state, request);
} }
void SinglePlayer::markFrameShown() { bool SinglePlayer::markFrameShown() {
Expects(_state != nullptr); Expects(_state != nullptr);
if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) { if (_nextFrameTime == kFrameDisplayTimeAlreadyDone) {
@ -136,7 +141,9 @@ void SinglePlayer::markFrameShown() {
} }
if (_state->markFrameShown()) { if (_state->markFrameShown()) {
_renderer->frameShown(); _renderer->frameShown();
return true;
} }
return false;
} }
} // namespace Lottie } // namespace Lottie

View file

@ -49,7 +49,7 @@ public:
void updateFrameRequest( void updateFrameRequest(
not_null<const Animation*> animation, not_null<const Animation*> animation,
const FrameRequest &request) override; const FrameRequest &request) override;
void markFrameShown() override; bool markFrameShown() override;
void checkStep() override; void checkStep() override;
rpl::producer<Update, Error> updates() const; rpl::producer<Update, Error> updates() const;
@ -57,6 +57,8 @@ public:
[[nodiscard]] bool ready() const; [[nodiscard]] bool ready() const;
[[nodiscard]] QImage frame() const; [[nodiscard]] QImage frame() const;
[[nodiscard]] QImage frame(const FrameRequest &request) const; [[nodiscard]] QImage frame(const FrameRequest &request) const;
[[nodiscard]] Animation::FrameInfo frameInfo(
const FrameRequest &request) const;
private: private:
void checkNextFrameAvailability(); void checkNextFrameAvailability();

View file

@ -49,7 +49,7 @@ Settings::Variables::Variables()
QByteArray Settings::serialize() const { QByteArray Settings::serialize() const {
const auto autoDownload = _variables.autoDownload.serialize(); const auto autoDownload = _variables.autoDownload.serialize();
auto size = sizeof(qint32) * 23; auto size = sizeof(qint32) * 30;
for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) { for (auto i = _variables.soundOverrides.cbegin(), e = _variables.soundOverrides.cend(); i != e; ++i) {
size += Serialize::stringSize(i.key()) + Serialize::stringSize(i.value()); size += Serialize::stringSize(i.key()) + Serialize::stringSize(i.value());
} }
@ -99,6 +99,8 @@ QByteArray Settings::serialize() const {
stream << qint32(_variables.notifyAboutPinned.current() ? 1 : 0); stream << qint32(_variables.notifyAboutPinned.current() ? 1 : 0);
stream << qint32(_variables.archiveInMainMenu.current() ? 1 : 0); stream << qint32(_variables.archiveInMainMenu.current() ? 1 : 0);
stream << qint32(_variables.skipArchiveInSearch.current() ? 1 : 0); stream << qint32(_variables.skipArchiveInSearch.current() ? 1 : 0);
stream << qint32(_variables.autoplayGifs ? 1 : 0);
stream << qint32(_variables.loopAnimatedStickers ? 1 : 0);
} }
return result; return result;
} }
@ -139,6 +141,8 @@ void Settings::constructFromSerialized(const QByteArray &serialized) {
qint32 notifyAboutPinned = _variables.notifyAboutPinned.current() ? 1 : 0; qint32 notifyAboutPinned = _variables.notifyAboutPinned.current() ? 1 : 0;
qint32 archiveInMainMenu = _variables.archiveInMainMenu.current() ? 1 : 0; qint32 archiveInMainMenu = _variables.archiveInMainMenu.current() ? 1 : 0;
qint32 skipArchiveInSearch = _variables.skipArchiveInSearch.current() ? 1 : 0; qint32 skipArchiveInSearch = _variables.skipArchiveInSearch.current() ? 1 : 0;
qint32 autoplayGifs = _variables.autoplayGifs ? 1 : 0;
qint32 loopAnimatedStickers = _variables.loopAnimatedStickers ? 1 : 0;
stream >> selectorTab; stream >> selectorTab;
stream >> lastSeenWarningSeen; stream >> lastSeenWarningSeen;
@ -230,6 +234,10 @@ void Settings::constructFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> skipArchiveInSearch; stream >> skipArchiveInSearch;
} }
if (!stream.atEnd()) {
stream >> autoplayGifs;
stream >> loopAnimatedStickers;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Settings::constructFromSerialized()")); "Bad data for Settings::constructFromSerialized()"));
@ -303,6 +311,8 @@ void Settings::constructFromSerialized(const QByteArray &serialized) {
_variables.notifyAboutPinned = (notifyAboutPinned == 1); _variables.notifyAboutPinned = (notifyAboutPinned == 1);
_variables.archiveInMainMenu = (archiveInMainMenu == 1); _variables.archiveInMainMenu = (archiveInMainMenu == 1);
_variables.skipArchiveInSearch = (skipArchiveInSearch == 1); _variables.skipArchiveInSearch = (skipArchiveInSearch == 1);
_variables.autoplayGifs = (autoplayGifs == 1);
_variables.loopAnimatedStickers = (loopAnimatedStickers == 1);
} }
void Settings::setSupportChatsTimeSlice(int slice) { void Settings::setSupportChatsTimeSlice(int slice) {

View file

@ -230,6 +230,18 @@ public:
void setExeLaunchWarning(bool warning) { void setExeLaunchWarning(bool warning) {
_variables.exeLaunchWarning = warning; _variables.exeLaunchWarning = warning;
} }
bool autoplayGifs() const {
return _variables.autoplayGifs;
}
void setAutoplayGifs(bool value) {
_variables.autoplayGifs = value;
}
bool loopAnimatedStickers() const {
return _variables.loopAnimatedStickers;
}
void setLoopAnimatedStickers(bool value) {
_variables.loopAnimatedStickers = value;
}
private: private:
struct Variables { struct Variables {
@ -264,6 +276,8 @@ private:
rpl::variable<bool> archiveInMainMenu = false; rpl::variable<bool> archiveInMainMenu = false;
rpl::variable<bool> notifyAboutPinned = true; rpl::variable<bool> notifyAboutPinned = true;
rpl::variable<bool> skipArchiveInSearch = false; rpl::variable<bool> skipArchiveInSearch = false;
bool autoplayGifs = true;
bool loopAnimatedStickers = true;
static constexpr auto kDefaultSupportChatsLimitSlice static constexpr auto kDefaultSupportChatsLimitSlice
= 7 * 24 * 60 * 60; = 7 * 24 * 60 * 60;

View file

@ -123,6 +123,7 @@ void LoaderMtproto::sendNext() {
const auto usedFileReference = _location.fileReference(); const auto usedFileReference = _location.fileReference();
const auto id = _sender.request(MTPupload_GetFile( const auto id = _sender.request(MTPupload_GetFile(
MTP_flags(0),
_location.tl(Auth().userId()), _location.tl(Auth().userId()),
MTP_int(offset), MTP_int(offset),
MTP_int(kPartSize) MTP_int(kPartSize)

View file

@ -72,4 +72,3 @@ int gOtherOnline = 0;
int32 gAutoDownloadPhoto = 0; // all auto download int32 gAutoDownloadPhoto = 0; // all auto download
int32 gAutoDownloadAudio = 0; int32 gAutoDownloadAudio = 0;
int32 gAutoDownloadGif = 0; int32 gAutoDownloadGif = 0;
bool gAutoPlayGif = true;

View file

@ -158,7 +158,6 @@ DeclareSetting(float64, RetinaFactor);
DeclareSetting(int32, IntRetinaFactor); DeclareSetting(int32, IntRetinaFactor);
DeclareSetting(int, OtherOnline); DeclareSetting(int, OtherOnline);
DeclareSetting(bool, AutoPlayGif);
constexpr auto kInterfaceScaleAuto = 0; constexpr auto kInterfaceScaleAuto = 0;
constexpr auto kInterfaceScaleMin = 100; constexpr auto kInterfaceScaleMin = 100;

View file

@ -424,21 +424,36 @@ void SetupPerformance(
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
SetupAnimations(container); SetupAnimations(container);
const auto session = &controller->session();
AddButton( AddButton(
container, container,
tr::lng_settings_autoplay_gifs(), tr::lng_settings_autoplay_gifs(),
st::settingsButton st::settingsButton
)->toggleOn( )->toggleOn(
rpl::single(cAutoPlayGif()) rpl::single(session->settings().autoplayGifs())
)->toggledValue( )->toggledValue(
) | rpl::filter([](bool enabled) { ) | rpl::filter([=](bool enabled) {
return (enabled != cAutoPlayGif()); return (enabled != session->settings().autoplayGifs());
}) | rpl::start_with_next([=](bool enabled) { }) | rpl::start_with_next([=](bool enabled) {
cSetAutoPlayGif(enabled); session->settings().setAutoplayGifs(enabled);
if (!cAutoPlayGif()) { if (!enabled) {
controller->session().data().stopAutoplayAnimations(); session->data().stopAutoplayAnimations();
} }
Local::writeUserSettings(); session->saveSettingsDelayed();
}, container->lifetime());
AddButton(
container,
tr::lng_settings_loop_stickers(),
st::settingsButton
)->toggleOn(
rpl::single(session->settings().loopAnimatedStickers())
)->toggledValue(
) | rpl::filter([=](bool enabled) {
return enabled != session->settings().loopAnimatedStickers();
}) | rpl::start_with_next([=](bool enabled) {
session->settings().setLoopAnimatedStickers(enabled);
session->saveSettingsDelayed();
}, container->lifetime()); }, container->lifetime());
} }

View file

@ -580,7 +580,7 @@ enum {
dbiAutoDownloadOld = 0x34, dbiAutoDownloadOld = 0x34,
dbiSavedGifsLimit = 0x35, dbiSavedGifsLimit = 0x35,
dbiShowingSavedGifsOld = 0x36, dbiShowingSavedGifsOld = 0x36,
dbiAutoPlay = 0x37, dbiAutoPlayOld = 0x37,
dbiAdaptiveForWide = 0x38, dbiAdaptiveForWide = 0x38,
dbiHiddenPinnedMessages = 0x39, dbiHiddenPinnedMessages = 0x39,
dbiRecentEmoji = 0x3a, dbiRecentEmoji = 0x3a,
@ -1104,12 +1104,12 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
set(Type::VideoMessage, gif); set(Type::VideoMessage, gif);
} break; } break;
case dbiAutoPlay: { case dbiAutoPlayOld: {
qint32 gif; qint32 gif;
stream >> gif; stream >> gif;
if (!_checkStreamStatus(stream)) return false; if (!_checkStreamStatus(stream)) return false;
cSetAutoPlayGif(gif == 1); GetStoredSessionSettings().setAutoplayGifs(gif == 1);
} break; } break;
case dbiDialogsMode: { case dbiDialogsMode: {
@ -2087,7 +2087,6 @@ void _writeUserSettings() {
data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6));
data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast<qint32>(Global::DialogsMode()); data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast<qint32>(Global::DialogsMode());
data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0);
data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0);
data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer());
data.stream << quint32(dbiCacheSettings) << qint64(_cacheTotalSizeLimit) << qint32(_cacheTotalTimeLimit) << qint64(_cacheBigFileTotalSizeLimit) << qint32(_cacheBigFileTotalTimeLimit); data.stream << quint32(dbiCacheSettings) << qint64(_cacheTotalSizeLimit) << qint32(_cacheTotalTimeLimit) << qint64(_cacheBigFileTotalSizeLimit) << qint32(_cacheBigFileTotalTimeLimit);
if (!userData.isEmpty()) { if (!userData.isEmpty()) {