Files
AyuGramDesktop/Telegram/SourceFiles/dialogs/dialogs_list.cpp
T
futpib 5f1121123c Freeze chat list order while mouse hovers over it
When subscribed to many active chats, the list reorders too quickly,
causing misclicks. Freeze the visual order while the mouse is over the
chat list and replay deferred reorderings when the mouse leaves or
after 5 seconds of inactivity.

Fixes #1504.
2026-03-06 23:16:40 +04:00

223 lines
5.2 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_entry.h"
#include "dialogs/ui/dialogs_layout.h"
#include "data/data_session.h"
namespace Dialogs {
List::List(SortMode sortMode, FilterId filterId)
: _sortMode(sortMode)
, _filterId(filterId) {
}
List::const_iterator List::cfind(Row *value) const {
return value
? (cbegin() + value->index())
: cend();
}
not_null<Row*> List::addToEnd(Key key) {
if (const auto result = getRow(key)) {
return result;
}
const auto result = _rowByKey.emplace(
key,
std::make_unique<Row>(key, _rows.size(), height())
).first->second.get();
result->recountHeight(_narrowRatio, _filterId);
_rows.emplace_back(result);
if (_sortMode == SortMode::Date) {
adjustByDate(result);
}
return result;
}
Row *List::adjustByName(Key key) {
Expects(_sortMode == SortMode::Name);
const auto row = getRow(key);
if (!row) {
return nullptr;
}
adjustByName(row);
return row;
}
not_null<Row*> List::addByName(Key key) {
Expects(_sortMode == SortMode::Name);
const auto row = addToEnd(key);
adjustByName(key);
return row;
}
void List::adjustByName(not_null<Row*> row) {
Expects(row->index() >= 0 && row->index() < _rows.size());
const auto &key = row->entry()->chatListNameSortKey();
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;
});
if (before != i + 1) {
rotate(i, i + 1, before);
} else if (i != _rows.begin()) {
const auto from = std::make_reverse_iterator(i);
const auto after = std::find_if(from, _rows.rend(), [&](Row *row) {
return row->entry()->chatListNameSortKey().compare(key) <= 0;
}).base();
if (after != i) {
rotate(after, i, i + 1);
}
}
}
void List::adjustByDate(not_null<Row*> row) {
Expects(_sortMode == SortMode::Date);
if (_frozen) {
_pendingAdjust.emplace(row);
return;
}
const auto key = row->sortKey(_filterId);
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);
});
if (before != i + 1) {
rotate(i, i + 1, before);
} else {
const auto from = std::make_reverse_iterator(i);
const auto after = std::find_if(from, _rows.rend(), [&](Row *row) {
return (row->sortKey(_filterId) >= key);
}).base();
if (after != i) {
rotate(after, i, i + 1);
}
}
}
void List::freeze() {
_frozen = true;
}
void List::unfreeze() {
_frozen = false;
for (const auto &row : base::take(_pendingAdjust)) {
adjustByDate(row);
}
}
bool List::updateHeight(Key key, float64 narrowRatio) {
const auto i = _rowByKey.find(key);
if (i == _rowByKey.cend()) {
return false;
}
const auto row = i->second.get();
const auto index = row->index();
auto top = row->top();
const auto was = row->height();
row->recountHeight(narrowRatio, _filterId);
if (row->height() == was) {
return false;
}
for (auto i = _rows.begin() + index, e = _rows.end(); i != e; ++i) {
(*i)->_top = top;
top += (*i)->height();
}
return true;
}
bool List::updateHeights(float64 narrowRatio) {
_narrowRatio = narrowRatio;
auto was = height();
auto top = 0;
for (const auto &row : _rows) {
row->_top = top;
row->recountHeight(narrowRatio, _filterId);
top += row->height();
}
return (height() != was);
}
bool List::moveToTop(Key key) {
const auto i = _rowByKey.find(key);
if (i == _rowByKey.cend()) {
return false;
}
const auto index = i->second->index();
const auto begin = _rows.begin();
rotate(begin, begin + index, begin + index + 1);
return true;
}
void List::rotate(
std::vector<not_null<Row*>>::iterator first,
std::vector<not_null<Row*>>::iterator middle,
std::vector<not_null<Row*>>::iterator last) {
auto top = (*first)->top();
std::rotate(first, middle, last);
auto count = (last - first);
auto index = (first - _rows.begin());
while (count--) {
const auto row = *first++;
row->_index = index++;
row->_top = top;
top += row->height();
}
}
bool List::remove(Key key, Row *replacedBy) {
auto i = _rowByKey.find(key);
if (i == _rowByKey.cend()) {
return false;
}
const auto row = i->second.get();
_pendingAdjust.remove(row);
row->entry()->owner().dialogsRowReplaced({ row, replacedBy });
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) {
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::iterator List::findByY(int y) const {
return ranges::lower_bound(_rows, y, ranges::less(), [](const Row *row) {
return row->top() + row->height() - 1;
});
}
} // namespace Dialogs