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.
This commit is contained in:
futpib
2026-02-19 10:17:29 +00:00
committed by John Preston
parent 68e1504685
commit 5f1121123c
6 changed files with 54 additions and 1 deletions
@@ -194,6 +194,20 @@ void IndexedList::adjustNames(
}
}
void IndexedList::freeze() {
_list.freeze();
for (auto &[ch, list] : _index) {
list.freeze();
}
}
void IndexedList::unfreeze() {
_list.unfreeze();
for (auto &[ch, list] : _index) {
list.unfreeze();
}
}
void IndexedList::remove(Key key, Row *replacedBy) {
if (_list.remove(key, replacedBy)) {
for (const auto &ch : key.entry()->chatListFirstLetters()) {
@@ -24,6 +24,8 @@ public:
Row *addByName(Key key);
void adjustByDate(const RowsByLetter &links);
void moveToTop(Key key);
void freeze();
void unfreeze();
bool updateHeight(Key key, float64 narrowRatio);
bool updateHeights(float64 narrowRatio);
@@ -95,6 +95,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Dialogs {
namespace {
constexpr auto kFreezeTimeout = crl::time(5000);
constexpr auto kHashtagResultsLimit = 5;
constexpr auto kStartReorderThreshold = 30;
constexpr auto kStartDragToFilterThresholdX = kStartReorderThreshold;
@@ -291,7 +292,8 @@ InnerWidget::InnerWidget(
, _narrowWidth(st::defaultDialogRow.padding.left()
+ st::defaultDialogRow.photoSize
+ st::defaultDialogRow.padding.left())
, _childListShown(std::move(childListShown)) {
, _childListShown(std::move(childListShown))
, _freezeTimer([=] { _shownList->unfreeze(); update(); }) {
setAttribute(Qt::WA_OpaquePaintEvent, true);
style::PaletteChanged(
@@ -1678,6 +1680,13 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
return;
}
if (_lastMousePosition && *_lastMousePosition != globalPosition) {
if (!_freezeTimer.isActive()) {
_shownList->freeze();
}
_freezeTimer.callOnce(kFreezeTimeout);
}
if (_pressed && (e->buttons() & Qt::LeftButton)) {
const auto local = e->pos();
const auto outside = _dragging ? false : true;
@@ -3052,6 +3061,8 @@ void InnerWidget::updateDialogRow(
void InnerWidget::enterEventHook(QEnterEvent *e) {
setMouseTracking(true);
_shownList->freeze();
_freezeTimer.callOnce(kFreezeTimeout);
}
Row *InnerWidget::shownRowByKey(Key key) {
@@ -3134,6 +3145,7 @@ void InnerWidget::refreshShownList() {
? session().data().chatsFilters().chatsList(_filterId)->indexed()
: session().data().chatsList(_openedFolder)->indexed();
if (_shownList != list) {
_shownList->unfreeze();
_shownList = list;
_shownList->updateHeights(_narrowRatio);
}
@@ -3141,7 +3153,10 @@ void InnerWidget::refreshShownList() {
void InnerWidget::leaveEventHook(QEvent *e) {
setMouseTracking(false);
_freezeTimer.cancel();
_shownList->unfreeze();
clearSelection();
update();
}
void InnerWidget::dragLeft() {
@@ -678,6 +678,7 @@ private:
rpl::event_stream<> _touchCancelRequests;
rpl::variable<ChildListShown> _childListShown;
base::Timer _freezeTimer;
float64 _narrowRatio = 0.;
bool _geometryInited = false;
@@ -84,6 +84,11 @@ void List::adjustByName(not_null<Row*> row) {
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;
@@ -103,6 +108,17 @@ void List::adjustByDate(not_null<Row*> row) {
}
}
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()) {
@@ -170,6 +186,7 @@ bool List::remove(Key key, Row *replacedBy) {
}
const auto row = i->second.get();
_pendingAdjust.remove(row);
row->entry()->owner().dialogsRowReplaced({ row, replacedBy });
auto top = row->top();
@@ -52,6 +52,8 @@ public:
not_null<Row*> addByName(Key key);
bool moveToTop(Key key);
void adjustByDate(not_null<Row*> row);
void freeze();
void unfreeze();
bool updateHeight(Key key, float64 narrowRatio);
bool updateHeights(float64 narrowRatio);
bool remove(Key key, Row *replacedBy = nullptr);
@@ -82,8 +84,10 @@ private:
SortMode _sortMode = SortMode();
FilterId _filterId = 0;
float64 _narrowRatio = 0.;
bool _frozen = false;
std::vector<not_null<Row*>> _rows;
std::map<Key, std::unique_ptr<Row>> _rowByKey;
base::flat_set<not_null<Row*>> _pendingAdjust;
};