Improve mouse/keyboard selection interactions.

Fixes #5458.
This commit is contained in:
John Preston 2018-12-25 16:41:40 +04:00
parent 44ff8f92ac
commit e5536880fb
9 changed files with 139 additions and 115 deletions

View File

@ -746,6 +746,8 @@ void PeerListContent::clearAllContent() {
setSelected(Selected());
setPressed(Selected());
setContexted(Selected());
_mouseSelection = false;
_lastMousePosition = std::nullopt;
_rowsById.clear();
_rowsByPeer.clear();
_filterResults.clear();
@ -836,7 +838,9 @@ void PeerListContent::refreshRows() {
if (_visibleBottom > 0) {
checkScrollForPreload();
}
updateSelection();
if (_mouseSelection) {
selectByMouse(QCursor::pos());
}
update();
}
@ -943,29 +947,32 @@ void PeerListContent::enterEventHook(QEvent *e) {
}
void PeerListContent::leaveEventHook(QEvent *e) {
_mouseSelection = false;
setMouseTracking(false);
setSelected(Selected());
if (_mouseSelection) {
setSelected(Selected());
_mouseSelection = false;
_lastMousePosition = std::nullopt;
}
}
void PeerListContent::mouseMoveEvent(QMouseEvent *e) {
handleMouseMove(e->globalPos());
}
void PeerListContent::handleMouseMove(QPoint position) {
if (_mouseSelection || _lastMousePosition != position) {
_lastMousePosition = position;
_mouseSelection = true;
updateSelection();
void PeerListContent::handleMouseMove(QPoint globalPosition) {
if (!_lastMousePosition) {
_lastMousePosition = globalPosition;
return;
} else if (!_mouseSelection
&& *_lastMousePosition == globalPosition) {
return;
}
selectByMouse(globalPosition);
}
void PeerListContent::mousePressEvent(QMouseEvent *e) {
_pressButton = e->button();
_mouseSelection = true;
_lastMousePosition = e->globalPos();
updateSelection();
selectByMouse(e->globalPos());
setPressed(_selected);
if (auto row = getRow(_selected.index)) {
auto updateCallback = [this, row, hint = _selected.index] {
@ -1157,6 +1164,7 @@ void PeerListContent::selectSkip(int direction) {
return;
}
_mouseSelection = false;
_lastMousePosition = std::nullopt;
auto newSelectedIndex = _selected.index.value + direction;
@ -1305,7 +1313,6 @@ void PeerListContent::searchQueryChanged(QString query) {
_controller->search(_searchQuery);
}
refreshRows();
restoreSelection();
}
}
@ -1357,6 +1364,8 @@ void PeerListContent::setSearchQuery(
setSelected(Selected());
setPressed(Selected());
setContexted(Selected());
_mouseSelection = false;
_lastMousePosition = std::nullopt;
_searchQuery = query;
_normalizedSearchQuery = normalizedQuery;
_mentionHighlight = _searchQuery.startsWith('@')
@ -1403,8 +1412,9 @@ void PeerListContent::setContexted(Selected contexted) {
}
void PeerListContent::restoreSelection() {
_lastMousePosition = QCursor::pos();
updateSelection();
if (_mouseSelection) {
selectByMouse(QCursor::pos());
}
}
auto PeerListContent::saveSelectedData(Selected from)
@ -1426,11 +1436,11 @@ auto PeerListContent::restoreSelectedData(SelectedSaved from)
return result;
}
void PeerListContent::updateSelection() {
if (!_mouseSelection) return;
auto point = mapFromGlobal(_lastMousePosition);
auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(_lastMousePosition));
void PeerListContent::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;
const auto point = mapFromGlobal(globalPosition);
auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(globalPosition));
auto selected = Selected();
auto rowsPointY = point.y() - rowsTop();
selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1;

View File

@ -553,7 +553,7 @@ private:
SelectedSaved saveSelectedData(Selected from);
Selected restoreSelectedData(SelectedSaved from);
void updateSelection();
void selectByMouse(QPoint globalPosition);
void loadProfilePhotos();
void checkScrollForPreload();
@ -587,7 +587,7 @@ private:
void clearSearchRows();
void clearAllContent();
void handleMouseMove(QPoint position);
void handleMouseMove(QPoint globalPosition);
void mousePressReleased(Qt::MouseButton button);
const style::PeerList &_st;
@ -602,6 +602,7 @@ private:
Selected _pressed;
Selected _contexted;
bool _mouseSelection = false;
std::optional<QPoint> _lastMousePosition;
Qt::MouseButton _pressButton = Qt::LeftButton;
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
@ -622,8 +623,6 @@ private:
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
QPoint _lastMousePosition;
std::vector<std::unique_ptr<PeerListRow>> _searchRows;
base::Timer _repaintByStatus;
base::unique_qptr<Ui::PopupMenu> _contextMenu;

View File

@ -241,6 +241,7 @@ void SuggestionsWidget::handleKeyEvent(int key) {
}
_mouseSelection = false;
_lastMousePosition = std::nullopt;
setSelected(newSelected);
}
@ -272,6 +273,7 @@ void SuggestionsWidget::clearMouseSelection() {
void SuggestionsWidget::clearSelection() {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
setSelected(-1);
}
@ -296,24 +298,30 @@ void SuggestionsWidget::mouseMoveEvent(QMouseEvent *e) {
auto inner = rect().marginsRemoved(QMargins(0, _st->skip, 0, _st->skip));
auto localPosition = e->pos();
if (inner.contains(localPosition)) {
_mouseSelection = true;
updateSelection(e->globalPos());
const auto globalPosition = e->globalPos();
if (!_lastMousePosition) {
_lastMousePosition = globalPosition;
return;
} else if (!_mouseSelection
&& *_lastMousePosition == globalPosition) {
return;
}
selectByMouse(globalPosition);
} else {
clearMouseSelection();
}
}
void SuggestionsWidget::updateSelection(QPoint globalPosition) {
if (!_mouseSelection) return;
void SuggestionsWidget::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;
auto p = mapFromGlobal(globalPosition) - QPoint(0, _st->skip);
auto selected = (p.y() >= 0) ? (p.y() / _rowHeight) : -1;
setSelected((selected >= 0 && selected < _rows.size()) ? selected : -1);
}
void SuggestionsWidget::mousePressEvent(QMouseEvent *e) {
if (!_mouseSelection) {
return;
}
selectByMouse(e->globalPos());
if (_selected >= 0 && _selected < _rows.size()) {
setPressed(_selected);
if (!_rows[_pressed].ripple()) {

View File

@ -49,7 +49,7 @@ private:
void updateSelectedItem();
int itemTop(int index);
void updateItem(int index);
void updateSelection(QPoint globalPosition);
void selectByMouse(QPoint globalPosition);
void triggerSelectedRow();
void triggerRow(const Row &row);
@ -59,6 +59,7 @@ private:
std::vector<Row> _rows;
int _rowHeight = 0;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
int _selected = -1;
int _pressed = -1;

View File

@ -682,13 +682,21 @@ void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) {
}
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
const auto globalPosition = e->globalPos();
if (!_lastMousePosition) {
_lastMousePosition = globalPosition;
return;
} else if (!_mouseSelection
&& *_lastMousePosition == globalPosition) {
return;
}
selectByMouse(globalPosition);
}
void FieldAutocompleteInner::clearSel(bool hidden) {
_mouseSel = _overDelete = false;
_overDelete = false;
_mouseSelection = false;
_lastMousePosition = std::nullopt;
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
if (hidden) {
_down = -1;
@ -697,7 +705,9 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
}
bool FieldAutocompleteInner::moveSel(int key) {
_mouseSel = false;
_mouseSelection = false;
_lastMousePosition = std::nullopt;
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
if (!_srows->empty()) {
@ -760,12 +770,9 @@ void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
}
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
selectByMouse(e->globalPos());
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
_mousePos = mapToGlobal(e->pos());
bool removed = false;
if (_mrows->isEmpty()) {
QString toRemove = _hrows->at(_sel);
@ -792,8 +799,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
}
_parent->updateFiltered();
_mouseSel = true;
onUpdateSelected(true);
selectByMouse(e->globalPos());
} else if (_srows->empty()) {
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
} else {
@ -809,9 +815,7 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
int32 pressed = _down;
_down = -1;
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
selectByMouse(e->globalPos());
if (_previewShown) {
_previewShown = false;
@ -825,14 +829,14 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
void FieldAutocompleteInner::enterEventHook(QEvent *e) {
setMouseTracking(true);
_mousePos = QCursor::pos();
onUpdateSelected(true);
}
void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
setMouseTracking(false);
if (_sel >= 0) {
if (_mouseSelection) {
setSel(-1);
_mouseSelection = false;
_lastMousePosition = std::nullopt;
}
}
@ -862,11 +866,14 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
}
}
void FieldAutocompleteInner::onUpdateSelected(bool force) {
QPoint mouse(mapFromGlobal(_mousePos));
if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
_mouseSelection = true;
_lastMousePosition = globalPosition;
const auto mouse = mapFromGlobal(globalPosition);
if (_down >= 0 && !_previewShown) return;
if (_down >= 0 && !_previewShown) {
return;
}
int32 sel = -1, maxSel = 0;
if (!_srows->empty()) {
@ -900,10 +907,12 @@ void FieldAutocompleteInner::onUpdateSelected(bool force) {
}
void FieldAutocompleteInner::onParentGeometryChanged() {
_mousePos = QCursor::pos();
if (rect().contains(mapFromGlobal(_mousePos))) {
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
onUpdateSelected(true);
if (_mouseSelection) {
selectByMouse(globalPosition);
}
}
}

View File

@ -152,7 +152,6 @@ signals:
public slots:
void onParentGeometryChanged();
void onUpdateSelected(bool force = false);
private:
void paintEvent(QPaintEvent *e) override;
@ -168,6 +167,7 @@ private:
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
void showPreview();
void selectByMouse(QPoint global);
FieldAutocomplete *_parent = nullptr;
MentionRows *_mrows = nullptr;
@ -178,8 +178,8 @@ private:
int _recentInlineBotsInRows = 0;
int _sel = -1;
int _down = -1;
bool _mouseSel = false;
QPoint _mousePos;
std::optional<QPoint> _lastMousePosition;
bool _mouseSelection = false;
bool _overDelete = false;

View File

@ -692,11 +692,15 @@ void DialogsInner::activate() {
}
void DialogsInner::mouseMoveEvent(QMouseEvent *e) {
if (_mouseLastGlobalPosition != e->globalPos()) {
_mouseLastGlobalPosition = e->globalPos();
_mouseSelection = true;
const auto globalPosition = e->globalPos();
if (!_lastMousePosition) {
_lastMousePosition = globalPosition;
return;
} else if (!_mouseSelection
&& *_lastMousePosition == globalPosition) {
return;
}
updateSelected(e->pos());
selectByMouse(globalPosition);
}
void DialogsInner::clearIrrelevantState() {
@ -718,16 +722,15 @@ void DialogsInner::clearIrrelevantState() {
}
}
void DialogsInner::updateSelected(QPoint localPos) {
if (updateReorderPinned(localPos)) {
void DialogsInner::selectByMouse(QPoint globalPosition) {
const auto local = mapFromGlobal(globalPosition);
if (updateReorderPinned(local)) {
return;
}
_mouseSelection = true;
_lastMousePosition = globalPosition;
if (!_mouseSelection) {
return;
}
int w = width(), mouseY = localPos.y();
int w = width(), mouseY = local.y();
clearIrrelevantState();
if (_state == State::Default) {
auto importantSwitchSelected = (_dialogsImportant && mouseY >= 0 && mouseY < dialogsOffset());
@ -756,7 +759,7 @@ void DialogsInner::updateSelected(QPoint localPos) {
_hashtagSelected = hashtagSelected;
updateSelectedRow();
}
_hashtagDeleteSelected = (_hashtagSelected >= 0) && (localPos.x() >= w - st::mentionHeight);
_hashtagDeleteSelected = (_hashtagSelected >= 0) && (local.x() >= w - st::mentionHeight);
}
if (!_filterResults.isEmpty()) {
auto skip = filteredOffset();
@ -801,8 +804,7 @@ void DialogsInner::updateSelected(QPoint localPos) {
}
void DialogsInner::mousePressEvent(QMouseEvent *e) {
_mouseSelection = true;
updateSelected(e->pos());
selectByMouse(e->globalPos());
_pressButton = e->button();
setPressed(_selected);
@ -851,7 +853,7 @@ void DialogsInner::mousePressEvent(QMouseEvent *e) {
}
if (anim::Disabled()
&& (!_pressed || !_pressed->entry()->isPinnedDialog())) {
mousePressReleased(e->button());
mousePressReleased(e->globalPos(), e->button());
}
}
@ -1072,15 +1074,16 @@ void DialogsInner::step_pinnedShifting(TimeMs ms, bool timer) {
}
void DialogsInner::mouseReleaseEvent(QMouseEvent *e) {
mousePressReleased(e->button());
mousePressReleased(e->globalPos(), e->button());
}
void DialogsInner::mousePressReleased(Qt::MouseButton button) {
void DialogsInner::mousePressReleased(
QPoint globalPosition,
Qt::MouseButton button) {
auto wasDragging = (_dragging != nullptr);
if (wasDragging) {
updateReorderIndexGetCount();
if (_draggingIndex >= 0) {
auto localPosition = mapFromGlobal(QCursor::pos());
_pinnedRows[_draggingIndex].yadd.start(0.);
_pinnedRows[_draggingIndex].animStartTime = getms();
if (!_a_pinnedShifting.animating()) {
@ -1105,7 +1108,7 @@ void DialogsInner::mousePressReleased(Qt::MouseButton button) {
auto searchedPressed = _searchedPressed;
setSearchedPressed(-1);
if (wasDragging) {
updateSelected();
selectByMouse(globalPosition);
}
updateSelectedRow();
if (!wasDragging && button == Qt::LeftButton) {
@ -1439,7 +1442,6 @@ void DialogsInner::updateDialogRow(
void DialogsInner::enterEventHook(QEvent *e) {
setMouseTracking(true);
updateSelected();
}
void DialogsInner::updateSelectedRow(Dialogs::Key key) {
@ -1498,6 +1500,7 @@ void DialogsInner::dragLeft() {
void DialogsInner::clearSelection() {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
if (_importantSwitchSelected
|| _selected
|| _filteredSelected >= 0
@ -1516,8 +1519,7 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) {
_menu = nullptr;
if (e->reason() == QContextMenuEvent::Mouse) {
_mouseSelection = true;
updateSelected();
selectByMouse(e->globalPos());
}
const auto key = [&]() -> Dialogs::Key {
@ -1536,7 +1538,7 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) {
_menuKey = key;
if (_pressButton != Qt::LeftButton) {
mousePressReleased(_pressButton);
mousePressReleased(e->globalPos(), _pressButton);
}
_menu = base::make_unique_q<Ui::PopupMenu>(this);
@ -1561,11 +1563,10 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) {
if (_menuKey) {
updateSelectedRow(base::take(_menuKey));
}
auto localPos = mapFromGlobal(QCursor::pos());
if (rect().contains(localPos)) {
_mouseSelection = true;
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
updateSelected(localPos);
selectByMouse(globalPosition);
}
});
_menu->popup(e->globalPos());
@ -1573,10 +1574,12 @@ void DialogsInner::contextMenuEvent(QContextMenuEvent *e) {
}
void DialogsInner::onParentGeometryChanged() {
auto localPos = mapFromGlobal(QCursor::pos());
if (rect().contains(localPos)) {
const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true);
updateSelected(localPos);
if (_mouseSelection) {
selectByMouse(globalPosition);
}
}
}
@ -1684,7 +1687,7 @@ void DialogsInner::onFilterUpdate(QString newFilter, bool force) {
}
refresh(true);
}
setMouseSelection(false, true);
clearMouseSelection(true);
}
if (_state != State::Default) {
emit searchMessages();
@ -1697,7 +1700,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) {
if (!_hashtagResults.empty()) {
_hashtagResults.clear();
refresh(true);
setMouseSelection(false, true);
clearMouseSelection(true);
}
return;
}
@ -1717,7 +1720,7 @@ void DialogsInner::onHashtagFilterUpdate(QStringRef newFilter) {
}
}
refresh(true);
setMouseSelection(false, true);
clearMouseSelection(true);
}
DialogsInner::~DialogsInner() {
@ -1733,9 +1736,8 @@ void DialogsInner::clearSearchResults(bool clearPeerSearchResults) {
_lastSearchId = _lastSearchMigratedId = 0;
}
PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) {
_mouseSelection = true;
updateSelected(mapFromGlobal(globalPos));
PeerData *DialogsInner::updateFromParentDrag(QPoint globalPosition) {
selectByMouse(globalPosition);
const auto getPeerFromRow = [](Dialogs::Row *row) -> PeerData* {
if (const auto history = row ? row->history() : nullptr) {
return history->peer;
@ -2106,9 +2108,10 @@ void DialogsInner::refresh(bool toTop) {
update();
}
void DialogsInner::setMouseSelection(bool mouseSelection, bool toTop) {
_mouseSelection = mouseSelection;
if (!_mouseSelection && toTop) {
void DialogsInner::clearMouseSelection(bool clearSelection) {
_mouseSelection = false;
_lastMousePosition = std::nullopt;
if (clearSelection) {
if (_state == State::Default) {
_selected = nullptr;
_importantSwitchSelected = false;
@ -2215,6 +2218,7 @@ void DialogsInner::clearFilter() {
}
void DialogsInner::selectSkip(int32 direction) {
clearMouseSelection();
if (_state == State::Default) {
if (_importantSwitchSelected) {
if (!shownDialogs()->isEmpty() && direction > 0) {
@ -2330,6 +2334,7 @@ void DialogsInner::scrollToEntry(const Dialogs::RowDescriptor &entry) {
}
void DialogsInner::selectSkipPage(int32 pixels, int32 direction) {
clearMouseSelection();
int toSkip = pixels / int(st::dialogsRowHeight);
if (_state == State::Default) {
if (!_selected) {
@ -2457,9 +2462,7 @@ bool DialogsInner::chooseHashtag() {
cSetRecentSearchHashtags(recent);
Local::writeRecentHashtagsAndBots();
emit refreshHashtags();
_mouseSelection = true;
updateSelected();
selectByMouse(QCursor::pos());
} else {
Local::saveRecentSearchHashtags('#' + hashtag->tag);
emit completeHashtag(hashtag->tag);
@ -2534,6 +2537,8 @@ bool DialogsInner::chooseRow() {
emit clearSearchQuery();
}
updateSelectedRow();
_mouseSelection = false;
_lastMousePosition = std::nullopt;
_selected = nullptr;
_hashtagSelected
= _filteredSelected

View File

@ -76,8 +76,6 @@ public:
MsgId lastSearchId() const;
MsgId lastSearchMigratedId() const;
void setMouseSelection(bool mouseSelection, bool toTop = false);
enum class State {
Default,
Filtered,
@ -93,7 +91,7 @@ public:
void onFilterUpdate(QString newFilter, bool force = false);
void onHashtagFilterUpdate(QStringRef newFilter);
PeerData *updateFromParentDrag(QPoint globalPos);
PeerData *updateFromParentDrag(QPoint globalPosition);
void setLoadMoreCallback(Fn<void()> callback) {
_loadMoreCallback = std::move(callback);
@ -154,13 +152,11 @@ private:
not_null<Dialogs::FakeRow*> result,
const Dialogs::RowDescriptor &entry) const;
void clearMouseSelection(bool clearSelection = false);
void userIsContactUpdated(not_null<UserData*> user);
void mousePressReleased(Qt::MouseButton button);
void mousePressReleased(QPoint globalPosition, Qt::MouseButton button);
void clearIrrelevantState();
void updateSelected() {
updateSelected(mapFromGlobal(QCursor::pos()));
}
void updateSelected(QPoint localPos);
void selectByMouse(QPoint globalPosition);
void loadPeerPhotos();
void setImportantSwitchPressed(bool pressed);
void setPressed(Dialogs::Row *pressed);
@ -296,7 +292,7 @@ private:
DialogsList _contacts;
bool _mouseSelection = false;
QPoint _mouseLastGlobalPosition;
std::optional<QPoint> _lastMousePosition;
Qt::MouseButton _pressButton = Qt::LeftButton;
std::unique_ptr<ImportantSwitch> _importantSwitch;

View File

@ -1378,16 +1378,12 @@ void DialogsWidget::keyPressEvent(QKeyEvent *e) {
}
}
} else if (e->key() == Qt::Key_Down) {
_inner->setMouseSelection(false);
_inner->selectSkip(1);
} else if (e->key() == Qt::Key_Up) {
_inner->setMouseSelection(false);
_inner->selectSkip(-1);
} else if (e->key() == Qt::Key_PageDown) {
_inner->setMouseSelection(false);
_inner->selectSkipPage(_scroll->height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
_inner->setMouseSelection(false);
_inner->selectSkipPage(_scroll->height(), -1);
} else {
e->ignore();