From a1049ec7ce017ed62b3bba6b14c805b1de1357d4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 May 2024 19:12:22 +0400 Subject: [PATCH] Add touchscreen preview to recent / channels. --- Telegram/SourceFiles/boxes/peer_list_box.cpp | 21 ++++- Telegram/SourceFiles/boxes/peer_list_box.h | 10 +++ .../dialogs/dialogs_inner_widget.cpp | 13 +-- .../dialogs/dialogs_inner_widget.h | 2 +- .../dialogs/ui/dialogs_suggestions.cpp | 88 ++++++++++++++++++- .../dialogs/ui/dialogs_suggestions.h | 3 + .../window/window_chat_preview.cpp | 16 +++- .../SourceFiles/window/window_chat_preview.h | 7 +- .../window/window_session_controller.cpp | 12 ++- .../window/window_session_controller.h | 6 +- 10 files changed, 154 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index c0ea166e01..22cc40411e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -1556,10 +1556,12 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) { if (_trackPressStart && ((*_trackPressStart - globalPosition).manhattanLength() > QApplication::startDragDistance())) { - _trackPressStart = std::nullopt; + _trackPressStart = {}; _controller->rowTrackPressCancel(); } - selectByMouse(globalPosition); + if (!_controller->rowTrackPressSkipMouseSelection()) { + selectByMouse(globalPosition); + } } void PeerListContent::pressLeftToContextMenu(bool shown) { @@ -1571,13 +1573,24 @@ void PeerListContent::pressLeftToContextMenu(bool shown) { } } +bool PeerListContent::trackRowPressFromGlobal(QPoint globalPosition) { + selectByMouse(globalPosition); + if (const auto row = getRow(_selected.index)) { + if (_controller->rowTrackPress(row)) { + _trackPressStart = globalPosition; + return true; + } + } + return false; +} + void PeerListContent::mousePressEvent(QMouseEvent *e) { _pressButton = e->button(); selectByMouse(e->globalPos()); setPressed(_selected); _trackPressStart = {}; - if (auto row = getRow(_selected.index)) { - auto updateCallback = [this, row, hint = _selected.index] { + if (const auto row = getRow(_selected.index)) { + const auto updateCallback = [this, row, hint = _selected.index] { updateRow(row, hint); }; if (_selected.element) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 47febc1adf..aa24856e28 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -347,7 +347,9 @@ public: virtual void peerListSortRows(Fn compare) = 0; virtual int peerListPartitionRows(Fn border) = 0; virtual std::shared_ptr peerListUiShow() = 0; + virtual void peerListPressLeftToContextMenu(bool shown) = 0; + virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0; template void peerListAddSelectedPeers(PeerDataRange &&range) { @@ -484,6 +486,9 @@ public: } virtual void rowTrackPressCancel() { } + virtual bool rowTrackPressSkipMouseSelection() { + return false; + } virtual void loadMoreRows() { } @@ -663,6 +668,7 @@ public: void mouseLeftGeometry(); void pressLeftToContextMenu(bool shown); + bool trackRowPressFromGlobal(QPoint globalPosition); void setSearchMode(PeerListSearchMode mode); void changeCheckState( @@ -1000,9 +1006,13 @@ public: not_null row, bool highlightRow, Fn)> destroyed = nullptr) override; + void peerListPressLeftToContextMenu(bool shown) override { _content->pressLeftToContextMenu(shown); } + bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override { + return _content->trackRowPressFromGlobal(globalPosition); + } protected: not_null content() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 343730d18b..d0d8b8fe7f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1526,7 +1526,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { if (alt && showChatPreview()) { return; } else if (!alt && isUserpicPress()) { - scheduleChatPreview(); + scheduleChatPreview(e->globalPos()); } if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { @@ -2440,12 +2440,16 @@ void InnerWidget::chatPreviewShown(bool shown, RowDescriptor row) { } } -bool InnerWidget::scheduleChatPreview() { +bool InnerWidget::scheduleChatPreview(QPoint positionOverride) { const auto row = computeChatPreviewRow(); const auto callback = crl::guard(this, [=](bool shown) { chatPreviewShown(shown, row); }); - _chatPreviewScheduled = _controller->scheduleChatPreview(row, callback); + _chatPreviewScheduled = _controller->scheduleChatPreview( + row, + callback, + nullptr, + positionOverride); return _chatPreviewScheduled; } @@ -2544,8 +2548,7 @@ bool InnerWidget::processTouchEvent(not_null e) { return false; } selectByMouse(*point); - const auto onlyUserpic = true; - if (isUserpicPress() && scheduleChatPreview()) { + if (isUserpicPressOnWide() && scheduleChatPreview(*point)) { _chatPreviewTouchGlobal = point; } else if (!_dragging) { _touchDragStartGlobal = point; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index ccca8eb718..9f069c243d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -126,7 +126,7 @@ public: [[nodiscard]] bool isUserpicPress() const; [[nodiscard]] bool isUserpicPressOnWide() const; void cancelChatPreview(); - bool scheduleChatPreview(); + bool scheduleChatPreview(QPoint positionOverride); bool showChatPreview(); void chatPreviewShown(bool shown, RowDescriptor row = {}); bool chooseRow( diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index 179b088870..ad6fe1f698 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -99,10 +99,17 @@ public: bool rowTrackPress(not_null row) override; void rowTrackPressCancel() override; + bool rowTrackPressSkipMouseSelection() override; + + bool processTouchEvent(not_null e); + void setupTouchChatPreview(not_null scroll); private: const not_null _window; + std::optional _chatPreviewTouchGlobal; + rpl::event_stream<> _touchCancelRequests; + }; class RecentsController final : public ControllerWithPreviews { @@ -426,22 +433,81 @@ bool ControllerWithPreviews::rowTrackPress(not_null row) { delegate()->peerListPressLeftToContextMenu(shown); }); if (base::IsAltPressed()) { - _window->showChatPreview({ history, FullMsgId() }, callback); + _window->showChatPreview( + { history, FullMsgId() }, + callback, + nullptr, + _chatPreviewTouchGlobal); return false; } const auto point = delegate()->peerListLastRowMousePosition(); const auto &st = computeListSt().item; if (point && point->x() < st.photoPosition.x() + st.photoSize) { - _window->scheduleChatPreview({ history, FullMsgId() }, callback); + _window->scheduleChatPreview( + { history, FullMsgId() }, + callback, + nullptr, + _chatPreviewTouchGlobal); return true; } return false; } void ControllerWithPreviews::rowTrackPressCancel() { + _chatPreviewTouchGlobal = {}; _window->cancelScheduledPreview(); } +bool ControllerWithPreviews::rowTrackPressSkipMouseSelection() { + return _chatPreviewTouchGlobal.has_value(); +} + +bool ControllerWithPreviews::processTouchEvent(not_null e) { + const auto point = e->touchPoints().empty() + ? std::optional() + : e->touchPoints().front().screenPos().toPoint(); + switch (e->type()) { + case QEvent::TouchBegin: { + if (!point) { + return false; + } + _chatPreviewTouchGlobal = point; + if (!delegate()->peerListTrackRowPressFromGlobal(*point)) { + _chatPreviewTouchGlobal = {}; + } + } break; + + case QEvent::TouchUpdate: { + if (!point) { + return false; + } + if (_chatPreviewTouchGlobal) { + const auto delta = (*_chatPreviewTouchGlobal - *point); + if (delta.manhattanLength() > computeListSt().item.photoSize) { + rowTrackPressCancel(); + } + } + } break; + + case QEvent::TouchEnd: + case QEvent::TouchCancel: { + if (_chatPreviewTouchGlobal) { + rowTrackPressCancel(); + } + } break; + } + return false; +} + +void ControllerWithPreviews::setupTouchChatPreview( + not_null scroll) { + _touchCancelRequests.events() | rpl::start_with_next([=] { + QTouchEvent ev(QEvent::TouchCancel); + ev.setTimestamp(crl::now()); + QGuiApplication::sendEvent(scroll, &ev); + }, lifetime()); +} + RecentsController::RecentsController( not_null window, RecentPeersList list) @@ -1030,6 +1096,7 @@ void Suggestions::setupChats() { }, _topPeers->lifetime()); _chatsScroll->setVisible(_tab.current() == Tab::Chats); + _chatsScroll->setCustomTouchProcess(_recentProcessTouch); } void Suggestions::handlePressForChatPreview( @@ -1063,6 +1130,11 @@ void Suggestions::setupChannels() { anim::type::instant); _channelsScroll->setVisible(_tab.current() == Tab::Channels); + _channelsScroll->setCustomTouchProcess([=](not_null e) { + const auto myChannels = _myChannelsProcessTouch(e); + const auto recommendations = _recommendationsProcessTouch(e); + return myChannels || recommendations; + }); } void Suggestions::selectJump(Qt::Key direction, int pageSize) { @@ -1371,6 +1443,9 @@ object_ptr> Suggestions::setupRecentPeers( controller->setStyleOverrides(&st::recentPeersList); _recentCount = controller->count(); + _recentProcessTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; controller->chosen( ) | rpl::start_with_next([=](not_null peer) { @@ -1419,6 +1494,7 @@ object_ptr> Suggestions::setupRecentPeers( delegate->setContent(raw); controller->setDelegate(delegate); + controller->setupTouchChatPreview(_chatsScroll.get()); return object_ptr>(this, std::move(content)); } @@ -1474,6 +1550,9 @@ object_ptr> Suggestions::setupMyChannels() { controller->setStyleOverrides(&st::recentPeersList); _myChannelsCount = controller->count(); + _myChannelsProcessTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; controller->chosen( ) | rpl::start_with_next([=](not_null peer) { @@ -1535,6 +1614,7 @@ object_ptr> Suggestions::setupMyChannels() { delegate->setContent(raw); controller->setDelegate(delegate); + controller->setupTouchChatPreview(_channelsScroll.get()); return object_ptr>(this, std::move(content)); } @@ -1549,6 +1629,9 @@ object_ptr> Suggestions::setupRecommendations() { controller->setStyleOverrides(&st::recentPeersList); _recommendationsCount = controller->count(); + _recommendationsProcessTouch = [=](not_null e) { + return controller->processTouchEvent(e); + }; _tab.value() | rpl::filter( rpl::mappers::_1 == Tab::Channels @@ -1603,6 +1686,7 @@ object_ptr> Suggestions::setupRecommendations() { delegate->setContent(raw); controller->setDelegate(delegate); + controller->setupTouchChatPreview(_channelsScroll.get()); return object_ptr>(this, std::move(content)); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index 734ec554c3..8b33718e6e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -139,6 +139,7 @@ private: Fn _recentSelectJump; Fn _recentUpdateFromParentDrag; Fn _recentDragLeft; + Fn)> _recentProcessTouch; const not_null*> _recentPeers; const not_null*> _emptyRecent; @@ -150,6 +151,7 @@ private: Fn _myChannelsSelectJump; Fn _myChannelsUpdateFromParentDrag; Fn _myChannelsDragLeft; + Fn)> _myChannelsProcessTouch; const not_null*> _myChannels; rpl::variable _recommendationsCount; @@ -157,6 +159,7 @@ private: Fn _recommendationsSelectJump; Fn _recommendationsUpdateFromParentDrag; Fn _recommendationsDragLeft; + Fn)> _recommendationsProcessTouch; const not_null*> _recommendations; const not_null*> _emptyChannels; diff --git a/Telegram/SourceFiles/window/window_chat_preview.cpp b/Telegram/SourceFiles/window/window_chat_preview.cpp index 60ce98bf6e..e95d58d4c5 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.cpp +++ b/Telegram/SourceFiles/window/window_chat_preview.cpp @@ -34,7 +34,8 @@ ChatPreviewManager::ChatPreviewManager( bool ChatPreviewManager::show( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { cancelScheduled(); _topicLifetime.destroy(); if (const auto topic = row.key.topic()) { @@ -94,7 +95,7 @@ bool ChatPreviewManager::show( if (callback) { callback(true); } - _menu->popup(QCursor::pos()); + _menu->popup(positionOverride.value_or(QCursor::pos())); return true; } @@ -102,7 +103,8 @@ bool ChatPreviewManager::show( bool ChatPreviewManager::schedule( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { cancelScheduled(); _topicLifetime.destroy(); if (const auto topic = row.key.topic()) { @@ -116,17 +118,23 @@ bool ChatPreviewManager::schedule( _scheduled = std::move(row); _scheduledCallback = std::move(callback); _scheduledParentOverride = std::move(parentOverride); + _scheduledPositionOverride = positionOverride; _timer.callOnce(kChatPreviewDelay); return true; } void ChatPreviewManager::showScheduled() { - show(base::take(_scheduled), base::take(_scheduledCallback)); + show( + base::take(_scheduled), + base::take(_scheduledCallback), + nullptr, + base::take(_scheduledPositionOverride)); } void ChatPreviewManager::cancelScheduled() { _scheduled = {}; _scheduledCallback = nullptr; + _scheduledPositionOverride = {}; _timer.cancel(); } diff --git a/Telegram/SourceFiles/window/window_chat_preview.h b/Telegram/SourceFiles/window/window_chat_preview.h index 6101ac3e99..3ad2b9de2a 100644 --- a/Telegram/SourceFiles/window/window_chat_preview.h +++ b/Telegram/SourceFiles/window/window_chat_preview.h @@ -26,11 +26,13 @@ public: bool show( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); bool schedule( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); void cancelScheduled(); private: @@ -40,6 +42,7 @@ private: Dialogs::RowDescriptor _scheduled; Fn _scheduledCallback; QPointer _scheduledParentOverride; + std::optional _scheduledPositionOverride; base::Timer _timer; rpl::lifetime _topicLifetime; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 4a004dce81..59d12ab358 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -2979,21 +2979,25 @@ QString SessionController::premiumRef() const { bool SessionController::showChatPreview( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { return _chatPreviewManager->show( std::move(row), std::move(callback), - std::move(parentOverride)); + std::move(parentOverride), + positionOverride); } bool SessionController::scheduleChatPreview( Dialogs::RowDescriptor row, Fn callback, - QPointer parentOverride) { + QPointer parentOverride, + std::optional positionOverride) { return _chatPreviewManager->schedule( std::move(row), std::move(callback), - std::move(parentOverride)); + std::move(parentOverride), + positionOverride); } void SessionController::cancelScheduledPreview() { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 37f45f86e7..35e6909039 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -604,11 +604,13 @@ public: bool showChatPreview( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); bool scheduleChatPreview( Dialogs::RowDescriptor row, Fn callback = nullptr, - QPointer parentOverride = nullptr); + QPointer parentOverride = nullptr, + std::optional positionOverride = {}); void cancelScheduledPreview(); [[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e) const;