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

View file

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

View file

@ -340,14 +340,24 @@ void ListWidget::refreshViewer() {
_idsLimit,
_idsLimit
) | rpl::start_with_next([=](Data::MessagesSlice &&slice) {
_slice = std::move(slice);
refreshRows();
std::swap(_slice, slice);
refreshRows(slice);
}, _viewerLifetime);
}
void ListWidget::refreshRows() {
void ListWidget::refreshRows(const Data::MessagesSlice &old) {
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.reserve(_slice.ids.size());
auto nearestIndex = -1;
@ -359,12 +369,17 @@ void ListWidget::refreshRows() {
_items.push_back(enforceViewForItem(item));
}
}
for (auto e = end(_items), i = e - addedToEndCount; i != e; ++i) {
_itemRevealPending.emplace(*i);
}
updateAroundPositionFromNearest(nearestIndex);
updateItemsGeometry();
checkUnreadBarCreation();
restoreScrollState();
mouseActionUpdate(QCursor::pos());
if (!_itemsRevealHeight) {
mouseActionUpdate(QCursor::pos());
}
_delegate->listContentRefreshed();
}
@ -1124,7 +1139,9 @@ bool ListWidget::loadedAtBottom() const {
}
bool ListWidget::isEmpty() const {
return loadedAtTop() && loadedAtBottom();
return loadedAtTop()
&& loadedAtBottom()
&& (_itemsHeight + _itemsRevealHeight == 0);
}
int ListWidget::itemMinimalHeight() const {
@ -1405,6 +1422,60 @@ void ListWidget::resizeToWidth(int newWidth, int minHeight) {
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) {
update();
@ -1423,8 +1494,9 @@ int ListWidget::resizeGetHeight(int newWidth) {
itemMinimalHeight(),
newHeight / int(_items.size()));
}
startItemRevealAnimations();
_itemsWidth = newWidth;
_itemsHeight = newHeight;
_itemsHeight = newHeight - _itemsRevealHeight;
_itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom)
? (_minHeight - _itemsHeight - st::historyPaddingBottom)
: 0;
@ -2726,6 +2798,23 @@ void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
_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) {

View file

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