Add an animated scroll to current media message.

This commit is contained in:
John Preston 2017-05-21 20:08:59 +03:00
parent 6bde8cdce4
commit 0bfff65306
4 changed files with 117 additions and 24 deletions

View File

@ -59,6 +59,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/file_upload.h" #include "storage/file_upload.h"
#include "media/media_audio.h" #include "media/media_audio.h"
#include "media/media_audio_capture.h" #include "media/media_audio_capture.h"
#include "media/player/media_player_instance.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "window/top_bar_widget.h" #include "window/top_bar_widget.h"
@ -81,6 +82,8 @@ constexpr auto kMessagesPerPage = 50;
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000; constexpr auto kTabbedSelectorToggleTooltipTimeoutMs = 3000;
constexpr auto kTabbedSelectorToggleTooltipCount = 3; constexpr auto kTabbedSelectorToggleTooltipCount = 3;
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
constexpr auto kSkipRepaintWhileScrollMs = 100;
ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() { ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
return [](ChannelData *channel, MsgId msgId) { return [](ChannelData *channel, MsgId msgId) {
@ -633,10 +636,68 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null<Window::Controller*>
updateControlsVisibility(); updateControlsVisibility();
} }
}); });
subscribe(Media::Player::instance()->switchToNextNotifier(), [this](const Media::Player::Instance::Switch &pair) {
if (pair.from.type() == AudioMsgId::Type::Voice) {
scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to);
}
});
orderWidgets(); orderWidgets();
} }
void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId) {
if (getms() <= _lastUserScrolled + kScrollToVoiceAfterScrolledMs) {
return;
}
if (!_list) {
return;
}
auto from = App::histItemById(fromId);
auto to = App::histItemById(toId);
if (!from || !to) {
return;
}
// If history has pending resize items, the scrollTopItem won't be updated.
// And the scrollTop will be reset back to scrollTopItem + scrollTopOffset.
notify_handlePendingHistoryUpdate();
auto fromTop = _list->itemTop(from);
auto toTop = _list->itemTop(to);
if (fromTop < 0 || toTop < 0) {
return;
}
auto scrollTop = _scroll->scrollTop();
auto scrollBottom = scrollTop + _scroll->height();
auto fromBottom = fromTop + from->height();
if (fromTop < scrollBottom && fromBottom > scrollTop) {
auto toBottom = toTop + to->height();
if ((toTop < scrollTop && toBottom < scrollBottom) || (toTop > scrollTop && toBottom > scrollBottom)) {
auto scrollTo = snap(itemTopForHighlight(to), 0, _scroll->scrollTopMax());
_scrollToMediaMessageAnimation.finish();
_scrollToMediaMessageAnimation.start([this, toId] {
auto toTop = _list->itemTop(App::histItemById(toId));
if (toTop < 0) {
_scrollToMediaMessageAnimation.finish();
} else {
synteticScrollToY(qRound(_scrollToMediaMessageAnimation.current()) + toTop);
}
}, scrollTop - toTop, scrollTo - toTop, 200, anim::sineInOut);
}
}
}
int HistoryWidget::itemTopForHighlight(HistoryItem *item) const {
auto itemTop = _list->itemTop(item);
auto heightLeft = (_scroll->height() - item->height());
if (heightLeft <= 0) {
return itemTop;
}
return qMax(itemTop - (heightLeft / 2), 0);
}
void HistoryWidget::start() { void HistoryWidget::start() {
connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated()));
updateRecentStickers(); updateRecentStickers();
@ -2235,7 +2296,7 @@ void HistoryWidget::historyToDown(History *history) {
migrated->forgetScrollState(); migrated->forgetScrollState();
} }
if (history == _history) { if (history == _history) {
_scroll->scrollToY(_scroll->scrollTopMax()); synteticScrollToY(_scroll->scrollTopMax());
} }
} }
@ -2571,6 +2632,9 @@ void HistoryWidget::onScroll() {
App::checkImageCacheSize(); App::checkImageCacheSize();
preloadHistoryIfNeeded(); preloadHistoryIfNeeded();
visibleAreaUpdated(); visibleAreaUpdated();
if (!_synteticScrollEvent) {
_lastUserScrolled = getms();
}
} }
void HistoryWidget::visibleAreaUpdated() { void HistoryWidget::visibleAreaUpdated() {
@ -2613,9 +2677,9 @@ void HistoryWidget::preloadHistoryIfNeeded() {
} }
} }
if (st != _lastScroll) { if (st != _lastScrollTop) {
_lastScrolled = getms(); _lastScrolled = getms();
_lastScroll = st; _lastScrollTop = st;
} }
} }
@ -4531,10 +4595,10 @@ bool HistoryWidget::isItemVisible(HistoryItem *item) {
void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) { void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) {
if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) { if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) {
auto ms = getms(); auto ms = getms();
if (_lastScrolled + 100 <= ms) { if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
_list->repaintItem(item); _list->repaintItem(item);
} else { } else {
_updateHistoryItems.start(_lastScrolled + 100 - ms); _updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms);
} }
} }
} }
@ -4543,10 +4607,10 @@ void HistoryWidget::onUpdateHistoryItems() {
if (!_list) return; if (!_list) return;
auto ms = getms(); auto ms = getms();
if (_lastScrolled + 100 <= ms) { if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
_list->update(); _list->update();
} else { } else {
_updateHistoryItems.start(_lastScrolled + 100 - ms); _updateHistoryItems.start(_lastScrolled + kSkipRepaintWhileScrollMs - ms);
} }
} }
@ -4732,7 +4796,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
if (_scroll->scrollTop() == toY) { if (_scroll->scrollTop() == toY) {
visibleAreaUpdated(); visibleAreaUpdated();
} else { } else {
_scroll->scrollToY(toY); synteticScrollToY(toY);
} }
return; return;
} }
@ -4745,27 +4809,27 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
if (initial && (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem))) { if (initial && (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem))) {
toY = _list->historyScrollTop(); toY = _list->historyScrollTop();
} else if (initial && _migrated && _showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) { } else if (initial && _migrated && _showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) {
HistoryItem *item = App::histItemById(0, -_showAtMsgId); auto item = App::histItemById(0, -_showAtMsgId);
int32 iy = _list->itemTop(item); auto iy = _list->itemTop(item);
if (iy < 0) { if (iy < 0) {
setMsgId(0); setMsgId(0);
_histInited = false; _histInited = false;
return updateListSize(initial, false, change); return updateListSize(initial, false, change);
} else { } else {
toY = (_scroll->height() > item->height()) ? qMax(iy - (_scroll->height() - item->height()) / 2, 0) : iy; toY = itemTopForHighlight(item);
_animActiveStart = getms(); _animActiveStart = getms();
_animActiveTimer.start(AnimationTimerDelta); _animActiveTimer.start(AnimationTimerDelta);
_activeAnimMsgId = _showAtMsgId; _activeAnimMsgId = _showAtMsgId;
} }
} else if (initial && _showAtMsgId > 0) { } else if (initial && _showAtMsgId > 0) {
HistoryItem *item = App::histItemById(_channel, _showAtMsgId); auto item = App::histItemById(_channel, _showAtMsgId);
int32 iy = _list->itemTop(item); auto iy = _list->itemTop(item);
if (iy < 0) { if (iy < 0) {
setMsgId(0); setMsgId(0);
_histInited = false; _histInited = false;
return updateListSize(initial, false, change); return updateListSize(initial, false, change);
} else { } else {
toY = (_scroll->height() > item->height()) ? qMax(iy - (_scroll->height() - item->height()) / 2, 0) : iy; toY = itemTopForHighlight(item);
_animActiveStart = getms(); _animActiveStart = getms();
_animActiveTimer.start(AnimationTimerDelta); _animActiveTimer.start(AnimationTimerDelta);
_activeAnimMsgId = _showAtMsgId; _activeAnimMsgId = _showAtMsgId;
@ -4806,7 +4870,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
if (_scroll->scrollTop() == toY) { if (_scroll->scrollTop() == toY) {
visibleAreaUpdated(); visibleAreaUpdated();
} else { } else {
_scroll->scrollToY(toY); synteticScrollToY(toY);
} }
} }
@ -5187,7 +5251,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
result = true; result = true;
if (_scroll->scrollTop() != unreadBarTop()) { if (_scroll->scrollTop() != unreadBarTop()) {
_scroll->scrollToY(_scroll->scrollTop() + st::historyReplyHeight); synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight);
} }
} else if (_pinnedBar->msgId != pinnedMsgId) { } else if (_pinnedBar->msgId != pinnedMsgId) {
_pinnedBar->msgId = pinnedMsgId; _pinnedBar->msgId = pinnedMsgId;
@ -5202,7 +5266,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
destroyPinnedBar(); destroyPinnedBar();
result = true; result = true;
if (_scroll->scrollTop() != unreadBarTop()) { if (_scroll->scrollTop() != unreadBarTop()) {
_scroll->scrollToY(_scroll->scrollTop() - st::historyReplyHeight); synteticScrollToY(_scroll->scrollTop() - st::historyReplyHeight);
} }
updateControlsGeometry(); updateControlsGeometry();
} }
@ -6390,7 +6454,7 @@ QPoint HistoryWidget::clampMousePosition(QPoint point) {
} }
void HistoryWidget::onScrollTimer() { void HistoryWidget::onScrollTimer() {
int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); auto d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed));
_scroll->scrollToY(_scroll->scrollTop() + d); _scroll->scrollToY(_scroll->scrollTop() + d);
} }
@ -6421,4 +6485,10 @@ bool HistoryWidget::touchScroll(const QPoint &delta) {
return true; return true;
} }
void HistoryWidget::synteticScrollToY(int y) {
_synteticScrollEvent = true;
_scroll->scrollToY(y);
_synteticScrollEvent = false;
}
HistoryWidget::~HistoryWidget() = default; HistoryWidget::~HistoryWidget() = default;

View File

@ -643,6 +643,13 @@ private:
// Counts scrollTop for placing the scroll right at the unread // Counts scrollTop for placing the scroll right at the unread
// messages bar, choosing from _history and _migrated unreadBar. // messages bar, choosing from _history and _migrated unreadBar.
int unreadBarTop() const; int unreadBarTop() const;
int itemTopForHighlight(HistoryItem *item) const;
void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId);
// Scroll to current y without updating the _lastUserScrolled time.
// Used to distinguish between user scrolls and syntetic scrolls.
// This one is syntetic.
void synteticScrollToY(int y);
void saveGifDone(DocumentData *doc, const MTPBool &result); void saveGifDone(DocumentData *doc, const MTPBool &result);
@ -721,10 +728,14 @@ private:
bool _histInited = false; // initial updateListSize() called bool _histInited = false; // initial updateListSize() called
int _addToScroll = 0; int _addToScroll = 0;
int _lastScroll = 0;// gifs optimization int _lastScrollTop = 0; // gifs optimization
TimeMs _lastScrolled = 0; TimeMs _lastScrolled = 0;
QTimer _updateHistoryItems; QTimer _updateHistoryItems;
TimeMs _lastUserScrolled = 0;
bool _synteticScrollEvent = false;
Animation _scrollToMediaMessageAnimation;
Animation _historyDownShown; Animation _historyDownShown;
bool _historyDownIsShown = false; bool _historyDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _historyDown; object_ptr<Ui::HistoryDownButton> _historyDown;

View File

@ -166,7 +166,7 @@ void Instance::rebuildPlaylist(Data *data) {
_playlistChangedNotifier.notify(data->type, true); _playlistChangedNotifier.notify(data->type, true);
} }
bool Instance::moveInPlaylist(Data *data, int delta) { bool Instance::moveInPlaylist(Data *data, int delta, bool autonext) {
Expects(data != nullptr); Expects(data != nullptr);
auto index = data->playlist.indexOf(data->current.contextId()); auto index = data->playlist.indexOf(data->current.contextId());
@ -179,6 +179,9 @@ bool Instance::moveInPlaylist(Data *data, int delta) {
auto msgId = data->playlist[newIndex]; auto msgId = data->playlist[newIndex];
if (auto item = App::histItemById(msgId)) { if (auto item = App::histItemById(msgId)) {
if (auto media = item->getMedia()) { if (auto media = item->getMedia()) {
if (autonext) {
_switchToNextNotifier.notify({ data->current, msgId });
}
media->playInline(); media->playInline();
return true; return true;
} }
@ -258,14 +261,14 @@ void Instance::playPause(AudioMsgId::Type type) {
bool Instance::next(AudioMsgId::Type type) { bool Instance::next(AudioMsgId::Type type) {
if (auto data = getData(type)) { if (auto data = getData(type)) {
return moveInPlaylist(data, 1); return moveInPlaylist(data, 1, false);
} }
return false; return false;
} }
bool Instance::previous(AudioMsgId::Type type) { bool Instance::previous(AudioMsgId::Type type) {
if (auto data = getData(type)) { if (auto data = getData(type)) {
return moveInPlaylist(data, -1); return moveInPlaylist(data, -1, false);
} }
return false; return false;
} }
@ -323,7 +326,7 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
if (data->isPlaying && state.state == State::StoppedAtEnd) { if (data->isPlaying && state.state == State::StoppedAtEnd) {
if (data->repeatEnabled) { if (data->repeatEnabled) {
play(data->current); play(data->current);
} else if (!next(type)) { } else if (!moveInPlaylist(data, 1, true)) {
_tracksFinishedNotifier.notify(type); _tracksFinishedNotifier.notify(type);
} }
} }

View File

@ -105,6 +105,14 @@ public:
return QList<FullMsgId>(); return QList<FullMsgId>();
} }
struct Switch {
AudioMsgId from;
FullMsgId to;
};
base::Observable<Switch> &switchToNextNotifier() {
return _switchToNextNotifier;
}
base::Observable<bool> &usePanelPlayer() { base::Observable<bool> &usePanelPlayer() {
return _usePanelPlayer; return _usePanelPlayer;
} }
@ -160,7 +168,7 @@ private:
void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update); void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update);
void setCurrent(const AudioMsgId &audioId); void setCurrent(const AudioMsgId &audioId);
void rebuildPlaylist(Data *data); void rebuildPlaylist(Data *data);
bool moveInPlaylist(Data *data, int delta); bool moveInPlaylist(Data *data, int delta, bool autonext);
void preloadNext(Data *data); void preloadNext(Data *data);
void handleLogout(); void handleLogout();
@ -188,6 +196,7 @@ private:
Data _songData; Data _songData;
Data _voiceData; Data _voiceData;
base::Observable<Switch> _switchToNextNotifier;
base::Observable<bool> _usePanelPlayer; base::Observable<bool> _usePanelPlayer;
base::Observable<bool> _titleButtonOver; base::Observable<bool> _titleButtonOver;
base::Observable<bool> _playerWidgetOver; base::Observable<bool> _playerWidgetOver;