diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fa06b446a..8b081b263 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1516,6 +1516,8 @@ PRIVATE window/section_widget.h window/window_adaptive.cpp window/window_adaptive.h + window/window_chat_preview.cpp + window/window_chat_preview.h window/window_connecting_widget.cpp window/window_connecting_widget.h window/window_controller.cpp diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index dc2728d21..112e4c9df 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -86,7 +86,6 @@ namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; -constexpr auto kChatPreviewDelay = crl::time(1000); [[nodiscard]] int FixedOnTopDialogsCount(not_null list) { auto result = 0; @@ -216,7 +215,6 @@ InnerWidget::InnerWidget( + st::defaultDialogRow.photoSize + st::defaultDialogRow.padding.left()) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) -, _chatPreviewTimer([=] { showChatPreview(true); }) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -714,8 +712,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { context.active = active; context.selected = _menuRow.key ? (row->key() == _menuRow.key) - : _chatPreviewKey - ? (row->key() == _chatPreviewKey) + : _chatPreviewRow.key + ? (row->key() == _chatPreviewRow.key) : selected; context.topicJumpSelected = selected && _selectedTopicJump @@ -981,6 +979,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto active = isSearchResultActive(result.get(), activeEntry); const auto selected = _menuRow.key ? isSearchResultActive(result.get(), _menuRow) + : _chatPreviewRow.key + ? isSearchResultActive(result.get(), _chatPreviewRow) : (from == (isPressed() ? _searchedPressed : _searchedSelected)); @@ -1330,15 +1330,18 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) { return; } selectByMouse(globalPosition); - if (!isUserpicPress()) { + if (_chatPreviewScheduled && !isUserpicPress()) { cancelChatPreview(); } } void InnerWidget::cancelChatPreview() { - _chatPreviewTimer.cancel(); - _chatPreviewWillBeFor = {}; _chatPreviewTouchGlobal = {}; + _chatPreviewScheduled = false; + if (_chatPreviewRow.key) { + updateDialogRow(base::take(_chatPreviewRow)); + } + _controller->cancelScheduledPreview(); } void InnerWidget::clearIrrelevantState() { @@ -1484,26 +1487,24 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { } } -Key InnerWidget::computeChatPreviewRow() const { - auto result = computeChosenRow().key; - if (const auto peer = result.peer()) { +RowDescriptor InnerWidget::computeChatPreviewRow() const { + auto result = computeChosenRow(); + if (const auto peer = result.key.peer()) { const auto topicId = _pressedTopicJump ? _pressedTopicJumpRootId : 0; if (const auto topic = peer->forumTopicFor(topicId)) { - return topic; + return { topic, FullMsgId() }; } } - return result; + return { result.key, result.message.fullId }; } void InnerWidget::processGlobalForceClick(QPoint globalPosition) { const auto parent = parentWidget(); if (_pressButton == Qt::LeftButton - && parent->rect().contains(parent->mapFromGlobal(globalPosition)) - && pressShowsPreview(false)) { - _chatPreviewWillBeFor = computeChatPreviewRow(); - showChatPreview(false); + && parent->rect().contains(parent->mapFromGlobal(globalPosition))) { + showChatPreview(); } } @@ -1520,14 +1521,10 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { setSearchedPressed(_searchedSelected); const auto alt = (e->modifiers() & Qt::AltModifier); - const auto onlyUserpic = !alt; - if (pressShowsPreview(onlyUserpic)) { - _chatPreviewWillBeFor = computeChatPreviewRow(); - if (alt) { - showChatPreview(onlyUserpic); - return; - } - _chatPreviewTimer.callOnce(kChatPreviewDelay); + if (alt && showChatPreview()) { + return; + } else if (!alt && isUserpicPress()) { + scheduleChatPreview(); } if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { @@ -1599,7 +1596,7 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { } ClickHandler::pressed(); if (anim::Disabled() - && !_chatPreviewTimer.isActive() + && !_chatPreviewScheduled && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } @@ -1866,7 +1863,9 @@ void InnerWidget::mousePressReleased( QPoint globalPosition, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { - _chatPreviewTimer.cancel(); + if (_chatPreviewScheduled) { + _controller->cancelScheduledPreview(); + } _pressButton = Qt::NoButton; const auto wasDragging = finishReorderOnRelease(); @@ -2412,67 +2411,40 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { }); } -void InnerWidget::showChatPreview(bool onlyUserpic) { - const auto key = base::take(_chatPreviewWillBeFor); - const auto touchGlobal = base::take(_chatPreviewTouchGlobal); - cancelChatPreview(); - if (!pressShowsPreview(onlyUserpic) || key != computeChatPreviewRow()) { - return; - } - if (onlyUserpic && touchGlobal) { - _touchCancelRequests.fire({}); - } - ClickHandler::unpressed(); - mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); +bool InnerWidget::showChatPreview() { + const auto row = computeChatPreviewRow(); + const auto callback = crl::guard(this, [=](bool shown) { + chatPreviewShown(shown, row); + }); + return _controller->showChatPreview(row, callback); +} - _chatPreviewKey = key; - auto preview = HistoryView::MakeChatPreview(this, key.entry()); - if (!preview.menu) { - return; - } - _menu = std::move(preview.menu); - const auto weakMenu = Ui::MakeWeak(_menu.get()); - const auto weakThread = base::make_weak(key.entry()->asThread()); - const auto weakController = base::make_weak(_controller); - std::move( - preview.actions - ) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) { - if (const auto controller = weakController.get()) { - if (const auto thread = weakThread.get()) { - const auto itemId = action.openItemId; - const auto owner = &thread->owner(); - if (action.markRead) { - Window::MarkAsReadThread(thread); - } else if (action.markUnread) { - if (const auto history = thread->asHistory()) { - history->owner().histories().changeDialogUnreadMark( - history, - true); - } - } else if (action.openInfo) { - controller->showPeerInfo(thread); - } else if (const auto item = owner->message(itemId)) { - controller->showMessage(item); - } else { - controller->showThread(thread); - } - } - } - if (const auto strong = weakMenu.data()) { - strong->hideMenu(); - } - }, _menu->lifetime()); - QObject::connect(_menu.get(), &QObject::destroyed, [=] { - if (_chatPreviewKey) { - updateDialogRow(RowDescriptor(base::take(_chatPreviewKey), {})); +void InnerWidget::chatPreviewShown(bool shown, RowDescriptor row) { + _chatPreviewScheduled = false; + if (shown) { + _chatPreviewRow = row; + if (base::take(_chatPreviewTouchGlobal)) { + _touchCancelRequests.fire({}); } + ClickHandler::unpressed(); + mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); + } else { + cancelChatPreview(); const auto globalPosition = QCursor::pos(); if (rect().contains(mapFromGlobal(globalPosition))) { setMouseTracking(true); selectByMouse(globalPosition); } + } +} + +bool InnerWidget::scheduleChatPreview() { + const auto row = computeChatPreviewRow(); + const auto callback = crl::guard(this, [=](bool shown) { + chatPreviewShown(shown, row); }); - _menu->popup(_lastMousePosition.value_or(QCursor::pos())); + _chatPreviewScheduled = _controller->scheduleChatPreview(row, callback); + return _chatPreviewScheduled; } void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { @@ -2571,10 +2543,8 @@ bool InnerWidget::processTouchEvent(not_null e) { } selectByMouse(*point); const auto onlyUserpic = true; - if (pressShowsPreview(onlyUserpic)) { + if (isUserpicPress() && scheduleChatPreview()) { _chatPreviewTouchGlobal = point; - _chatPreviewWillBeFor = computeChatPreviewRow(); - _chatPreviewTimer.callOnce(kChatPreviewDelay); } else if (!_dragging) { _touchDragStartGlobal = point; _touchDragPinnedTimer.callOnce(QApplication::startDragTime()); @@ -2894,11 +2864,8 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { refresh(); clearMouseSelection(true); } - if (_chatPreviewWillBeFor.topic() == topic) { - _chatPreviewWillBeFor = {}; - } - if (_chatPreviewKey.topic() == topic) { - _chatPreviewKey = {}; + if (_chatPreviewRow.key.topic() == topic) { + _chatPreviewRow = {}; } }, _searchResultsLifetime); } @@ -3793,18 +3760,6 @@ bool InnerWidget::isUserpicPressOnWide() const { return isUserpicPress() && (width() > _narrowWidth); } -bool InnerWidget::pressShowsPreview(bool onlyUserpic) const { - if (onlyUserpic && !isUserpicPress()) { - return false; - } - const auto key = computeChosenRow().key; - if (const auto history = key.history()) { - return !history->peer->isForum() - || (_pressedTopicJump && _pressedTopicJumpRootId); - } - return key.topic() != nullptr; -} - bool InnerWidget::chooseRow( Qt::KeyboardModifiers modifiers, MsgId pressedTopicRootId) { @@ -4157,7 +4112,7 @@ void InnerWidget::setupShortcuts() { && !_controller->isLayerShown() && !_controller->window().locked() && !_childListShown.current().shown - && !_chatPreviewKey; + && !_chatPreviewRow.key; }) | rpl::start_with_next([=](not_null request) { using Command = Shortcuts::Command; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 286f84842..ccca8eb71 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -125,9 +125,10 @@ public: [[nodiscard]] bool isUserpicPress() const; [[nodiscard]] bool isUserpicPressOnWide() const; - [[nodiscard]] bool pressShowsPreview(bool onlyUserpic) const; void cancelChatPreview(); - void showChatPreview(bool onlyUserpic); + bool scheduleChatPreview(); + bool showChatPreview(); + void chatPreviewShown(bool shown, RowDescriptor row = {}); bool chooseRow( Qt::KeyboardModifiers modifiers = {}, MsgId pressedTopicRootId = {}); @@ -400,7 +401,7 @@ private: void trackSearchResultsHistory(not_null history); [[nodiscard]] QBrush currentBg() const; - [[nodiscard]] Key computeChatPreviewRow() const; + [[nodiscard]] RowDescriptor computeChatPreviewRow() const; [[nodiscard]] const std::vector &pinnedChatsOrder() const; void checkReorderPinnedStart(QPoint localPosition); @@ -525,9 +526,8 @@ private: rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; - base::Timer _chatPreviewTimer; - Key _chatPreviewWillBeFor; - Key _chatPreviewKey; + RowDescriptor _chatPreviewRow; + bool _chatPreviewScheduled = false; std::optional _chatPreviewTouchGlobal; base::Timer _touchDragPinnedTimer; std::optional _touchDragStartGlobal; diff --git a/Telegram/SourceFiles/window/window_chat_preview.cpp b/Telegram/SourceFiles/window/window_chat_preview.cpp new file mode 100644 index 000000000..cee4671dd --- /dev/null +++ b/Telegram/SourceFiles/window/window_chat_preview.cpp @@ -0,0 +1,126 @@ +/* +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 "window/window_chat_preview.h" + +#include "data/data_forum_topic.h" +#include "data/data_histories.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/view/history_view_chat_preview.h" +#include "mainwidget.h" +#include "ui/widgets/popup_menu.h" +#include "window/window_peer_menu.h" +#include "window/window_session_controller.h" + +namespace Window { +namespace { + +constexpr auto kChatPreviewDelay = crl::time(1000); + +} // namespace + +ChatPreviewManager::ChatPreviewManager( + not_null controller) +: _controller(controller) +, _timer([=] { showScheduled(); }) { +} + +bool ChatPreviewManager::show( + Dialogs::RowDescriptor row, + Fn callback) { + cancelScheduled(); + _topicLifetime.destroy(); + if (const auto topic = row.key.topic()) { + _topicLifetime = topic->destroyed() | rpl::start_with_next([=] { + _menu = nullptr; + }); + } + + const auto parent = _controller->content(); + auto preview = HistoryView::MakeChatPreview(parent, row.key.entry()); + if (!preview.menu) { + return false; + } + _menu = std::move(preview.menu); + const auto weakMenu = Ui::MakeWeak(_menu.get()); + const auto weakThread = base::make_weak(row.key.entry()->asThread()); + const auto weakController = base::make_weak(_controller); + std::move( + preview.actions + ) | rpl::start_with_next([=](HistoryView::ChatPreviewAction action) { + if (const auto controller = weakController.get()) { + if (const auto thread = weakThread.get()) { + const auto itemId = action.openItemId; + const auto owner = &thread->owner(); + if (action.markRead) { + MarkAsReadThread(thread); + } else if (action.markUnread) { + if (const auto history = thread->asHistory()) { + history->owner().histories().changeDialogUnreadMark( + history, + true); + } + } else if (action.openInfo) { + controller->showPeerInfo(thread); + } else if (const auto item = owner->message(itemId)) { + controller->showMessage(item); + } else { + controller->showThread(thread); + } + } + } + if (const auto strong = weakMenu.data()) { + strong->hideMenu(); + } + }, _menu->lifetime()); + QObject::connect(_menu.get(), &QObject::destroyed, [=] { + _topicLifetime.destroy(); + callback(false); + }); + + callback(true); + _menu->popup(QCursor::pos()); + + return true; +} + +bool ChatPreviewManager::schedule( + Dialogs::RowDescriptor row, + Fn callback) { + cancelScheduled(); + _topicLifetime.destroy(); + if (const auto topic = row.key.topic()) { + _topicLifetime = topic->destroyed() | rpl::start_with_next([=] { + cancelScheduled(); + _menu = nullptr; + }); + } else if (const auto history = row.key.history()) { + if (history->peer->isForum()) { + return false; + } + } else { + return false; + } + _scheduled = row; + _scheduledCallback = std::move(callback); + _timer.callOnce(kChatPreviewDelay); + return true; +} + +void ChatPreviewManager::showScheduled() { + show(base::take(_scheduled), base::take(_scheduledCallback)); +} + +void ChatPreviewManager::cancelScheduled() { + _scheduled = {}; + _scheduledCallback = nullptr; + _timer.cancel(); +} + +} // namespace Window \ No newline at end of file diff --git a/Telegram/SourceFiles/window/window_chat_preview.h b/Telegram/SourceFiles/window/window_chat_preview.h new file mode 100644 index 000000000..c3744e697 --- /dev/null +++ b/Telegram/SourceFiles/window/window_chat_preview.h @@ -0,0 +1,48 @@ +/* +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 +*/ +#pragma once + +#include "base/timer.h" +#include "base/unique_qptr.h" +#include "dialogs/dialogs_key.h" + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace Window { + +class SessionController; + +class ChatPreviewManager final { +public: + ChatPreviewManager(not_null controller); + + bool show( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + bool schedule( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + void cancelScheduled(); + +private: + void showScheduled(); + + const not_null _controller; + Dialogs::RowDescriptor _scheduled; + Fn _scheduledCallback; + base::Timer _timer; + + rpl::lifetime _topicLifetime; + + base::unique_qptr _menu; + +}; + +} // namespace Window \ No newline at end of file diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 37acebe47..e673da20a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/replace_boost_box.h" #include "boxes/delete_messages_box.h" +#include "window/window_chat_preview.h" #include "window/window_controller.h" #include "window/window_filters_menu.h" #include "info/channel_statistics/earn/info_earn_inner_widget.h" @@ -1179,6 +1180,7 @@ SessionController::SessionController( , _window(window) , _emojiInteractions( std::make_unique(session)) +, _chatPreviewManager(std::make_unique(this)) , _isPrimary(window->isPrimary()) , _sendingAnimation( std::make_unique(this)) @@ -2974,6 +2976,24 @@ QString SessionController::premiumRef() const { return _premiumRef; } +bool SessionController::showChatPreview( + Dialogs::RowDescriptor row, + Fn callback) { + return _chatPreviewManager->show(std::move(row), std::move(callback)); +} + +bool SessionController::scheduleChatPreview( + Dialogs::RowDescriptor row, + Fn callback) { + return _chatPreviewManager->schedule( + std::move(row), + std::move(callback)); +} + +void SessionController::cancelScheduledPreview() { + _chatPreviewManager->cancelScheduled(); +} + bool SessionController::contentOverlapped(QWidget *w, QPaintEvent *e) const { return widget()->contentOverlapped(w, e); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 38085b945..0cbaf0b9a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -88,6 +88,7 @@ using GifPauseReasons = ChatHelpers::PauseReasons; class SectionMemento; class Controller; class FiltersMenu; +class ChatPreviewManager; struct PeerByLinkInfo; @@ -600,6 +601,14 @@ public: void setPremiumRef(const QString &ref); [[nodiscard]] QString premiumRef() const; + bool showChatPreview( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + bool scheduleChatPreview( + Dialogs::RowDescriptor row, + Fn callback = nullptr); + void cancelScheduledPreview(); + [[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e) const; [[nodiscard]] std::shared_ptr uiShow() override; @@ -656,6 +665,7 @@ private: const not_null _window; const std::unique_ptr _emojiInteractions; + const std::unique_ptr _chatPreviewManager; const bool _isPrimary = false; mutable std::shared_ptr _cachedShow;