From b82fa3112c9502eabadccabbbb82316140788dd2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Sep 2024 15:56:16 +0200 Subject: [PATCH] Improve touchscreen swipt-to-reply. --- .../history/history_inner_widget.cpp | 23 +++++++++--- .../history/history_inner_widget.h | 1 + .../history/history_view_swipe.cpp | 36 +++++++++++++++---- .../SourceFiles/history/history_view_swipe.h | 3 +- .../history/view/history_view_list_widget.cpp | 29 ++++++++++++--- .../history/view/history_view_list_widget.h | 2 ++ .../view/history_view_replies_section.cpp | 2 +- 7 files changed, 77 insertions(+), 19 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index b7687266e..c38ad8e0e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -640,7 +640,7 @@ void HistoryInner::setupSwipeReply() { return false; }); return result; - }); + }, _touchMaybeSelecting.value()); } bool HistoryInner::hasSelectRestriction() const { @@ -1497,6 +1497,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) { _touchScroll = _touchSelect = false; _horizontalScrollLocked = false; _touchScrollState = Ui::TouchScrollState::Manual; + _touchMaybeSelecting = false; mouseActionCancel(); return; } @@ -1519,6 +1520,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) { _touchInProgress = true; _horizontalScrollLocked = false; if (_touchScrollState == Ui::TouchScrollState::Auto) { + _touchMaybeSelecting = false; _touchScrollState = Ui::TouchScrollState::Acceleration; _touchWaitingAcceleration = true; _touchAccelerationTime = crl::now(); @@ -1526,6 +1528,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) { _touchStart = _touchPos; } else { _touchScroll = false; + _touchMaybeSelecting = true; _touchSelectTimer.callOnce(QApplication::startDragTime()); } _touchSelect = false; @@ -1539,6 +1542,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) { mouseActionUpdate(_touchPos); } else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchSelectTimer.cancel(); + _touchMaybeSelecting = false; _touchScroll = true; touchUpdateSpeed(); } @@ -1560,11 +1564,18 @@ void HistoryInner::touchEvent(QTouchEvent *e) { return; } _touchInProgress = false; + const auto notMoved = (_touchPos - _touchStart).manhattanLength() + < QApplication::startDragDistance(); auto weak = Ui::MakeWeak(this); if (_touchSelect) { - mouseActionFinish(_touchPos, Qt::RightButton); - QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos); - showContextMenu(&contextMenu, true); + if (notMoved || _touchMaybeSelecting.current()) { + mouseActionFinish(_touchPos, Qt::RightButton); + auto contextMenu = QContextMenuEvent( + QContextMenuEvent::Mouse, + mapFromGlobal(_touchPos), + _touchPos); + showContextMenu(&contextMenu, true); + } _touchScroll = false; } else if (_touchScroll) { if (_touchScrollState == Ui::TouchScrollState::Manual) { @@ -1582,12 +1593,13 @@ void HistoryInner::touchEvent(QTouchEvent *e) { _touchWaitingAcceleration = false; _touchPrevPosValid = false; } - } else { // One short tap is like left mouse click. + } else if (notMoved) { // One short tap is like left mouse click. mouseActionStart(_touchPos, Qt::LeftButton); mouseActionFinish(_touchPos, Qt::LeftButton); } if (weak) { _touchSelectTimer.cancel(); + _touchMaybeSelecting = false; _touchSelect = false; } } break; @@ -3789,6 +3801,7 @@ MessageIdsList HistoryInner::getSelectedItems() const { void HistoryInner::onTouchSelect() { _touchSelect = true; + _touchMaybeSelecting = true; mouseActionStart(_touchPos, Qt::LeftButton); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index f4338a3ce..030a53adc 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -513,6 +513,7 @@ private: bool _touchSelect = false; bool _touchInProgress = false; QPoint _touchStart, _touchPrevPos, _touchPos; + rpl::variable _touchMaybeSelecting; base::Timer _touchSelectTimer; Ui::DraggingScrollManager _selectScroll; diff --git a/Telegram/SourceFiles/history/history_view_swipe.cpp b/Telegram/SourceFiles/history/history_view_swipe.cpp index 42d3ec160..8d7648673 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.cpp +++ b/Telegram/SourceFiles/history/history_view_swipe.cpp @@ -25,7 +25,8 @@ void SetupSwipeHandler( not_null widget, not_null scroll, Fn update, - Fn generateFinishByTop) { + Fn generateFinishByTop, + rpl::producer dontStart) { constexpr auto kThresholdWidth = 50; const auto threshold = style::ConvertFloatScale(kThresholdWidth); struct State { @@ -37,6 +38,7 @@ void SetupSwipeHandler( QPointF startAt; QPointF delta; int cursorTop = 0; + bool dontStart = false; bool started = false; bool reached = false; bool touch = false; @@ -44,6 +46,12 @@ void SetupSwipeHandler( rpl::lifetime lifetime; }; const auto state = widget->lifetime().make_state(); + std::move( + dontStart + ) | rpl::start_with_next([=](bool dontStart) { + state->dontStart = dontStart; + }, state->lifetime); + const auto updateRatio = [=](float64 ratio) { update({ .ratio = std::clamp(ratio, 0., 1.5), @@ -83,12 +91,15 @@ void SetupSwipeHandler( state->reached = false; }; scroll->scrolls() | rpl::start_with_next([=] { - processEnd(); + if (state->orientation != Qt::Vertical) { + processEnd(); + } }, state->lifetime); const auto animationReachCallback = [=] { updateRatio(state->delta.x() / threshold); }; struct UpdateArgs { + QPoint globalCursor; QPointF position; QPointF delta; bool touch = false; @@ -99,8 +110,7 @@ void SetupSwipeHandler( state->touch = args.touch; state->startAt = args.position; state->delta = QPointF(); - state->cursorTop = widget->mapFromGlobal( - QCursor::pos()).y(); + state->cursorTop = widget->mapFromGlobal(args.globalCursor).y(); state->finishByTopData = generateFinishByTop( state->cursorTop); if (!state->finishByTopData.callback) { @@ -112,7 +122,9 @@ void SetupSwipeHandler( - std::abs(args.delta.y()); constexpr auto kOrientationThreshold = 1.; if (diffXtoY > kOrientationThreshold) { - setOrientation(Qt::Horizontal); + if (!state->dontStart) { + setOrientation(Qt::Horizontal); + } } else if (diffXtoY < -kOrientationThreshold) { setOrientation(Qt::Vertical); } else { @@ -143,12 +155,12 @@ void SetupSwipeHandler( const auto type = e->type(); switch (type) { case QEvent::Leave: { - if (state->orientation) { + if (state->orientation == Qt::Horizontal) { processEnd(); } } break; case QEvent::MouseMove: { - if (state->orientation) { + if (state->orientation == Qt::Horizontal) { const auto m = static_cast(e.get()); if (std::abs(m->pos().y() - state->cursorTop) > QApplication::startDragDistance()) { @@ -165,6 +177,9 @@ void SetupSwipeHandler( && (t->device()->type() == base::TouchDevice::TouchScreen); if (!Platform::IsMac() && !touchscreen) { break; + } else if (type == QEvent::TouchBegin) { + // Reset state in case we lost some TouchEnd. + processEnd(); } const auto &touches = t->touchPoints(); const auto released = [&](int index) { @@ -184,6 +199,9 @@ void SetupSwipeHandler( : (state->startAt - touches[0].pos())); } else { updateWith({ + .globalCursor = (touchscreen + ? touches[0].screenPos().toPoint() + : QCursor::pos()), .position = touches[0].pos(), .delta = state->startAt - touches[0].pos(), .touch = true, @@ -198,6 +216,9 @@ void SetupSwipeHandler( const auto phase = w->phase(); if (Platform::IsMac() || phase == Qt::NoScrollPhase) { break; + } else if (phase == Qt::ScrollBegin) { + // Reset state in case we lost some TouchEnd. + processEnd(); } const auto cancel = w->buttons() || (phase == Qt::ScrollEnd) @@ -206,6 +227,7 @@ void SetupSwipeHandler( processEnd(); } else { updateWith({ + .globalCursor = w->globalPos(), .position = QPointF(), .delta = state->delta - Ui::ScrollDelta(w), .touch = false, diff --git a/Telegram/SourceFiles/history/history_view_swipe.h b/Telegram/SourceFiles/history/history_view_swipe.h index e7fb2ea4a..fe9ec2ea0 100644 --- a/Telegram/SourceFiles/history/history_view_swipe.h +++ b/Telegram/SourceFiles/history/history_view_swipe.h @@ -25,6 +25,7 @@ void SetupSwipeHandler( not_null widget, not_null scroll, Fn update, - Fn generateFinishByTop); + Fn generateFinishByTop, + rpl::producer dontStart = nullptr); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index c6b0fd066..e54affc2c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2940,6 +2940,7 @@ void ListWidget::touchEvent(QTouchEvent *e) { _touchSelectTimer.cancel(); _touchScroll = _touchSelect = false; _touchScrollState = Ui::TouchScrollState::Manual; + _touchMaybeSelecting = false; mouseActionCancel(); return; } @@ -2960,6 +2961,7 @@ void ListWidget::touchEvent(QTouchEvent *e) { _touchInProgress = true; if (_touchScrollState == Ui::TouchScrollState::Auto) { + _touchMaybeSelecting = false; _touchScrollState = Ui::TouchScrollState::Acceleration; _touchWaitingAcceleration = true; _touchAccelerationTime = crl::now(); @@ -2967,6 +2969,7 @@ void ListWidget::touchEvent(QTouchEvent *e) { _touchStart = _touchPos; } else { _touchScroll = false; + _touchMaybeSelecting = true; _touchSelectTimer.callOnce(QApplication::startDragTime()); } _touchSelect = false; @@ -2979,6 +2982,7 @@ void ListWidget::touchEvent(QTouchEvent *e) { mouseActionUpdate(_touchPos); } else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) { _touchSelectTimer.cancel(); + _touchMaybeSelecting = false; _touchScroll = true; touchUpdateSpeed(); } @@ -2996,13 +3000,22 @@ void ListWidget::touchEvent(QTouchEvent *e) { } break; case QEvent::TouchEnd: { - if (!_touchInProgress) return; + if (!_touchInProgress) { + return; + } _touchInProgress = false; auto weak = Ui::MakeWeak(this); + const auto notMoved = (_touchPos - _touchStart).manhattanLength() + < QApplication::startDragDistance(); if (_touchSelect) { - mouseActionFinish(_touchPos, Qt::RightButton); - QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos); - showContextMenu(&contextMenu, true); + if (notMoved || _touchMaybeSelecting.current()) { + mouseActionFinish(_touchPos, Qt::RightButton); + auto contextMenu = QContextMenuEvent( + QContextMenuEvent::Mouse, + mapFromGlobal(_touchPos), + _touchPos); + showContextMenu(&contextMenu, true); + } _touchScroll = false; } else if (_touchScroll) { if (_touchScrollState == Ui::TouchScrollState::Manual) { @@ -3019,12 +3032,13 @@ void ListWidget::touchEvent(QTouchEvent *e) { _touchWaitingAcceleration = false; _touchPrevPosValid = false; } - } else { // One short tap is like left mouse click. + } else if (notMoved) { // One short tap is like left mouse click. mouseActionStart(_touchPos, Qt::LeftButton); mouseActionFinish(_touchPos, Qt::LeftButton); } if (weak) { _touchSelectTimer.cancel(); + _touchMaybeSelecting = false; _touchSelect = false; } } break; @@ -3064,6 +3078,10 @@ void ListWidget::touchScrollUpdated(const QPoint &screenPos) { touchUpdateSpeed(); } +rpl::producer ListWidget::touchMaybeSelectingValue() const { + return _touchMaybeSelecting.value(); +} + void ListWidget::enterEventHook(QEnterEvent *e) { mouseActionUpdate(QCursor::pos()); return TWidget::enterEventHook(e); @@ -3120,6 +3138,7 @@ void ListWidget::updateDragSelection() { void ListWidget::onTouchSelect() { _touchSelect = true; + _touchMaybeSelecting = true; mouseActionStart(_touchPos, Qt::LeftButton); } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index d0507474f..09121d357 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -327,6 +327,7 @@ public: void selectItemAsGroup(not_null item); void touchScrollUpdated(const QPoint &screenPos); + [[nodiscard]] rpl::producer touchMaybeSelectingValue() const; [[nodiscard]] bool loadedAtTopKnown() const; [[nodiscard]] bool loadedAtTop() const; @@ -830,6 +831,7 @@ private: bool _touchSelect = false; bool _touchInProgress = false; QPoint _touchStart, _touchPrevPos, _touchPos; + rpl::variable _touchMaybeSelecting; base::Timer _touchSelectTimer; Ui::DraggingScrollManager _selectScroll; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index def3156c2..e54af4e2e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -923,7 +923,7 @@ void RepliesWidget::setupSwipeReply() { }); }; return result; - }); + }, _inner->touchMaybeSelectingValue()); } void RepliesWidget::chooseAttach(