Support add item animation in ListWidget (sections).

This commit is contained in:
John Preston 2021-07-19 20:01:39 +03:00
parent 7bbc4b7191
commit c789349b24
4 changed files with 133 additions and 29 deletions

View file

@ -154,7 +154,6 @@ constexpr auto kSaveDraftTimeout = 1000;
constexpr auto kSaveDraftAnywayTimeout = 5000; constexpr auto kSaveDraftAnywayTimeout = 5000;
constexpr auto kSaveCloudDraftIdleTimeout = 14000; constexpr auto kSaveCloudDraftIdleTimeout = 14000;
constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
constexpr auto kItemRevealDuration = crl::time(200);
constexpr auto kCommonModifiers = 0 constexpr auto kCommonModifiers = 0
| Qt::ShiftModifier | Qt::ShiftModifier
| Qt::MetaModifier | Qt::MetaModifier
@ -4688,6 +4687,10 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
_itemRevealAnimations.erase(i); _itemRevealAnimations.erase(i);
revealItemsCallback(); revealItemsCallback();
} }
const auto j = _itemRevealPending.find(item);
if (j != _itemRevealPending.end()) {
_itemRevealPending.erase(j);
}
} }
void HistoryWidget::itemEdited(not_null<HistoryItem*> item) { void HistoryWidget::itemEdited(not_null<HistoryItem*> item) {
@ -4904,35 +4907,35 @@ void HistoryWidget::revealItemsCallback() {
} }
} }
void HistoryWidget::updateListSize() { void HistoryWidget::startItemRevealAnimations() {
_list->recountHistoryGeometry();
auto washidden = _scroll->isHidden();
if (washidden) {
_scroll->show();
}
for (const auto item : base::take(_itemRevealPending)) { for (const auto item : base::take(_itemRevealPending)) {
if (const auto view = item->mainView()) { if (const auto view = item->mainView()) {
if (const auto top = _list->itemTop(view); top >= 0) { if (const auto top = _list->itemTop(view); top >= 0) {
if (const auto height = view->height()) { if (const auto height = view->height()) {
if (!_itemRevealAnimations.contains(item)) { if (!_itemRevealAnimations.contains(item)) {
auto &animation = _itemRevealAnimations[item]; auto &animation = _itemRevealAnimations[item];
if (!animation.animation.animating()) { animation.startHeight = height;
animation.startHeight _itemsRevealHeight += height;
= animation.currentHeight animation.animation.start(
= height; [=] { revealItemsCallback(); },
_itemsRevealHeight += height; 0.,
animation.animation.start( 1.,
[=] { revealItemsCallback(); }, HistoryView::kItemRevealDuration,
0., anim::easeOutCirc);
1.,
kItemRevealDuration,
anim::easeOutCirc);
}
} }
} }
} }
} }
} }
}
void HistoryWidget::updateListSize() {
_list->recountHistoryGeometry();
auto washidden = _scroll->isHidden();
if (washidden) {
_scroll->show();
}
startItemRevealAnimations();
_list->setItemsRevealHeight(_itemsRevealHeight); _list->setItemsRevealHeight(_itemsRevealHeight);
_list->updateSize(); _list->updateSize();
if (washidden) { if (washidden) {

View file

@ -323,9 +323,8 @@ private:
bool active = false; bool active = false;
}; };
struct ItemRevealAnimation { struct ItemRevealAnimation {
int startHeight = 0;
int currentHeight = 0;
Ui::Animations::Simple animation; Ui::Animations::Simple animation;
int startHeight = 0;
}; };
enum class TextUpdateEvent { enum class TextUpdateEvent {
SaveDraft = (1 << 0), SaveDraft = (1 << 0),
@ -536,6 +535,7 @@ private:
void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
void updateListSize(); void updateListSize();
void startItemRevealAnimations();
void revealItemsCallback(); void revealItemsCallback();
// Does any of the shown histories has this flag set. // Does any of the shown histories has this flag set.

View file

@ -340,14 +340,24 @@ void ListWidget::refreshViewer() {
_idsLimit, _idsLimit,
_idsLimit _idsLimit
) | rpl::start_with_next([=](Data::MessagesSlice &&slice) { ) | rpl::start_with_next([=](Data::MessagesSlice &&slice) {
_slice = std::move(slice); std::swap(_slice, slice);
refreshRows(); refreshRows(slice);
}, _viewerLifetime); }, _viewerLifetime);
} }
void ListWidget::refreshRows() { void ListWidget::refreshRows(const Data::MessagesSlice &old) {
saveScrollState(); saveScrollState();
const auto addedToEndFrom = (old.skippedAfter == 0
&& (_slice.skippedAfter == 0)
&& !old.ids.empty())
? ranges::find(_slice.ids, old.ids.back())
: end(_slice.ids);
const auto addedToEndCount = std::max(
int(end(_slice.ids) - addedToEndFrom),
1
) - 1;
_items.clear(); _items.clear();
_items.reserve(_slice.ids.size()); _items.reserve(_slice.ids.size());
auto nearestIndex = -1; auto nearestIndex = -1;
@ -359,12 +369,17 @@ void ListWidget::refreshRows() {
_items.push_back(enforceViewForItem(item)); _items.push_back(enforceViewForItem(item));
} }
} }
for (auto e = end(_items), i = e - addedToEndCount; i != e; ++i) {
_itemRevealPending.emplace(*i);
}
updateAroundPositionFromNearest(nearestIndex); updateAroundPositionFromNearest(nearestIndex);
updateItemsGeometry(); updateItemsGeometry();
checkUnreadBarCreation(); checkUnreadBarCreation();
restoreScrollState(); restoreScrollState();
mouseActionUpdate(QCursor::pos()); if (!_itemsRevealHeight) {
mouseActionUpdate(QCursor::pos());
}
_delegate->listContentRefreshed(); _delegate->listContentRefreshed();
} }
@ -1124,7 +1139,9 @@ bool ListWidget::loadedAtBottom() const {
} }
bool ListWidget::isEmpty() const { bool ListWidget::isEmpty() const {
return loadedAtTop() && loadedAtBottom(); return loadedAtTop()
&& loadedAtBottom()
&& (_itemsHeight + _itemsRevealHeight == 0);
} }
int ListWidget::itemMinimalHeight() const { int ListWidget::itemMinimalHeight() const {
@ -1405,6 +1422,60 @@ void ListWidget::resizeToWidth(int newWidth, int minHeight) {
restoreScrollPosition(); restoreScrollPosition();
} }
void ListWidget::startItemRevealAnimations() {
for (const auto view : base::take(_itemRevealPending)) {
if (const auto height = view->height()) {
if (!_itemRevealAnimations.contains(view)) {
auto &animation = _itemRevealAnimations[view];
animation.startHeight = height;
_itemsRevealHeight += height;
animation.animation.start(
[=] { revealItemsCallback(); },
0.,
1.,
kItemRevealDuration,
anim::easeOutCirc);
}
}
}
}
void ListWidget::revealItemsCallback() {
auto revealHeight = 0;
for (auto i = begin(_itemRevealAnimations)
; i != end(_itemRevealAnimations);) {
if (!i->second.animation.animating()) {
i = _itemRevealAnimations.erase(i);
} else {
revealHeight += anim::interpolate(
i->second.startHeight,
0,
i->second.animation.value(1.));
++i;
}
}
if (_itemsRevealHeight != revealHeight) {
saveScrollState();
const auto old = std::exchange(_itemsRevealHeight, revealHeight);
const auto delta = old - _itemsRevealHeight;
_itemsHeight += delta;
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
? (_minHeight - _itemsHeight - st::historyPaddingBottom)
: 0;
const auto wasHeight = height();
const auto nowHeight = std::min(_minHeight, wasHeight + delta);
if (wasHeight != nowHeight) {
resize(width(), nowHeight);
}
update();
restoreScrollState();
if (!_itemsRevealHeight) {
mouseActionUpdate(QCursor::pos());
}
}
}
int ListWidget::resizeGetHeight(int newWidth) { int ListWidget::resizeGetHeight(int newWidth) {
update(); update();
@ -1423,8 +1494,9 @@ int ListWidget::resizeGetHeight(int newWidth) {
itemMinimalHeight(), itemMinimalHeight(),
newHeight / int(_items.size())); newHeight / int(_items.size()));
} }
startItemRevealAnimations();
_itemsWidth = newWidth; _itemsWidth = newWidth;
_itemsHeight = newHeight; _itemsHeight = newHeight - _itemsRevealHeight;
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
? (_minHeight - _itemsHeight - st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom)
: 0; : 0;
@ -2726,6 +2798,23 @@ void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
_bar.element->createUnreadBar(_barText.value()); _bar.element->createUnreadBar(_barText.value());
} }
} }
const auto i = _itemRevealPending.find(was);
if (i != end(_itemRevealPending)) {
_itemRevealPending.erase(i);
if (now) {
_itemRevealPending.emplace(now);
}
}
const auto j = _itemRevealAnimations.find(was);
if (j != end(_itemRevealAnimations)) {
auto data = std::move(j->second);
_itemRevealAnimations.erase(j);
if (now) {
_itemRevealAnimations.emplace(now, std::move(data));
} else {
revealItemsCallback();
}
}
} }
void ListWidget::itemRemoved(not_null<const HistoryItem*> item) { void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {

View file

@ -143,6 +143,8 @@ private:
}; };
inline constexpr auto kItemRevealDuration = crl::time(150);
class ListWidget final class ListWidget final
: public Ui::RpWidget : public Ui::RpWidget
, public ElementDelegate , public ElementDelegate
@ -296,7 +298,10 @@ private:
inline bool operator!=(const MouseState &other) const { inline bool operator!=(const MouseState &other) const {
return !(*this == other); return !(*this == other);
} }
};
struct ItemRevealAnimation {
Ui::Animations::Simple animation;
int startHeight = 0;
}; };
enum class Direction { enum class Direction {
Up, Up,
@ -329,7 +334,7 @@ private:
void refreshViewer(); void refreshViewer();
void updateAroundPositionFromNearest(int nearestIndex); void updateAroundPositionFromNearest(int nearestIndex);
void refreshRows(); void refreshRows(const Data::MessagesSlice &old);
ScrollTopState countScrollState() const; ScrollTopState countScrollState() const;
void saveScrollState(); void saveScrollState();
void restoreScrollState(); void restoreScrollState();
@ -458,6 +463,8 @@ private:
void checkUnreadBarCreation(); void checkUnreadBarCreation();
void applyUpdatedScrollState(); void applyUpdatedScrollState();
void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo); void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo);
void startItemRevealAnimations();
void revealItemsCallback();
void updateHighlightedMessage(); void updateHighlightedMessage();
void clearHighlightedMessage(); void clearHighlightedMessage();
@ -505,6 +512,11 @@ private:
int _itemsWidth = 0; int _itemsWidth = 0;
int _itemsHeight = 0; int _itemsHeight = 0;
int _itemAverageHeight = 0; int _itemAverageHeight = 0;
base::flat_set<not_null<Element*>> _itemRevealPending;
base::flat_map<
not_null<Element*>,
ItemRevealAnimation> _itemRevealAnimations;
int _itemsRevealHeight = 0;
base::flat_set<FullMsgId> _animatedStickersPlayed; base::flat_set<FullMsgId> _animatedStickersPlayed;
base::flat_map< base::flat_map<
not_null<PeerData*>, not_null<PeerData*>,