From 37308cde21f2a63da74e8b28015a39898ae0f238 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 11 Nov 2022 10:23:23 +0400 Subject: [PATCH] Support dialog rows with variable height. --- Telegram/SourceFiles/apiwrap.cpp | 4 +- Telegram/SourceFiles/boxes/share_box.cpp | 13 +- Telegram/SourceFiles/data/data_session.cpp | 18 +- Telegram/SourceFiles/data/data_session.h | 16 +- Telegram/SourceFiles/dialogs/dialogs.style | 3 + .../SourceFiles/dialogs/dialogs_entry.cpp | 8 +- Telegram/SourceFiles/dialogs/dialogs_entry.h | 1 + .../dialogs/dialogs_indexed_list.cpp | 12 +- .../dialogs/dialogs_indexed_list.h | 51 +- .../dialogs/dialogs_inner_widget.cpp | 587 ++++++++++-------- .../dialogs/dialogs_inner_widget.h | 46 +- Telegram/SourceFiles/dialogs/dialogs_key.h | 14 +- Telegram/SourceFiles/dialogs/dialogs_list.cpp | 45 +- Telegram/SourceFiles/dialogs/dialogs_list.h | 49 +- .../SourceFiles/dialogs/dialogs_main_list.cpp | 6 +- .../SourceFiles/dialogs/dialogs_main_list.h | 4 +- .../dialogs/dialogs_pinned_list.cpp | 8 +- .../SourceFiles/dialogs/dialogs_pinned_list.h | 8 +- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 12 +- Telegram/SourceFiles/dialogs/dialogs_row.h | 30 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 6 +- .../SourceFiles/history/history_widget.cpp | 4 +- .../history/view/history_view_list_widget.cpp | 50 +- .../history/view/history_view_list_widget.h | 2 + Telegram/SourceFiles/main/main_session.cpp | 2 +- .../SourceFiles/ui/chat/choose_send_as.cpp | 2 +- 26 files changed, 600 insertions(+), 401 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e1ec8482c..cf58b25b2 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -386,7 +386,7 @@ void ApiWrap::checkChatInvite( void ApiWrap::savePinnedOrder(Data::Folder *folder) { const auto &order = _session->data().pinnedChatsOrder(folder); - const auto input = [](const Dialogs::Key &key) { + const auto input = [](Dialogs::Key key) { if (const auto history = key.history()) { return MTP_inputDialogPeer(history->peer->input); } else if (const auto folder = key.folder()) { @@ -409,7 +409,7 @@ void ApiWrap::savePinnedOrder(Data::Folder *folder) { void ApiWrap::savePinnedOrder(not_null forum) { const auto &order = _session->data().pinnedChatsOrder(forum); - const auto input = [](const Dialogs::Key &key) { + const auto input = [](Dialogs::Key key) { if (const auto topic = key.topic()) { return MTP_int(topic->rootId().bare); } diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 080590e45..bb33abb4a 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -795,7 +795,9 @@ ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) { } const auto row = [=] { if (_filter.isEmpty()) { - return _chatsIndexed->rowAtY(index, 1); + return (index < _chatsIndexed->size()) + ? (_chatsIndexed->begin() + index)->get() + : nullptr; } return (index < _filtered.size()) ? _filtered[index].get() @@ -865,9 +867,11 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) { if (_filter.isEmpty()) { if (!_chatsIndexed->empty()) { - auto i = _chatsIndexed->cfind(yFrom, _rowHeight); + const auto index = yFrom / _rowHeight; + auto i = _chatsIndexed->begin() + + std::min(index, _chatsIndexed->size());; for (auto end = _chatsIndexed->cend(); i != end; ++i) { - if (((*i)->pos() * _rowHeight) >= yTo) { + if (((*i)->index() * _rowHeight) >= yTo) { break; } (*i)->entry()->loadUserpic(); @@ -973,7 +977,8 @@ void ShareBox::Inner::paintEvent(QPaintEvent *e) { auto indexTo = rowTo * _columnCount; if (_filter.isEmpty()) { if (!_chatsIndexed->empty()) { - auto i = _chatsIndexed->cfind(indexFrom, 1); + auto i = _chatsIndexed->begin() + + std::min(indexFrom, _chatsIndexed->size()); for (auto end = _chatsIndexed->cend(); i != end; ++i) { if (indexFrom >= indexTo) { break; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 4c4d6a508..bb0390cdf 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1381,8 +1381,8 @@ void Session::setupUserIsContactViewer() { _contactsNoChatsList.addByName(history); } } else if (const auto history = historyLoaded(user)) { - _contactsNoChatsList.del(history); - _contactsList.del(history); + _contactsNoChatsList.remove(history); + _contactsList.remove(history); } }, _lifetime); } @@ -1915,7 +1915,7 @@ MessageIdsList Session::itemOrItsGroup(not_null item) const { } void Session::setChatPinned( - const Dialogs::Key &key, + Dialogs::Key key, FilterId filterId, bool pinned) { Expects(key.entry()->folderKnown()); @@ -1927,7 +1927,7 @@ void Session::setChatPinned( notifyPinnedDialogsOrderUpdated(); } -void Session::setPinnedFromEntryList(const Dialogs::Key &key, bool pinned) { +void Session::setPinnedFromEntryList(Dialogs::Key key, bool pinned) { Expects(key.entry()->folderKnown()); const auto list = chatsListFor(key.entry())->pinned(); @@ -2109,8 +2109,8 @@ void Session::clearPinnedChats(Data::Folder *folder) { void Session::reorderTwoPinnedChats( FilterId filterId, - const Dialogs::Key &key1, - const Dialogs::Key &key2) { + Dialogs::Key key1, + Dialogs::Key key2) { Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown()); Expects(filterId || (key1.entry()->folder() == key2.entry()->folder())); @@ -2510,14 +2510,14 @@ bool Session::unreadBadgeMuted() const { return computeUnreadBadgeMuted(_chatsList.unreadState()); } -int Session::unreadBadgeIgnoreOne(const Dialogs::Key &key) const { +int Session::unreadBadgeIgnoreOne(Dialogs::Key key) const { const auto remove = (key && key.entry()->inChatList()) ? key.entry()->chatListUnreadState() : Dialogs::UnreadState(); return computeUnreadBadge(_chatsList.unreadState() - remove); } -bool Session::unreadBadgeMutedIgnoreOne(const Dialogs::Key &key) const { +bool Session::unreadBadgeMutedIgnoreOne(Dialogs::Key key) const { if (!Core::App().settings().includeMutedCounter()) { return false; } @@ -3997,7 +3997,7 @@ void Session::refreshChatListEntry(Dialogs::Key key) { return; } else if (event.existenceChanged) { const auto mainRow = entry->addToChatList(0, mainList); - _contactsNoChatsList.del(key, mainRow); + _contactsNoChatsList.remove(key, mainRow); } else { event.moved = entry->adjustByPosInChatList(0, mainList); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index c3f908416..6e7f5f644 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -363,11 +363,8 @@ public: not_null forum) const; [[nodiscard]] const std::vector &pinnedChatsOrder( FilterId filterId) const; - void setChatPinned( - const Dialogs::Key &key, - FilterId filterId, - bool pinned); - void setPinnedFromEntryList(const Dialogs::Key &key, bool pinned); + void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned); + void setPinnedFromEntryList(Dialogs::Key key, bool pinned); void clearPinnedChats(Folder *folder); void applyPinnedChats( Folder *folder, @@ -377,8 +374,8 @@ public: const QVector &list); void reorderTwoPinnedChats( FilterId filterId, - const Dialogs::Key &key1, - const Dialogs::Key &key2); + Dialogs::Key key1, + Dialogs::Key key2); void setSuggestToGigagroup(not_null group, bool suggest); [[nodiscard]] bool suggestToGigagroup( @@ -474,9 +471,8 @@ public: [[nodiscard]] int unreadBadge() const; [[nodiscard]] bool unreadBadgeMuted() const; - [[nodiscard]] int unreadBadgeIgnoreOne(const Dialogs::Key &key) const; - [[nodiscard]] bool unreadBadgeMutedIgnoreOne( - const Dialogs::Key &key) const; + [[nodiscard]] int unreadBadgeIgnoreOne(Dialogs::Key key) const; + [[nodiscard]] bool unreadBadgeMutedIgnoreOne(Dialogs::Key key) const; [[nodiscard]] int unreadOnlyMutedBadge() const; [[nodiscard]] rpl::producer<> unreadBadgeChanges() const; void notifyUnreadBadgeChanged(); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 51f2b6f81..dcfa17e64 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -71,6 +71,9 @@ defaultDialogRow: DialogRow { textLeft: 68px; textTop: 34px; } +forumDialogRow: DialogRow { + height: 80px; +} dialogsOnlineBadgeStroke: 2px; dialogsOnlineBadgeSize: 10px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 20d705b63..830012a3f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -300,10 +300,10 @@ PositionChange Entry::adjustByPosInChatList( not_null list) { const auto links = chatListLinks(filterId); Assert(links != nullptr); - const auto from = links->main->pos(); + const auto from = links->main->top(); list->indexed()->adjustByDate(*links); - const auto to = links->main->pos(); - return { from, to }; + const auto to = links->main->top(); + return { .from = from, .to = to, .height = links->main->height() }; } void Entry::setChatListTimeId(TimeId date) { @@ -315,7 +315,7 @@ void Entry::setChatListTimeId(TimeId date) { } int Entry::posInChatList(FilterId filterId) const { - return mainChatListLink(filterId)->pos(); + return mainChatListLink(filterId)->index(); } not_null Entry::addToChatList( diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 0a3e70c5d..e8709ea53 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -56,6 +56,7 @@ enum class SortMode { struct PositionChange { int from = -1; int to = -1; + int height = 0; }; struct UnreadState { diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index 843646f0d..a733b1c1f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -134,7 +134,7 @@ void IndexedList::adjustByName( } for (auto ch : toRemove) { if (auto it = _index.find(ch); it != _index.cend()) { - it->second.del(key, mainRow); + it->second.remove(key, mainRow); } } if (!toAdd.empty()) { @@ -171,7 +171,7 @@ void IndexedList::adjustNames( history->removeChatListEntryByLetter(filterId, ch); } if (auto it = _index.find(ch); it != _index.cend()) { - it->second.del(key, mainRow); + it->second.remove(key, mainRow); } } for (auto ch : toAdd) { @@ -186,11 +186,11 @@ void IndexedList::adjustNames( } } -void IndexedList::del(Key key, Row *replacedBy) { - if (_list.del(key, replacedBy)) { +void IndexedList::remove(Key key, Row *replacedBy) { + if (_list.remove(key, replacedBy)) { for (const auto &ch : key.entry()->chatListFirstLetters()) { - if (auto it = _index.find(ch); it != _index.cend()) { - it->second.del(key, replacedBy); + if (const auto it = _index.find(ch); it != _index.cend()) { + it->second.remove(key, replacedBy); } } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index 99c362571..6e570ab6e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -37,39 +37,48 @@ public: not_null peer, const base::flat_set &oldChars); - void del(Key key, Row *replacedBy = nullptr); + void remove(Key key, Row *replacedBy = nullptr); void clear(); - const List &all() const { + [[nodiscard]] const List &all() const { return _list; } - const List *filtered(QChar ch) const { + [[nodiscard]] const List *filtered(QChar ch) const { const auto i = _index.find(ch); return (i != _index.end()) ? &i->second : nullptr; } - std::vector> filtered(const QStringList &words) const; + [[nodiscard]] std::vector> filtered( + const QStringList &words) const; // Part of List interface is duplicated here for all() list. - int size() const { return all().size(); } - bool empty() const { return all().empty(); } - bool contains(Key key) const { return all().contains(key); } - Row *getRow(Key key) const { return all().getRow(key); } - Row *rowAtY(int32 y, int32 h) const { return all().rowAtY(y, h); } + [[nodiscard]] int size() const { return all().size(); } + [[nodiscard]] bool empty() const { return all().empty(); } + [[nodiscard]] int height() const { return all().height(); } + [[nodiscard]] bool contains(Key key) const { + return all().contains(key); + } + [[nodiscard]] Row *getRow(Key key) const { return all().getRow(key); } + [[nodiscard]] Row *rowAtY(int y) const { return all().rowAtY(y); } using iterator = List::iterator; using const_iterator = List::const_iterator; - const_iterator cbegin() const { return all().cbegin(); } - const_iterator cend() const { return all().cend(); } - const_iterator begin() const { return all().cbegin(); } - const_iterator end() const { return all().cend(); } - iterator begin() { return all().begin(); } - iterator end() { return all().end(); } - const_iterator cfind(Row *value) const { return all().cfind(value); } - const_iterator find(Row *value) const { return all().cfind(value); } - iterator find(Row *value) { return all().find(value); } - const_iterator cfind(int y, int h) const { return all().cfind(y, h); } - const_iterator find(int y, int h) const { return all().cfind(y, h); } - iterator find(int y, int h) { return all().find(y, h); } + [[nodiscard]] const_iterator cbegin() const { return all().cbegin(); } + [[nodiscard]] const_iterator cend() const { return all().cend(); } + [[nodiscard]] const_iterator begin() const { return all().cbegin(); } + [[nodiscard]] const_iterator end() const { return all().cend(); } + [[nodiscard]] iterator begin() { return all().begin(); } + [[nodiscard]] iterator end() { return all().end(); } + [[nodiscard]] const_iterator cfind(Row *value) const { + return all().cfind(value); + } + [[nodiscard]] const_iterator find(Row *value) const { + return all().cfind(value); + } + [[nodiscard]] iterator find(Row *value) { return all().find(value); } + [[nodiscard]] const_iterator findByY(int y) const { + return all().findByY(y); + } + [[nodiscard]] iterator findByY(int y) { return all().findByY(y); } private: void adjustByName( diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 04f64f21b..e536127cd 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -131,11 +131,20 @@ struct InnerWidget::PeerSearchResult { BasicRow row; }; +Key InnerWidget::FilterResult::key() const { + return row->key(); +} + +int InnerWidget::FilterResult::bottom() const { + return top + row->height(); +} + InnerWidget::InnerWidget( QWidget *parent, not_null controller) : RpWidget(parent) , _controller(controller) +, _shownList(controller->session().data().chatsList()->indexed()) , _st(&st::defaultDialogRow) , _pinnedShiftAnimation([=](crl::time now) { return pinnedShiftAnimationCallback(now); @@ -318,9 +327,8 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { _collapsedSelected = -1; _collapsedRows.clear(); - const auto list = shownDialogs(); - const auto archive = !list->empty() - ? (*list->begin())->folder() + const auto archive = !_shownList->empty() + ? _shownList->begin()->get()->folder() : nullptr; const auto inMainMenu = session().settings().archiveInMainMenu(); if (archive && (session().settings().archiveCollapsed() || inMainMenu)) { @@ -330,13 +338,13 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { if (_pressed && _pressed->folder() == archive) { setPressed(nullptr); } - _skipTopDialogs = 1; + _skipTopDialog = true; if (!inMainMenu && !_filterId) { _collapsedRows.push_back( std::make_unique(archive)); } } else { - _skipTopDialogs = 0; + _skipTopDialog = false; } Assert(!needCollapsedRowsRefresh()); @@ -350,14 +358,20 @@ void InnerWidget::refreshWithCollapsedRows(bool toTop) { } } +int InnerWidget::skipTopHeight() const { + return (_skipTopDialog && !_shownList->empty()) + ? _shownList->begin()->get()->height() + : 0; +} + int InnerWidget::dialogsOffset() const { return _collapsedRows.size() * st::dialogsImportantBarHeight - - _skipTopDialogs * _st->height; + - skipTopHeight(); } int InnerWidget::fixedOnTopCount() const { auto result = 0; - for (const auto &row : *shownDialogs()) { + for (const auto &row : *_shownList) { if (row->entry()->fixedOnTopIndex()) { ++result; } else { @@ -367,17 +381,42 @@ int InnerWidget::fixedOnTopCount() const { return result; } +int InnerWidget::shownHeight(int till) const { + return !till + ? 0 + : (till > 0 && till < _shownList->size()) + ? (_shownList->begin() + till)->get()->top() + : _shownList->height(); +} + int InnerWidget::pinnedOffset() const { - return dialogsOffset() + fixedOnTopCount() * _st->height; + return dialogsOffset() + shownHeight(fixedOnTopCount()); } int InnerWidget::filteredOffset() const { return _hashtagResults.size() * st::mentionHeight; } +int InnerWidget::filteredIndex(int y) const { + return ranges::lower_bound( + _filterResults, + y, + ranges::less(), + &FilterResult::bottom + ) - begin(_filterResults); +} + +int InnerWidget::filteredHeight(int till) const { + return (!till || _filterResults.empty()) + ? 0 + : (till > 0 && till < _filterResults.size()) + ? _filterResults[till].top + : (_filterResults.back().top + _filterResults.back().row->height()); +} + int InnerWidget::peerSearchOffset() const { return filteredOffset() - + (_filterResults.size() * _st->height) + + filteredHeight() + st::searchedBarHeight; } @@ -412,6 +451,7 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { stopReorderPinned(); clearSelection(); _openedFolder = folder; + refreshShownList(); refreshWithCollapsedRows(true); if (_loadMoreCallback) { _loadMoreCallback(); @@ -438,6 +478,7 @@ void InnerWidget::changeOpenedForum(ChannelData *forum) { } _openedForum = forum ? forum->forum() : nullptr; _st = forum ? &st::forumTopicRow : &st::defaultDialogRow; + refreshShownList(); _openedForumLifetime.destroy(); if (forum) { @@ -477,10 +518,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { if (_state == WidgetState::Default) { paintCollapsedRows(p, r); - const auto rows = shownDialogs(); - const auto &list = rows->all(); - const auto shownCount = int(rows->size()) - _skipTopDialogs; - const auto otherStart = std::max(shownCount, 0) * _st->height; + const auto &list = _shownList->all(); + const auto shownBottom = _shownList->height() - skipTopHeight(); const auto active = activeEntry.key; const auto selected = _menuRow.key ? _menuRow.key @@ -491,7 +530,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { : (_selected ? _selected->key() : Key())); - if (otherStart) { + if (shownBottom) { const auto skip = dialogsOffset(); auto reorderingPinned = (_aboveIndex >= 0 && !_pinnedRows.empty()); if (reorderingPinned) { @@ -499,8 +538,9 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } const auto promoted = fixedOnTopCount(); + const auto skippedTop = skipTopHeight(); const auto paintDialog = [&](not_null row) { - const auto pinned = row->pos() - promoted; + const auto pinned = row->index() - promoted; const auto count = _pinnedRows.size(); const auto xadd = 0; const auto yadd = base::in_range(pinned, 0, count) @@ -529,48 +569,48 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } }; - auto i = list.cfind(dialogsClip.top() - skip, _st->height); - while (i != list.cend() && (*i)->pos() < _skipTopDialogs) { + auto i = list.findByY(dialogsClip.top() - skip); + if (_skipTopDialog && i != list.cend() && !(*i)->index()) { ++i; } if (i != list.cend()) { - auto lastPaintedPos = (*i)->pos(); + auto top = (*i)->top(); // If we're reordering pinned chats we need to fill this area background first. if (reorderingPinned) { - p.fillRect(0, (promoted - _skipTopDialogs) * _st->height, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg); + const auto pinnedBottom = shownHeight(promoted + _pinnedRows.size()); + const auto pinnedTop = shownHeight(promoted); + p.fillRect(0, pinnedTop - skippedTop, fullWidth, pinnedBottom - pinnedTop, st::dialogsBg); } - p.translate(0, (lastPaintedPos - _skipTopDialogs) * _st->height); + p.translate(0, top - skippedTop); for (auto e = list.cend(); i != e; ++i) { auto row = (*i); - if ((lastPaintedPos - _skipTopDialogs) * _st->height >= dialogsClip.top() - skip + dialogsClip.height()) { + if (top - skippedTop >= dialogsClip.top() - skip + dialogsClip.height()) { break; } // Skip currently dragged chat to paint it above others after. - if (lastPaintedPos != promoted + _aboveIndex - || _aboveIndex < 0) { + if (row->index() != promoted + _aboveIndex || _aboveIndex < 0) { paintDialog(row); } - p.translate(0, _st->height); - ++lastPaintedPos; + p.translate(0, row->height()); + top += row->height(); } // Paint the dragged chat above all others. if (_aboveIndex >= 0) { - auto i = list.cfind(promoted + _aboveIndex, 1); - auto pos = (i == list.cend()) ? -1 : (*i)->pos(); - if (pos == promoted + _aboveIndex) { - p.translate(0, (pos - lastPaintedPos) * _st->height); + const auto index = promoted + _aboveIndex; + if (index < list.size()) { + const auto row = *(list.cbegin() + index); + p.translate(0, row->top() - top); paintDialog(*i); - p.translate(0, (lastPaintedPos - pos) * _st->height); + p.translate(0, top - row->top()); } } } - } - if (!otherStart) { + } else { p.fillRect(dialogsClip, st::dialogsBg); } } else if (_state == WidgetState::Filtered) { @@ -623,34 +663,34 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } if (!_filterResults.empty()) { auto skip = filteredOffset(); - auto from = floorclamp(r.y() - skip, _st->height, 0, _filterResults.size()); - auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _filterResults.size()); - p.translate(0, from * _st->height); - if (from < _filterResults.size()) { - for (; from < to; ++from) { - const auto row = _filterResults[from]; - const auto key = row->key(); - const auto active = (activeEntry.key == key) - && !activeEntry.fullId; - const auto selected = _menuRow.key - ? (key == _menuRow.key) - : (from == (isPressed() - ? _filteredPressed - : _filteredSelected)); - Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), { - .st = _st, - .folder = _openedFolder, - .forum = _openedForum, - .filter = _filterId, - .now = ms, - .width = fullWidth, - .active = active, - .selected = selected, - .paused = videoPaused, - .narrow = (fullWidth < st::columnMinimalWidthLeft), - }); - p.translate(0, _st->height); - } + auto from = filteredIndex(r.y() - skip); + auto to = std::min( + filteredIndex(r.y() + r.height() - skip) + 1, + int(_filterResults.size())); + p.translate(0, filteredHeight(from)); + for (; from < to; ++from) { + const auto row = _filterResults[from].row; + const auto key = row->key(); + const auto active = (activeEntry.key == key) + && !activeEntry.fullId; + const auto selected = _menuRow.key + ? (key == _menuRow.key) + : (from == (isPressed() + ? _filteredPressed + : _filteredSelected)); + Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), { + .st = _st, + .folder = _openedFolder, + .forum = _openedForum, + .filter = _filterId, + .now = ms, + .width = fullWidth, + .active = active, + .selected = selected, + .paused = videoPaused, + .narrow = (fullWidth < st::columnMinimalWidthLeft), + }); + p.translate(0, row->height()); } } @@ -1129,7 +1169,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { const auto selected = (collapsedSelected >= 0) ? nullptr : (mouseY >= offset) - ? shownDialogs()->rowAtY(mouseY - offset, _st->height) + ? _shownList->rowAtY(mouseY - offset) : nullptr; if (_selected != selected || _collapsedSelected != collapsedSelected) { updateSelectedRow(); @@ -1160,7 +1200,9 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { } if (!_filterResults.empty()) { auto skip = filteredOffset(); - auto filteredSelected = (mouseY >= skip) ? ((mouseY - skip) / _st->height) : -1; + auto filteredSelected = (mouseY >= skip) + ? filteredIndex(mouseY - skip) + : -1; if (filteredSelected < 0 || filteredSelected >= _filterResults.size()) { filteredSelected = -1; } @@ -1219,8 +1261,8 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { } else if (_pressed) { auto row = _pressed; row->addRipple( - e->pos() - QPoint(0, dialogsOffset() + _pressed->pos() * _st->height), - QSize(width(), _st->height), + e->pos() - QPoint(0, dialogsOffset() + _pressed->top()), + QSize(width(), _pressed->height()), [this, row] { if (!_pinnedShiftAnimation.animating()) { row->entry()->updateChatListEntry(); @@ -1233,11 +1275,12 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { update(0, index * st::mentionHeight, width(), st::mentionHeight); }); } else if (base::in_range(_filteredPressed, 0, _filterResults.size())) { - const auto row = _filterResults[_filteredPressed]; + const auto &result = _filterResults[_filteredPressed]; + const auto row = result.row; const auto filterId = _filterId; row->addRipple( - e->pos() - QPoint(0, filteredOffset() + _filteredPressed * _st->height), - QSize(width(), _st->height), + e->pos() - QPoint(0, filteredOffset() + result.top), + QSize(width(), row->height()), [=] { repaintDialogRow(filterId, row); }); } else if (base::in_range(_peerSearchPressed, 0, _peerSearchResults.size())) { auto &result = _peerSearchResults[_peerSearchPressed]; @@ -1248,7 +1291,10 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { [this, peer = result->peer] { updateSearchResult(peer); }); } else if (base::in_range(_searchedPressed, 0, _searchResults.size())) { auto &row = _searchResults[_searchedPressed]; - row->addRipple(e->pos() - QPoint(0, searchedOffset() + _searchedPressed * _st->height), QSize(width(), _st->height), row->repaint()); + row->addRipple( + e->pos() - QPoint(0, searchedOffset() + _searchedPressed * _st->height), + QSize(width(), _st->height), + row->repaint()); } if (anim::Disabled() && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { @@ -1290,7 +1336,7 @@ int InnerWidget::countPinnedIndex(Row *ofRow) { return -1; } auto result = 0; - for (const auto &row : *shownDialogs()) { + for (const auto &row : *_shownList) { if (row->entry()->fixedOnTopIndex()) { continue; } else if (!row->entry()->isPinnedDialog(_filterId)) { @@ -1351,7 +1397,7 @@ int InnerWidget::updateReorderIndexGetCount() { return 0; } - const auto count = Dialogs::PinnedDialogsCount(_filterId, shownDialogs()); + const auto count = Dialogs::PinnedDialogsCount(_filterId, _shownList); Assert(index < count); if (count < 2) { stopReorderPinned(); @@ -1376,7 +1422,6 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { return false; } - const auto list = shownDialogs(); auto yaddWas = _pinnedRows[_draggingIndex].yadd.current(); auto shift = 0; auto now = crl::now(); @@ -1384,7 +1429,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { shift = -floorclamp(_dragStart.y() - localPosition.y() + (_st->height / 2), _st->height, 0, _draggingIndex); for (auto from = _draggingIndex, to = _draggingIndex + shift; from > to; --from) { - list->movePinned(_dragging, -1); + _shownList->movePinned(_dragging, -1); std::swap(_pinnedRows[from], _pinnedRows[from - 1]); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - _st->height, 0); _pinnedRows[from].animStartTime = now; @@ -1393,7 +1438,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) { shift = floorclamp(localPosition.y() - _dragStart.y() + (_st->height / 2), _st->height, 0, pinnedCount - _draggingIndex - 1); for (auto from = _draggingIndex, to = _draggingIndex + shift; from < to; ++from) { - list->movePinned(_dragging, 1); + _shownList->movePinned(_dragging, 1); std::swap(_pinnedRows[from], _pinnedRows[from + 1]); _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + _st->height, 0); _pinnedRows[from].animStartTime = now; @@ -1559,7 +1604,7 @@ void InnerWidget::setHashtagPressed(int pressed) { void InnerWidget::setFilteredPressed(int pressed) { if (base::in_range(_filteredPressed, 0, _filterResults.size())) { - _filterResults[_filteredPressed]->stopLastRipple(); + _filterResults[_filteredPressed].row->stopLastRipple(); } _filteredPressed = pressed; } @@ -1597,9 +1642,9 @@ void InnerWidget::dialogRowReplaced( Row *newRow) { if (_state == WidgetState::Filtered) { for (auto i = _filterResults.begin(); i != _filterResults.end();) { - if (*i == oldRow) { // this row is shown in filtered and maybe is in contacts! + if (i->row == oldRow) { // this row is shown in filtered and maybe is in contacts! if (newRow) { - *i = newRow; + i->row = newRow; ++i; } else { i = _filterResults.erase(i); @@ -1637,8 +1682,8 @@ void InnerWidget::handleChatListEntryRefreshes() { } }) | rpl::start_with_next([=](const Event &event) { const auto offset = dialogsOffset(); - const auto from = offset + event.moved.from * _st->height; - const auto to = offset + event.moved.to * _st->height; + const auto from = offset + event.moved.from; + const auto to = offset + event.moved.to; const auto &key = event.key; const auto entry = key.entry(); @@ -1663,7 +1708,10 @@ void InnerWidget::handleChatListEntryRefreshes() { if (_pressed && _pressed->key() == key) { setPressed(nullptr); } - const auto i = ranges::find(_filterResults, key, &Row::key); + const auto i = ranges::find( + _filterResults, + key, + &FilterResult::key); if (i != _filterResults.end()) { if (_filteredSelected == (i - _filterResults.begin()) && (i + 1) == _filterResults.end()) { @@ -1679,7 +1727,7 @@ void InnerWidget::handleChatListEntryRefreshes() { 0, std::min(from, to), width(), - std::abs(from - to) + _st->height); + std::abs(from - to) + event.moved.height); } }, lifetime()); } @@ -1694,12 +1742,12 @@ void InnerWidget::repaintCollapsedFolderRow(not_null folder) { } int InnerWidget::defaultRowTop(not_null row) const { - const auto position = row->pos(); + const auto index = row->index(); auto top = dialogsOffset(); - if (base::in_range(position, 0, _pinnedRows.size())) { - top += qRound(_pinnedRows[position].yadd.current()); + if (base::in_range(index, 0, _pinnedRows.size())) { + top += qRound(_pinnedRows[index].yadd.current()); } - return top + position * _st->height; + return top + row->top(); } void InnerWidget::repaintDialogRow( @@ -1710,17 +1758,18 @@ void InnerWidget::repaintDialogRow( if (const auto folder = row->folder()) { repaintCollapsedFolderRow(folder); } - update(0, defaultRowTop(row), width(), _st->height); + update(0, defaultRowTop(row), width(), row->height()); } } else if (_state == WidgetState::Filtered) { if (!filterId) { for (auto i = 0, l = int(_filterResults.size()); i != l; ++i) { - if (_filterResults[i]->key() == row->key()) { + const auto &result = _filterResults[i]; + if (result.key() == row->key()) { update( 0, - filteredOffset() + i * _st->height, + filteredOffset() + result.top, width(), - _st->height); + result.row->height()); break; } } @@ -1765,9 +1814,6 @@ void InnerWidget::updateDialogRow( RowDescriptor row, QRect updateRect, UpdateRowSections sections) { - if (updateRect.isEmpty()) { - updateRect = QRect(0, 0, width(), _st->height); - } if (IsServerMsgId(-row.fullId.msg)) { if (const auto peer = row.key.peer()) { if (const auto from = peer->migrateFrom()) { @@ -1780,50 +1826,49 @@ void InnerWidget::updateDialogRow( } } - const auto updateRow = [&](int rowTop, int rowHeight = 0) { - rtlupdate( - updateRect.x(), - rowTop + updateRect.y(), - updateRect.width(), - rowHeight ? rowHeight : updateRect.height()); + const auto updateRow = [&](int rowTop, int rowHeight) { + if (!updateRect.isEmpty()) { + rtlupdate(updateRect.translated(0, rowTop)); + } else { + rtlupdate(0, rowTop, width(), rowHeight); + } }; if (_state == WidgetState::Default) { if (sections & UpdateRowSection::Default) { if (const auto folder = row.key.folder()) { repaintCollapsedFolderRow(folder); } - if (const auto dialog = shownDialogs()->getRow(row.key)) { - const auto position = dialog->pos(); + if (const auto dialog = _shownList->getRow(row.key)) { + const auto position = dialog->index(); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { top += qRound(_pinnedRows[position].yadd.current()); } - updateRow(top + position * _st->height); + updateRow(top + dialog->top(), dialog->height()); } } } else if (_state == WidgetState::Filtered) { if ((sections & UpdateRowSection::Filtered) && !_filterResults.empty()) { - const auto add = filteredOffset(); - auto index = 0; - for (const auto result : _filterResults) { - if (result->key() == row.key) { - updateRow(add + index * _st->height); + for (const auto &result : _filterResults) { + if (result.key() == row.key) { + updateRow( + filteredOffset() + result.top, + result.row->height()); break; } - ++index; } } if ((sections & UpdateRowSection::PeerSearch) && !_peerSearchResults.empty()) { if (const auto peer = row.key.peer()) { - const auto add = peerSearchOffset(); + const auto rowHeight = st::dialogsRowHeight; auto index = 0; for (const auto &result : _peerSearchResults) { if (result->peer == peer) { updateRow( - add + index * st::dialogsRowHeight, - st::dialogsRowHeight); + peerSearchOffset() + index * rowHeight, + rowHeight); break; } ++index; @@ -1836,9 +1881,7 @@ void InnerWidget::updateDialogRow( auto index = 0; for (const auto &result : _searchResults) { if (isSearchResultActive(result.get(), row)) { - updateRow( - add + index * _st->height, - _st->height); + updateRow(add + index * _st->height, _st->height); break; } ++index; @@ -1851,36 +1894,62 @@ void InnerWidget::enterEventHook(QEnterEvent *e) { setMouseTracking(true); } +Row *InnerWidget::shownRowByKey(Key key) { + const auto entry = key.entry(); + if (_openedForum) { + const auto topic = entry->asTopic(); + if (!topic || topic->forum() != _openedForum) { + return nullptr; + } + } else if (_openedFolder) { + const auto history = entry->asHistory(); + if (!history || history->folder() != _openedFolder) { + return nullptr; + } + } else { + const auto history = entry->asHistory(); + if (!entry->asFolder() && (!history || history->folder())) { + return nullptr; + } + } + const auto links = entry->chatListLinks(FilterId()); + return links ? links->main.get() : nullptr; +} + void InnerWidget::updateSelectedRow(Key key) { if (_state == WidgetState::Default) { if (key) { - const auto entry = key.entry(); - if (!entry->inChatList(_filterId)) { + const auto row = shownRowByKey(key); + if (!row) { return; } - auto position = entry->posInChatList(_filterId); + auto position = row->index(); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { top += qRound(_pinnedRows[position].yadd.current()); } - update(0, top + position * _st->height, width(), _st->height); + update(0, top + row->top(), width(), row->height()); } else if (_selected) { - update(0, dialogsOffset() + _selected->pos() * _st->height, width(), _st->height); + update(0, dialogsOffset() + _selected->top(), width(), _selected->height()); } else if (_collapsedSelected >= 0) { update(0, _collapsedSelected * st::dialogsImportantBarHeight, width(), st::dialogsImportantBarHeight); } } else if (_state == WidgetState::Filtered) { if (key) { for (auto i = 0, l = int(_filterResults.size()); i != l; ++i) { - if (_filterResults[i]->key() == key) { - update(0, filteredOffset() + i * _st->height, width(), _st->height); + const auto &result = _filterResults[i]; + if (result.key() == key) { + update(0, filteredOffset() + result.top, width(), result.row->height()); break; } } } else if (_hashtagSelected >= 0) { update(0, _hashtagSelected * st::mentionHeight, width(), st::mentionHeight); } else if (_filteredSelected >= 0) { - update(0, filteredOffset() + _filteredSelected * _st->height, width(), _st->height); + if (_filteredSelected < _filterResults.size()) { + const auto &result = _filterResults[_filteredSelected]; + update(0, filteredOffset() + result.top, width(), result.row->height()); + } } else if (_peerSearchSelected >= 0) { update(0, peerSearchOffset() + _peerSearchSelected * st::dialogsRowHeight, width(), st::dialogsRowHeight); } else if (_searchedSelected >= 0) { @@ -1889,8 +1958,8 @@ void InnerWidget::updateSelectedRow(Key key) { } } -not_null InnerWidget::shownDialogs() const { - return _openedForum +void InnerWidget::refreshShownList() { + _shownList = _openedForum ? _openedForum->topicsList()->indexed() : _filterId ? session().data().chatsFilters().chatsList(_filterId)->indexed() @@ -1971,7 +2040,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } } else if (_state == WidgetState::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { - return { _filterResults[_filteredSelected]->key(), FullMsgId() }; + return { _filterResults[_filteredSelected].key(), FullMsgId() }; } else if (base::in_range(_searchedSelected, 0, _searchResults.size())) { return { _searchResults[_searchedSelected]->item()->history(), @@ -2053,10 +2122,15 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { _filterResultsGlobal.clear(); const auto append = [&](not_null list) { const auto results = list->filtered(words); - _filterResults.insert( + auto top = filteredHeight(); + auto i = _filterResults.insert( end(_filterResults), begin(results), end(results)); + for (const auto e = end(_filterResults); i != e; ++i) { + i->top = top; + top += i->row->height(); + } }; if (!_searchInChat && !_searchFromPeer && !words.isEmpty()) { if (_openedForum) { @@ -2111,13 +2185,15 @@ void InnerWidget::onHashtagFilterUpdate(QStringView newFilter) { void InnerWidget::appendToFiltered(Key key) { for (const auto &row : _filterResults) { - if (row->key() == key) { + if (row.key() == key) { return; } } - auto row = std::make_unique(key, _filterResults.size()); + auto row = std::make_unique(key, 0, 0); const auto [i, ok] = _filterResultsGlobal.emplace(key, std::move(row)); + const auto height = filteredHeight(); _filterResults.emplace_back(i->second.get()); + _filterResults.back().top = height; trackSearchResultsHistory(key.owningHistory()); } @@ -2159,7 +2235,10 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { if (topic->channel() == channel) { removed = true; _filterResults.erase( - ranges::remove(_filterResults, i->first, &Row::key), + ranges::remove( + _filterResults, + i->first, + &FilterResult::key), end(_filterResults)); i = _filterResultsGlobal.erase(i); continue; @@ -2188,8 +2267,8 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { } const auto ffrom = ranges::remove( _filterResults, - topic.get(), - &Row::topic); + Key(topic), + &FilterResult::key); if (ffrom != end(_filterResults)) { _filterResults.erase(ffrom, end(_filterResults)); removed = true; @@ -2213,7 +2292,7 @@ Data::Thread *InnerWidget::updateFromParentDrag(QPoint globalPosition) { return fromRow(_selected); } else if (_state == WidgetState::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { - return fromRow(_filterResults[_filteredSelected]); + return fromRow(_filterResults[_filteredSelected].row); } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { return session().data().history( _peerSearchResults[_peerSearchSelected]->peer); @@ -2336,8 +2415,8 @@ bool InnerWidget::hasHistoryInResults(not_null history) const { } const auto inFilteredResults = ranges::find( _filterResults, - history.get(), - [](Row *row) { return row->history(); } + Key(history), + &FilterResult::key ) != end(_filterResults); if (inFilteredResults) { return true; @@ -2459,9 +2538,8 @@ Data::Forum *InnerWidget::shownForum() const { } bool InnerWidget::needCollapsedRowsRefresh() const { - const auto list = shownDialogs(); - const auto archive = !list->empty() - ? (*list->begin())->folder() + const auto archive = !_shownList->empty() + ? _shownList->begin()->get()->folder() : nullptr; const auto collapsedHasArchive = !_collapsedRows.empty() && (_collapsedRows.back()->folder != nullptr); @@ -2470,10 +2548,10 @@ bool InnerWidget::needCollapsedRowsRefresh() const { const auto archiveIsInMainMenu = (archive != nullptr) && session().settings().archiveInMainMenu(); return archiveIsInMainMenu - ? (collapsedHasArchive || _skipTopDialogs != 1) + ? (collapsedHasArchive || !_skipTopDialog) : archiveIsCollapsed - ? (!collapsedHasArchive || _skipTopDialogs != 1) - : (collapsedHasArchive || _skipTopDialogs != 0); + ? (!collapsedHasArchive || !_skipTopDialog) + : (collapsedHasArchive || _skipTopDialog); } void InnerWidget::editOpenedFilter() { @@ -2487,13 +2565,12 @@ void InnerWidget::refresh(bool toTop) { return refreshWithCollapsedRows(toTop); } refreshEmptyLabel(); - const auto list = shownDialogs(); auto h = 0; if (_state == WidgetState::Default) { - if (list->empty()) { + if (_shownList->empty()) { h = st::dialogsEmptyHeight; } else { - h = dialogsOffset() + list->size() * _st->height; + h = dialogsOffset() + _shownList->height(); } } else if (_state == WidgetState::Filtered) { if (_waitingForSearch) { @@ -2515,7 +2592,7 @@ void InnerWidget::refresh(bool toTop) { void InnerWidget::refreshEmptyLabel() { const auto data = &session().data(); - const auto state = !shownDialogs()->empty() + const auto state = !_shownList->empty() ? EmptyState::None : _openedForum ? (_openedForum->topicsList()->loaded() @@ -2714,42 +2791,39 @@ void InnerWidget::clearFilter() { void InnerWidget::selectSkip(int32 direction) { clearMouseSelection(); if (_state == WidgetState::Default) { - const auto list = shownDialogs(); - if (_collapsedRows.empty() && list->size() <= _skipTopDialogs) { + const auto skip = _skipTopDialog ? 1 : 0; + if (_collapsedRows.empty() && _shownList->size() <= skip) { return; } if (_collapsedSelected < 0 && !_selected) { if (!_collapsedRows.empty()) { _collapsedSelected = 0; } else { - _selected = *(list->cbegin() + _skipTopDialogs); + _selected = (_shownList->cbegin() + skip)->get(); } } else { auto cur = (_collapsedSelected >= 0) ? _collapsedSelected : int(_collapsedRows.size() - + (list->cfind(_selected) - list->cbegin() - _skipTopDialogs)); + + (_shownList->cfind(_selected) + - _shownList->cbegin() + - skip)); cur = std::clamp( cur + direction, 0, static_cast(_collapsedRows.size() - + list->size() - - _skipTopDialogs + + _shownList->size() + - skip - 1)); if (cur < _collapsedRows.size()) { _collapsedSelected = cur; _selected = nullptr; } else { _collapsedSelected = -1; - _selected = *(list->cbegin() + _skipTopDialogs + cur - _collapsedRows.size()); + _selected = *(_shownList->cbegin() + skip + cur - _collapsedRows.size()); } } - if (_collapsedSelected >= 0 || _selected) { - const auto fromY = (_collapsedSelected >= 0) - ? (_collapsedSelected * st::dialogsImportantBarHeight) - : (dialogsOffset() + _selected->pos() * _st->height); - _mustScrollTo.fire({ fromY, fromY + _st->height }); - } + scrollToDefaultSelected(); } else if (_state == WidgetState::Filtered) { if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _searchResults.empty()) { return; @@ -2797,115 +2871,120 @@ void InnerWidget::selectSkip(int32 direction) { } } if (base::in_range(_hashtagSelected, 0, _hashtagResults.size())) { - _mustScrollTo.fire({ - _hashtagSelected * st::mentionHeight, - (_hashtagSelected + 1) * st::mentionHeight, - }); + const auto from = _hashtagSelected * st::mentionHeight; + scrollToItem(from, st::mentionHeight); } else if (base::in_range(_filteredSelected, 0, _filterResults.size())) { - _mustScrollTo.fire({ - filteredOffset() + _filteredSelected * _st->height, - filteredOffset() - + (_filteredSelected + 1) * _st->height, - }); + const auto &result = _filterResults[_filteredSelected]; + const auto from = filteredOffset() + result.top; + scrollToItem(from, result.row->height()); } else if (base::in_range(_peerSearchSelected, 0, _peerSearchResults.size())) { - _mustScrollTo.fire({ - peerSearchOffset() - + _peerSearchSelected * st::dialogsRowHeight - + (_peerSearchSelected ? 0 : -st::searchedBarHeight), - peerSearchOffset() - + (_peerSearchSelected + 1) * st::dialogsRowHeight, - }); + const auto from = peerSearchOffset() + + _peerSearchSelected * st::dialogsRowHeight + + (_peerSearchSelected ? 0 : -st::searchedBarHeight); + const auto height = st::dialogsRowHeight + + (_peerSearchSelected ? 0 : st::searchedBarHeight); + scrollToItem(from, height); } else { - _mustScrollTo.fire({ - searchedOffset() - + _searchedSelected * _st->height - + (_searchedSelected ? 0 : -st::searchedBarHeight), - searchedOffset() - + (_searchedSelected + 1) * _st->height, - }); + const auto from = searchedOffset() + + _searchedSelected * _st->height + + (_searchedSelected ? 0 : -st::searchedBarHeight); + const auto height = _st->height + + (_searchedSelected ? 0 : st::searchedBarHeight); + scrollToItem(from, height); } } update(); } void InnerWidget::scrollToEntry(const RowDescriptor &entry) { - auto fromY = -1; - auto rowHeight = _st->height; if (_state == WidgetState::Default) { - if (auto row = shownDialogs()->getRow(entry.key)) { - fromY = dialogsOffset() + row->pos() * _st->height; + if (auto row = _shownList->getRow(entry.key)) { + scrollToItem(dialogsOffset() + row->top(), row->height()); } } else if (_state == WidgetState::Filtered) { for (int32 i = 0, c = _searchResults.size(); i < c; ++i) { if (isSearchResultActive(_searchResults[i].get(), entry)) { - fromY = searchedOffset() + i * _st->height; - rowHeight = _st->height; - break; + const auto from = searchedOffset() + i * _st->height; + scrollToItem(from, _st->height); + return; } } - if (fromY < 0) { - for (auto i = 0, c = int(_filterResults.size()); i != c; ++i) { - if (_filterResults[i]->key() == entry.key) { - fromY = filteredOffset() + (i * _st->height); - break; - } + for (auto i = 0, c = int(_filterResults.size()); i != c; ++i) { + auto &result = _filterResults[i]; + if (result.key() == entry.key) { + const auto from = filteredOffset() + result.top; + scrollToItem(from, result.row->height()); + return; } } } - if (fromY >= 0) { - _mustScrollTo.fire({ fromY, fromY + rowHeight }); - } } void InnerWidget::selectSkipPage(int32 pixels, int32 direction) { clearMouseSelection(); - const auto list = shownDialogs(); int toSkip = pixels / _st->height; - if (_state == WidgetState::Default) { - if (!_selected) { - if (direction > 0 && list->size() > _skipTopDialogs) { - _selected = *(list->cbegin() + _skipTopDialogs); - _collapsedSelected = -1; - } else { - return; - } - } - if (direction > 0) { - for (auto i = list->cfind(_selected), end = list->cend(); i != end && (toSkip--); ++i) { - _selected = *i; - } + if (_state != WidgetState::Default) { + selectSkip(direction * toSkip); + return; + } + const auto skip = _skipTopDialog ? 1 : 0; + if (!_selected) { + if (direction > 0 && _shownList->size() > skip) { + _selected = (_shownList->cbegin() + skip)->get(); + _collapsedSelected = -1; } else { - for (auto i = list->cfind(_selected), b = list->cbegin(); i != b && (*i)->pos() > _skipTopDialogs && (toSkip--);) { - _selected = *(--i); - } - if (toSkip && !_collapsedRows.empty()) { - _collapsedSelected = std::max(int(_collapsedRows.size()) - toSkip, 0); - _selected = nullptr; - } + return; } - if (_collapsedSelected >= 0 || _selected) { - const auto fromY = (_collapsedSelected >= 0) - ? (_collapsedSelected * st::dialogsImportantBarHeight) - : (dialogsOffset() + _selected->pos() * _st->height); - _mustScrollTo.fire({ fromY, fromY + _st->height }); + } + if (direction > 0) { + for (auto i = _shownList->cfind(_selected), end = _shownList->cend() + ; i != end && (toSkip--) + ; ++i) { + _selected = *i; } } else { - return selectSkip(direction * toSkip); + for (auto i = _shownList->cfind(_selected), b = _shownList->cbegin() + ; i != b && (*i)->index() > skip && (toSkip--) + ;) { + _selected = *(--i); + } + if (toSkip && !_collapsedRows.empty()) { + _collapsedSelected = std::max(int(_collapsedRows.size()) - toSkip, 0); + _selected = nullptr; + } } + scrollToDefaultSelected(); update(); } +void InnerWidget::scrollToItem(int top, int height) { + _mustScrollTo.fire({ top, top + height }); +} + +void InnerWidget::scrollToDefaultSelected() { + Expects(_state == WidgetState::Default); + + if (_collapsedSelected >= 0) { + const auto from = _collapsedSelected * st::dialogsImportantBarHeight; + scrollToItem(from, st::dialogsImportantBarHeight); + } else if (_selected) { + const auto from = dialogsOffset() + _selected->top(); + scrollToItem(from, _selected->height()); + } +} + void InnerWidget::loadPeerPhotos() { if (!parentWidget()) return; - const auto list = shownDialogs(); auto yFrom = _visibleTop; auto yTo = _visibleTop + (_visibleBottom - _visibleTop) * (PreloadHeightsCount + 1); if (_state == WidgetState::Default) { - auto otherStart = list->size() * _st->height; + auto otherStart = _shownList->size() * _st->height; if (yFrom < otherStart) { - for (auto i = list->cfind(yFrom, _st->height), end = list->cend(); i != end; ++i) { - if (((*i)->pos() * _st->height) >= yTo) { + for (auto i = _shownList->findByY(yFrom), end = _shownList->cend() + ; i != end + ; ++i) { + if (((*i)->index() * _st->height) >= yTo) { break; } (*i)->entry()->loadUserpic(); @@ -2923,7 +3002,7 @@ void InnerWidget::loadPeerPhotos() { if (to > _filterResults.size()) to = _filterResults.size(); for (; from < to; ++from) { - _filterResults[from]->entry()->loadUserpic(); + _filterResults[from].key().entry()->loadUserpic(); } } @@ -2979,10 +3058,12 @@ void InnerWidget::switchToFilter(FilterId filterId) { saveChatsFilterScrollState(_filterId); if (_openedFolder) { _filterId = filterId; + refreshShownList(); } else { clearSelection(); stopReorderPinned(); _filterId = filterId; + refreshShownList(); refreshWithCollapsedRows(true); } refreshEmptyLabel(); @@ -2997,7 +3078,7 @@ void InnerWidget::switchToFilter(FilterId filterId) { } void InnerWidget::saveChatsFilterScrollState(FilterId filterId) { - _chatsFilterScrollStates[filterId] = -pos().y(); + _chatsFilterScrollStates[filterId] = -y(); } void InnerWidget::restoreChatsFilterScrollState(FilterId filterId) { @@ -3046,7 +3127,7 @@ ChosenRow InnerWidget::computeChosenRow() const { } else if (_state == WidgetState::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { return { - _filterResults[_filteredSelected]->key(), + _filterResults[_filteredSelected].key(), Data::UnreadMessagePosition, true }; @@ -3099,10 +3180,9 @@ RowDescriptor InnerWidget::chatListEntryBefore( return RowDescriptor(); } if (_state == WidgetState::Default) { - const auto list = shownDialogs(); - if (const auto row = list->getRow(which.key)) { - const auto i = list->cfind(row); - if (i != list->cbegin()) { + if (const auto row = _shownList->getRow(which.key)) { + const auto i = _shownList->cfind(row); + if (i != _shownList->cbegin()) { return RowDescriptor( (*(i - 1))->key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); @@ -3130,7 +3210,7 @@ RowDescriptor InnerWidget::chatListEntryBefore( return RowDescriptor(); } return RowDescriptor( - _filterResults.back()->key(), + _filterResults.back().key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); } return RowDescriptor( @@ -3144,7 +3224,7 @@ RowDescriptor InnerWidget::chatListEntryBefore( return RowDescriptor(); } return RowDescriptor( - _filterResults.back()->key(), + _filterResults.back().key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); } if (!_peerSearchResults.empty()) { @@ -3156,14 +3236,14 @@ RowDescriptor InnerWidget::chatListEntryBefore( } } } - if (_filterResults.empty() || _filterResults[0]->key() == which.key) { + if (_filterResults.empty() || _filterResults[0].key() == which.key) { return RowDescriptor(); } for (auto b = _filterResults.cbegin(), i = b + 1, e = _filterResults.cend(); i != e; ++i) { - if ((*i)->key() == which.key) { + if (i->key() == which.key) { return RowDescriptor( - (*(i - 1))->key(), + (i - 1)->key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); } } @@ -3176,10 +3256,9 @@ RowDescriptor InnerWidget::chatListEntryAfter( return RowDescriptor(); } if (_state == WidgetState::Default) { - const auto list = shownDialogs(); - if (const auto row = list->getRow(which.key)) { - const auto i = list->cfind(row) + 1; - if (i != list->cend()) { + if (const auto row = _shownList->getRow(which.key)) { + const auto i = _shownList->cfind(row) + 1; + if (i != _shownList->cend()) { return RowDescriptor( (*i)->key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); @@ -3218,11 +3297,11 @@ RowDescriptor InnerWidget::chatListEntryAfter( } } for (auto i = _filterResults.cbegin(), e = _filterResults.cend(); i != e; ++i) { - if ((*i)->key() == which.key) { + if ((*i).key() == which.key) { ++i; if (i != e) { return RowDescriptor( - (*i)->key(), + (*i).key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); } else if (!_peerSearchResults.empty()) { return RowDescriptor( @@ -3241,9 +3320,8 @@ RowDescriptor InnerWidget::chatListEntryAfter( RowDescriptor InnerWidget::chatListEntryFirst() const { if (_state == WidgetState::Default) { - const auto list = shownDialogs(); - const auto i = list->cbegin(); - if (i != list->cend()) { + const auto i = _shownList->cbegin(); + if (i != _shownList->cend()) { return RowDescriptor( (*i)->key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); @@ -3251,7 +3329,7 @@ RowDescriptor InnerWidget::chatListEntryFirst() const { return RowDescriptor(); } else if (!_filterResults.empty()) { return RowDescriptor( - _filterResults.front()->key(), + _filterResults.front().key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); } else if (!_peerSearchResults.empty()) { return RowDescriptor( @@ -3267,9 +3345,8 @@ RowDescriptor InnerWidget::chatListEntryFirst() const { RowDescriptor InnerWidget::chatListEntryLast() const { if (_state == WidgetState::Default) { - const auto list = shownDialogs(); - const auto i = list->cend(); - if (i != list->cbegin()) { + const auto i = _shownList->cend(); + if (i != _shownList->cbegin()) { return RowDescriptor( (*(i - 1))->key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); @@ -3285,7 +3362,7 @@ RowDescriptor InnerWidget::chatListEntryLast() const { FullMsgId(PeerId(), ShowAtUnreadMsgId)); } else if (!_filterResults.empty()) { return RowDescriptor( - _filterResults.back()->key(), + _filterResults.back().key(), FullMsgId(PeerId(), ShowAtUnreadMsgId)); } return RowDescriptor(); @@ -3364,15 +3441,15 @@ void InnerWidget::updateRowCornerStatusShown(not_null history) { const auto findRow = [&](not_null history) -> std::pair { if (state() == WidgetState::Default) { - const auto row = shownDialogs()->getRow({ history }); + const auto row = _shownList->getRow({ history }); return { row, row ? defaultRowTop(row) : 0 }; } const auto i = ranges::find( _filterResults, - history.get(), - [](not_null row) { return row->history(); }); + Key(history), + &FilterResult::key); const auto index = (i - begin(_filterResults)); - const auto row = (i == end(_filterResults)) ? nullptr : i->get(); + const auto row = (i == end(_filterResults)) ? nullptr : i->row.get(); return { row, filteredOffset() + index * _st->height }; }; if (const auto &[row, top] = findRow(history); row != nullptr) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 5923e8869..c1a8d7765 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -192,6 +192,19 @@ private: EmptyForum, }; + struct PinnedRow { + anim::value yadd; + crl::time animStartTime = 0; + }; + + struct FilterResult { + not_null row; + int top = 0; + + [[nodiscard]] Key key() const; + [[nodiscard]] int bottom() const; + }; + Main::Session &session() const; void dialogRowReplaced(Row *oldRow, Row *newRow); @@ -220,6 +233,8 @@ private: void clearIrrelevantState(); void selectByMouse(QPoint globalPosition); void loadPeerPhotos(); + void scrollToItem(int top, int height); + void scrollToDefaultSelected(); void setCollapsedPressed(int pressed); void setPressed(Row *pressed); void setHashtagPressed(int pressed); @@ -283,13 +298,18 @@ private: void fillSupportSearchMenu(not_null menu); void fillArchiveSearchMenu(not_null menu); - int dialogsOffset() const; - int fixedOnTopCount() const; - int pinnedOffset() const; - int filteredOffset() const; - int peerSearchOffset() const; - int searchedOffset() const; - int searchInChatSkip() const; + void refreshShownList(); + [[nodiscard]] int skipTopHeight() const; + [[nodiscard]] int dialogsOffset() const; + [[nodiscard]] int shownHeight(int till = -1) const; + [[nodiscard]] int fixedOnTopCount() const; + [[nodiscard]] int pinnedOffset() const; + [[nodiscard]] int filteredOffset() const; + [[nodiscard]] int filteredIndex(int y) const; + [[nodiscard]] int filteredHeight(int till = -1) const; + [[nodiscard]] int peerSearchOffset() const; + [[nodiscard]] int searchedOffset() const; + [[nodiscard]] int searchInChatSkip() const; void paintCollapsedRows( Painter &p, @@ -339,13 +359,12 @@ private: Ui::VideoUserpic *validateVideoUserpic(not_null row); Ui::VideoUserpic *validateVideoUserpic(not_null history); + Row *shownRowByKey(Key key); void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(Key key = Key()); void trackSearchResultsHistory(not_null history); void trackSearchResultsForum(Data::Forum *forum); - [[nodiscard]] not_null shownDialogs() const; - [[nodiscard]] const std::vector &pinnedChatsOrder() const; void checkReorderPinnedStart(QPoint localPosition); int updateReorderIndexGetCount(); @@ -363,6 +382,7 @@ private: const not_null _controller; + not_null _shownList; FilterId _filterId = 0; bool _mouseSelection = false; std::optional _lastMousePosition; @@ -376,7 +396,7 @@ private: not_null _st; int _collapsedSelected = -1; int _collapsedPressed = -1; - int _skipTopDialogs = 0; + bool _skipTopDialog = false; Row *_selected = nullptr; Row *_pressed = nullptr; @@ -384,10 +404,6 @@ private: int _draggingIndex = -1; int _aboveIndex = -1; QPoint _dragStart; - struct PinnedRow { - anim::value yadd; - crl::time animStartTime = 0; - }; std::vector _pinnedRows; Ui::Animations::Basic _pinnedShiftAnimation; base::flat_set _pinnedOnDragStart; @@ -405,7 +421,7 @@ private: bool _hashtagDeleteSelected = false; bool _hashtagDeletePressed = false; - std::vector> _filterResults; + std::vector _filterResults; base::flat_map> _filterResultsGlobal; int _filteredSelected = -1; int _filteredPressed = -1; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 8ac12ec64..54fdd1a40 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -39,13 +39,13 @@ public: explicit operator bool() const { return (_value != nullptr); } - not_null entry() const; - History *history() const; - Data::Folder *folder() const; - Data::ForumTopic *topic() const; - Data::Thread *thread() const; - History *owningHistory() const; - PeerData *peer() const; + [[nodiscard]] not_null entry() const; + [[nodiscard]] History *history() const; + [[nodiscard]] Data::Folder *folder() const; + [[nodiscard]] Data::ForumTopic *topic() const; + [[nodiscard]] Data::Thread *thread() const; + [[nodiscard]] History *owningHistory() const; + [[nodiscard]] PeerData *peer() const; friend inline constexpr auto operator<=>(Key, Key) noexcept = default; diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index 234f3fbad..0d47d06f4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -21,7 +21,7 @@ List::List(SortMode sortMode, FilterId filterId) List::const_iterator List::cfind(Row *value) const { return value - ? (cbegin() + value->pos()) + ? (cbegin() + value->index()) : cend(); } @@ -31,7 +31,7 @@ not_null List::addToEnd(Key key) { } const auto result = _rowByKey.emplace( key, - std::make_unique(key, _rows.size()) + std::make_unique(key, _rows.size(), height()) ).first->second.get(); _rows.emplace_back(result); if (_sortMode == SortMode::Date) { @@ -60,10 +60,10 @@ not_null List::addByName(Key key) { } void List::adjustByName(not_null row) { - Expects(row->pos() >= 0 && row->pos() < _rows.size()); + Expects(row->index() >= 0 && row->index() < _rows.size()); const auto &key = row->entry()->chatListNameSortKey(); - const auto index = row->pos(); + const auto index = row->index(); const auto i = _rows.begin() + index; const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) { return row->entry()->chatListNameSortKey().compare(key) >= 0; @@ -85,7 +85,7 @@ void List::adjustByDate(not_null row) { Expects(_sortMode == SortMode::Date); const auto key = row->sortKey(_filterId); - const auto index = row->pos(); + const auto index = row->index(); const auto i = _rows.begin() + index; const auto before = std::find_if(i + 1, _rows.end(), [&](Row *row) { return (row->sortKey(_filterId) <= key); @@ -108,7 +108,7 @@ bool List::moveToTop(Key key) { if (i == _rowByKey.cend()) { return false; } - const auto index = i->second->pos(); + const auto index = i->second->index(); const auto begin = _rows.begin(); rotate(begin, begin + index, begin + index + 1); return true; @@ -118,16 +118,20 @@ void List::rotate( std::vector>::iterator first, std::vector>::iterator middle, std::vector>::iterator last) { + auto top = (*first)->top(); std::rotate(first, middle, last); auto count = (last - first); auto index = (first - _rows.begin()); while (count--) { - (*first++)->_pos = index++; + const auto row = *first++; + row->_index = index++; + row->_top = top; + top += row->height(); } } -bool List::del(Key key, Row *replacedBy) { +bool List::remove(Key key, Row *replacedBy) { auto i = _rowByKey.find(key); if (i == _rowByKey.cend()) { return false; @@ -136,13 +140,34 @@ bool List::del(Key key, Row *replacedBy) { const auto row = i->second.get(); row->entry()->owner().dialogsRowReplaced({ row, replacedBy }); - const auto index = row->pos(); + auto top = row->top(); + const auto index = row->index(); _rows.erase(_rows.begin() + index); for (auto i = index, count = int(_rows.size()); i != count; ++i) { - _rows[i]->_pos = i; + const auto row = _rows[i]; + row->_index = i; + row->_top = top; + top += row->height(); } _rowByKey.erase(i); return true; } +Row *List::rowAtY(int y) const { + const auto i = findByY(y); + if (i == cend()) { + return nullptr; + } + const auto row = *i; + const auto top = row->top(); + const auto bottom = top + row->height(); + return (top <= y && bottom > y) ? row.get() : nullptr; +} + +List::const_iterator List::findByY(int y) const { + return ranges::lower_bound(_rows, y, ranges::less(), [](const Row *row) { + return row->top() + row->height(); + }); +} + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h index 00e4985e6..683d47342 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_list.h @@ -23,52 +23,49 @@ public: List &operator=(List &&other) = default; ~List() = default; - int size() const { + [[nodiscard]] int size() const { return _rows.size(); } - bool empty() const { + [[nodiscard]] bool empty() const { return _rows.empty(); } - bool contains(Key key) const { + [[nodiscard]] int height() const { + return _rows.empty() + ? 0 + : (_rows.back()->top() + _rows.back()->height()); + } + [[nodiscard]] bool contains(Key key) const { return _rowByKey.find(key) != _rowByKey.end(); } - Row *getRow(Key key) const { + [[nodiscard]] Row *getRow(Key key) const { const auto i = _rowByKey.find(key); return (i != _rowByKey.end()) ? i->second.get() : nullptr; } - Row *rowAtY(int y, int h) const { - const auto i = cfind(y, h); - if (i == cend() || (*i)->pos() != ((y > 0) ? (y / h) : 0)) { - return nullptr; - } - return *i; - } + [[nodiscard]] Row *rowAtY(int y) const; not_null addToEnd(Key key); Row *adjustByName(Key key); not_null addByName(Key key); bool moveToTop(Key key); void adjustByDate(not_null row); - bool del(Key key, Row *replacedBy = nullptr); + bool remove(Key key, Row *replacedBy = nullptr); using const_iterator = std::vector>::const_iterator; using iterator = const_iterator; - const_iterator cbegin() const { return _rows.cbegin(); } - const_iterator cend() const { return _rows.cend(); } - const_iterator begin() const { return cbegin(); } - const_iterator end() const { return cend(); } - iterator begin() { return cbegin(); } - iterator end() { return cend(); } - const_iterator cfind(Row *value) const; - const_iterator find(Row *value) const { return cfind(value); } - iterator find(Row *value) { return cfind(value); } - const_iterator cfind(int y, int h) const { - const auto index = std::max(y, 0) / h; - return _rows.begin() + std::min(index, size()); + [[nodiscard]] const_iterator cbegin() const { return _rows.cbegin(); } + [[nodiscard]] const_iterator cend() const { return _rows.cend(); } + [[nodiscard]] const_iterator begin() const { return cbegin(); } + [[nodiscard]] const_iterator end() const { return cend(); } + [[nodiscard]] iterator begin() { return cbegin(); } + [[nodiscard]] iterator end() { return cend(); } + [[nodiscard]] const_iterator cfind(Row *value) const; + [[nodiscard]] const_iterator find(Row *value) const { + return cfind(value); } - const_iterator find(int y, int h) const { return cfind(y, h); } - iterator find(int y, int h) { return cfind(y, h); } + [[nodiscard]] iterator find(Row *value) { return cfind(value); } + [[nodiscard]] const_iterator findByY(int y) const; + [[nodiscard]] iterator findByY(int y) { return findByY(y); } private: void adjustByName(not_null row); diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index f4da53ab3..d0197d391 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -89,7 +89,7 @@ void MainList::clear() { _cloudListSize = 0; } -RowsByLetter MainList::addEntry(const Key &key) { +RowsByLetter MainList::addEntry(Key key) { const auto result = _all.addToEnd(key); const auto unread = key.entry()->chatListUnreadState(); @@ -99,8 +99,8 @@ RowsByLetter MainList::addEntry(const Key &key) { return result; } -void MainList::removeEntry(const Key &key) { - _all.del(key); +void MainList::removeEntry(Key key) { + _all.remove(key); const auto unread = key.entry()->chatListUnreadState(); unreadEntryChanged(unread, false); diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.h b/Telegram/SourceFiles/dialogs/dialogs_main_list.h index ccd3b1847..157e48e31 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h @@ -33,8 +33,8 @@ public: void setAllAreMuted(bool allAreMuted = true); void clear(); - RowsByLetter addEntry(const Key &key); - void removeEntry(const Key &key); + RowsByLetter addEntry(Key key); + void removeEntry(Key key); void unreadStateChanged( const UnreadState &wasState, diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp index c47ee9b3c..1382201a8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp @@ -31,13 +31,13 @@ void PinnedList::setLimit(int limit) { applyLimit(_limit); } -void PinnedList::addPinned(const Key &key) { +void PinnedList::addPinned(Key key) { Expects(key.entry()->folderKnown()); addPinnedGetPosition(key); } -int PinnedList::addPinnedGetPosition(const Key &key) { +int PinnedList::addPinnedGetPosition(Key key) { const auto already = ranges::find(_data, key); if (already != end(_data)) { return already - begin(_data); @@ -49,7 +49,7 @@ int PinnedList::addPinnedGetPosition(const Key &key) { return position; } -void PinnedList::setPinned(const Key &key, bool pinned) { +void PinnedList::setPinned(Key key, bool pinned) { Expects(key.entry()->folderKnown() || _filterId != 0); if (pinned) { @@ -128,7 +128,7 @@ void PinnedList::applyList(const std::vector> &list) { } } -void PinnedList::reorder(const Key &key1, const Key &key2) { +void PinnedList::reorder(Key key1, Key key2) { const auto index1 = ranges::find(_data, key1) - begin(_data); const auto index2 = ranges::find(_data, key2) - begin(_data); Assert(index1 >= 0 && index1 < _data.size()); diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h index 238175b0f..ce18ae9c3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h @@ -26,10 +26,10 @@ public: // Places on the last place in the list otherwise. // Does nothing if already pinned. - void addPinned(const Key &key); + void addPinned(Key key); // if (pinned) places on the first place in the list. - void setPinned(const Key &key, bool pinned); + void setPinned(Key key, bool pinned); void clear(); @@ -40,14 +40,14 @@ public: not_null forum, const QVector &list); void applyList(const std::vector> &list); - void reorder(const Key &key1, const Key &key2); + void reorder(Key key1, Key key2); const std::vector &order() const { return _data; } private: - int addPinnedGetPosition(const Key &key); + int addPinnedGetPosition(Key key); void applyLimit(int limit); FilterId _filterId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index a0fce172e..eeae9145d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -143,9 +143,14 @@ void BasicRow::paintUserpic( context.paused); } -Row::Row(Key key, int pos) : _id(key), _pos(pos) { +Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) { if (const auto history = key.history()) { updateCornerBadgeShown(history->peer); + _height = history->peer->isForum() + ? st::forumDialogRow.height + : st::defaultDialogRow.height; + } else { + _height = st::forumTopicRow.height; } } @@ -173,10 +178,11 @@ void Row::validateListEntryCache() const { void Row::setCornerBadgeShown( bool shown, Fn updateCallback) const { - if (_cornerBadgeShown == shown) { + const auto value = shown ? 1 : 0; + if (_cornerBadgeShown == value) { return; } - _cornerBadgeShown = shown; + _cornerBadgeShown = value; if (_cornerBadgeUserpic && _cornerBadgeUserpic->animation.animating()) { _cornerBadgeUserpic->animation.change( _cornerBadgeShown ? 1. : 0., diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 0b460f323..396042eb7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.h @@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class HistoryItem; +namespace style { +struct DialogRow; +} // namespace style + namespace Data { class CloudImageView; } // namespace Data @@ -57,7 +61,8 @@ public: int outerWidth, const QColor *colorOverride = nullptr) const; - std::shared_ptr &userpicView() const { + [[nodiscard]] auto userpicView() const + -> std::shared_ptr & { return _userpic; } @@ -68,11 +73,18 @@ private: }; class List; -class Row : public BasicRow { +class Row final : public BasicRow { public: explicit Row(std::nullptr_t) { } - Row(Key key, int pos); + Row(Key key, int index, int top); + + [[nodiscard]] int top() const { + return _top; + } + [[nodiscard]] int height() const { + return _height; + } void updateCornerBadgeShown( not_null peer, @@ -102,8 +114,8 @@ public: [[nodiscard]] not_null entry() const { return _id.entry(); } - [[nodiscard]] int pos() const { - return _pos; + [[nodiscard]] int index() const { + return _index; } [[nodiscard]] uint64 sortKey(FilterId filterId) const; @@ -139,11 +151,13 @@ private: const Ui::PaintContext &context); Key _id; - int _pos = 0; - mutable uint32 _listEntryCacheVersion = 0; mutable Ui::Text::String _listEntryCache; mutable std::unique_ptr _cornerBadgeUserpic; - mutable bool _cornerBadgeShown = false; + int _top = 0; + int _height = 0; + int _index = 0; + mutable int _listEntryCacheVersion : 31 = 0; + mutable int _cornerBadgeShown : 1 = 0; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 6607cbf9a..cdd04937a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -245,6 +245,7 @@ template void PaintRow( Painter &p, not_null row, + QRect geometry, not_null entry, VideoUserpic *videoUserpic, PeerData *from, @@ -264,7 +265,6 @@ void PaintRow( draft = nullptr; } - auto fullRect = QRect(0, 0, context.width, context.st->height); auto bg = context.active ? st::dialogsBgActive : context.selected @@ -273,7 +273,7 @@ void PaintRow( auto ripple = context.active ? st::dialogsRippleBgActive : st::dialogsRippleBg; - p.fillRect(fullRect, bg); + p.fillRect(geometry, bg); row->paintRipple(p, 0, 0, context.width, &ripple->c); const auto history = entry->asHistory(); @@ -932,6 +932,7 @@ void RowPainter::Paint( PaintRow( p, row, + QRect(0, 0, context.width, row->height()), entry, videoUserpic, from, @@ -1029,6 +1030,7 @@ void RowPainter::Paint( PaintRow( p, row, + QRect(0, 0, context.width, context.st->height), entry, nullptr, from, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 92e113775..d29efe271 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -205,7 +205,7 @@ base::options::toggle AutoScrollInactiveChat({ [[nodiscard]] rpl::producer ActivePeerValue( not_null controller) { return controller->activeChatValue( - ) | rpl::map([](const Dialogs::Key &key) { + ) | rpl::map([](Dialogs::Key key) { const auto history = key.history(); return history ? history->peer.get() : nullptr; }); @@ -2530,7 +2530,7 @@ void HistoryWidget::refreshSilentToggle() { void HistoryWidget::setupScheduledToggle() { controller()->activeChatValue( - ) | rpl::map([=](const Dialogs::Key &key) -> rpl::producer<> { + ) | rpl::map([=](Dialogs::Key key) -> rpl::producer<> { if (const auto history = key.history()) { return session().data().scheduledMessages().updates(history); } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 909b2d203..8d3d93ec3 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/delete_messages_box.h" #include "boxes/premium_preview_box.h" #include "boxes/peers/edit_participant_box.h" +#include "core/crash_reports.h" #include "data/data_session.h" #include "data/data_sponsored_messages.h" #include "data/data_changes.h" @@ -136,7 +137,10 @@ void ListWidget::enumerateItems(Method method) { if (TopToBottom) { Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); } else { - Assert(itemTop(from->get()) < _visibleBottom); + if (itemTop(from->get()) >= _visibleBottom) { + setGeometryCrashAnnotations(*from); + Unexpected("itemTop(from->get()) >= _visibleBottom"); + } } while (true) { @@ -148,7 +152,10 @@ void ListWidget::enumerateItems(Method method) { if (TopToBottom) { Assert(itembottom > _visibleTop); } else { - Assert(itemtop < _visibleBottom); + if (itemtop >= _visibleBottom) { + setGeometryCrashAnnotations(view); + Unexpected("itemtop >= _visibleBottom"); + } } if (!method(view, itemtop, itembottom)) { @@ -441,6 +448,39 @@ void ListWidget::refreshViewer() { }, _viewerLifetime); } +void ListWidget::setGeometryCrashAnnotations(not_null view) { + CrashReports::SetAnnotation( + "Geometry", + u"size: %1x%2, visibleTop: %3, visibleBottom: %4, top: %5"_q + .arg(width()) + .arg(height()) + .arg(_visibleTop) + .arg(_visibleBottom) + .arg(_itemsTop)); + const auto logItems = [&] { + auto items = QStringList(); + auto top = _itemsTop; + auto index = 0; + for (const auto &some : _items) { + items.push_back(u"(%1)%2=%3,%4,%5"_q + .arg(index++) + .arg(top) + .arg(itemTop(some)) + .arg(some->y()) + .arg(some->height())); + top += some->height(); + } + return items.join(';'); + }; + CrashReports::SetAnnotation("Chosen", u"%1,%2,%3"_q + .arg(itemTop(view)) + .arg(view->y()) + .arg(view->height())); + CrashReports::SetAnnotation("Before", logItems()); + updateSize(); + CrashReports::SetAnnotation("After", logItems()); +} + void ListWidget::refreshRows(const Data::MessagesSlice &old) { saveScrollState(); @@ -3167,6 +3207,12 @@ void ListWidget::mouseActionFinish( auto activated = ClickHandler::unpressed(); + if (_overElement) { + AssertIsDebug(); + setGeometryCrashAnnotations(_overElement); + Unexpected("Test"); + } + auto simpleSelectionChange = pressState.itemId && !_pressWasInactive && (button != Qt::RightButton) diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 210f28eff..7d8c435c0 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -608,6 +608,8 @@ private: template void enumerateDates(Method method); + void setGeometryCrashAnnotations(not_null view); + static constexpr auto kMinimalIdsLimit = 24; const not_null _delegate; diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index f55b202c1..4e3acc095 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -401,7 +401,7 @@ void Session::addWindow(not_null controller) { _windows.remove(controller); }); updates().addActiveChat(controller->activeChatChanges( - ) | rpl::map([=](const Dialogs::Key &chat) { + ) | rpl::map([=](Dialogs::Key chat) { return chat.peer(); }) | rpl::distinct_until_changed()); } diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp index f7b4ee44a..c3be83c2f 100644 --- a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp +++ b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp @@ -325,7 +325,7 @@ void SetupSendAsButton( not_null button, not_null window) { auto active = window->activeChatValue( - ) | rpl::map([=](const Dialogs::Key &key) { + ) | rpl::map([=](Dialogs::Key key) { return key.history() ? key.history()->peer.get() : nullptr; }); SetupSendAsButton(button, std::move(active), window);