diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 88d42a190..88778fc72 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -781,6 +781,8 @@ PRIVATE history/view/history_view_about_view.h history/view/history_view_bottom_info.cpp history/view/history_view_bottom_info.h + history/view/history_view_chat_preview.cpp + history/view/history_view_chat_preview.h history/view/history_view_contact_status.cpp history/view/history_view_contact_status.h history/view/history_view_context_menu.cpp diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index cdf2fef0d..fbf6eb513 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_search_from_controllers.h" #include "dialogs/dialogs_search_tags.h" +#include "history/view/history_view_chat_preview.h" #include "history/view/history_view_context_menu.h" #include "history/history.h" #include "history/history_item.h" @@ -80,6 +81,7 @@ namespace { constexpr auto kHashtagResultsLimit = 5; constexpr auto kStartReorderThreshold = 30; +constexpr auto kChatPreviewDelay = crl::time(1000); int FixedOnTopDialogsCount(not_null list) { auto result = 0; @@ -157,6 +159,7 @@ InnerWidget::InnerWidget( + st::defaultDialogRow.padding.left()) , _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) , _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) +, _chatPreviewTimer([=] { showChatPreview(); }) , _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); @@ -651,6 +654,8 @@ void InnerWidget::paintEvent(QPaintEvent *e) { context.active = active; context.selected = _menuRow.key ? (row->key() == _menuRow.key) + : _chatPreviewKey + ? (row->key() == _chatPreviewKey) : selected; context.topicJumpSelected = selected && _selectedTopicJump @@ -1268,6 +1273,14 @@ void InnerWidget::mouseMoveEvent(QMouseEvent *e) { return; } selectByMouse(globalPosition); + if (!isUserpicPress()) { + cancelChatPreview(); + } +} + +void InnerWidget::cancelChatPreview() { + _chatPreviewTimer.cancel(); + _chatPreviewWillBeFor = {}; } void InnerWidget::clearIrrelevantState() { @@ -1490,11 +1503,15 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { row->repaint()); } ClickHandler::pressed(); - if (anim::Disabled() + if (pressShowsPreview()) { + _chatPreviewWillBeFor = computeChosenRow().key; + _chatPreviewTimer.callOnce(kChatPreviewDelay); + } else if (anim::Disabled() && (!_pressed || !_pressed->entry()->isPinnedDialog(_filterId))) { mousePressReleased(e->globalPos(), e->button(), e->modifiers()); } } + const std::vector &InnerWidget::pinnedChatsOrder() const { const auto owner = &session().data(); return _savedSublists @@ -1513,6 +1530,7 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) { < style::ConvertScale(kStartReorderThreshold)) { return; } + cancelChatPreview(); _dragging = _pressed; if (updateReorderIndexGetCount() < 2) { _dragging = nullptr; @@ -2288,6 +2306,33 @@ void InnerWidget::fillArchiveSearchMenu(not_null menu) { }); } +void InnerWidget::showChatPreview() { + const auto key = base::take(_chatPreviewWillBeFor); + cancelChatPreview(); + if (!pressShowsPreview() || key != computeChosenRow().key) { + return; + } + ClickHandler::unpressed(); + mousePressReleased(QCursor::pos(), Qt::NoButton, Qt::NoModifier); + + _chatPreviewKey = key; + _menu = HistoryView::MakeChatPreview(this, key.entry()); + if (!_menu) { + return; + } + QObject::connect(_menu.get(), &QObject::destroyed, [=] { + if (_chatPreviewKey) { + updateDialogRow(RowDescriptor(base::take(_chatPreviewKey), {})); + } + const auto globalPosition = QCursor::pos(); + if (rect().contains(mapFromGlobal(globalPosition))) { + setMouseTracking(true); + selectByMouse(globalPosition); + } + }); + _menu->popup(_lastMousePosition.value_or(QCursor::pos())); +} + void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { _menu = nullptr; @@ -2316,7 +2361,9 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } return RowDescriptor(); }(); - if (!row.key) return; + if (!row.key) { + return; + } _menuRow = row; if (_pressButton != Qt::LeftButton) { @@ -2555,6 +2602,12 @@ void InnerWidget::trackSearchResultsHistory(not_null history) { refresh(); clearMouseSelection(true); } + if (_chatPreviewWillBeFor.topic() == topic) { + _chatPreviewWillBeFor = {}; + } + if (_chatPreviewKey.topic() == topic) { + _chatPreviewKey = {}; + } }, _searchResultsLifetime); } } @@ -3499,6 +3552,23 @@ ChosenRow InnerWidget::computeChosenRow() const { return ChosenRow(); } +bool InnerWidget::isUserpicPress() const { + return (_lastRowLocalMouseX >= 0) + && (_lastRowLocalMouseX < _st->nameLeft) + && (width() > _narrowWidth); +} + +bool InnerWidget::pressShowsPreview() const { + if (!isUserpicPress()) { + return false; + } + const auto key = computeChosenRow().key; + if (const auto history = key.history()) { + return !history->peer->isForum(); + } + return key.topic() != nullptr;; +} + bool InnerWidget::chooseRow( Qt::KeyboardModifiers modifiers, MsgId pressedTopicRootId) { @@ -3511,9 +3581,7 @@ bool InnerWidget::chooseRow( ChosenRow row, Qt::KeyboardModifiers modifiers) { row.newWindow = (modifiers & Qt::ControlModifier); - row.userpicClick = (_lastRowLocalMouseX >= 0) - && (_lastRowLocalMouseX < _st->nameLeft) - && (width() > _narrowWidth); + row.userpicClick = isUserpicPress(); return row; }; auto chosen = modifyChosenRow(computeChosenRow(), modifiers); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 7699576c8..c8186e2ea 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -7,14 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/flags.h" +#include "base/object_ptr.h" +#include "base/timer.h" #include "dialogs/dialogs_key.h" #include "data/data_messages.h" #include "ui/dragging_scroll_manager.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" #include "ui/userpic_view.h" -#include "base/flags.h" -#include "base/object_ptr.h" namespace style { struct DialogRow; @@ -121,6 +122,10 @@ public: void refreshEmptyLabel(); void resizeEmptyLabel(); + [[nodiscard]] bool isUserpicPress() const; + [[nodiscard]] bool pressShowsPreview() const; + void cancelChatPreview(); + void showChatPreview(); bool chooseRow( Qt::KeyboardModifiers modifiers = {}, MsgId pressedTopicRootId = {}); @@ -514,6 +519,10 @@ private: rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; + base::Timer _chatPreviewTimer; + Key _chatPreviewWillBeFor; + Key _chatPreviewKey; + rpl::variable _childListShown; float64 _narrowRatio = 0.; bool _geometryInited = false; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp new file mode 100644 index 000000000..7779b8b28 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -0,0 +1,113 @@ +/* +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 "history/view/history_view_chat_preview.h" + +#include "data/data_peer.h" +#include "history/history.h" +#include "ui/chat/chat_theme.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +class Item final : public Ui::Menu::ItemBase { +public: + Item(not_null parent, not_null history); + + not_null action() const override; + bool isEnabled() const override; + +private: + void setupBackground(); + + int contentHeight() const override; + + void paintEvent(QPaintEvent *e) override; + + const not_null _dummyAction; + const std::shared_ptr _theme; + + QImage _bg; + +}; + +Item::Item(not_null parent, not_null history) +: Ui::Menu::ItemBase(parent, st::previewMenu.menu) +, _dummyAction(new QAction(parent)) +, _theme(Window::Theme::DefaultChatThemeOn(lifetime())) { + setPointerCursor(false); + setMinWidth(st::previewMenu.menu.widthMin); + resize(minWidth(), contentHeight()); + setupBackground(); +} + +not_null Item::action() const { + return _dummyAction; +} + +bool Item::isEnabled() const { + return false; +} + +int Item::contentHeight() const { + return st::previewMenu.maxHeight; +} + +void Item::setupBackground() { + const auto ratio = style::DevicePixelRatio(); + _bg = QImage( + size() * ratio, + QImage::Format_ARGB32_Premultiplied); + + const auto paint = [=] { + auto p = QPainter(&_bg); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(width(), height() * 2), + QRect(QPoint(), size())); + }; + paint(); + _theme->repaintBackgroundRequests() | rpl::start_with_next([=] { + paint(); + update(); + }, lifetime()); +} + +void Item::paintEvent(QPaintEvent *e) { + auto p = QPainter(this); + p.drawImage(0, 0, _bg); +} + +} // namespace + +base::unique_qptr MakeChatPreview( + QWidget *parent, + not_null entry) { + if (const auto topic = entry->asTopic()) { + return nullptr; + } + const auto history = entry->asHistory(); + if (!history || history->peer->isForum()) { + return nullptr; + } + + auto result = base::make_unique_q( + parent, + st::previewMenu); + + result->addAction(base::make_unique_q(result.get(), history)); + + return result; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.h b/Telegram/SourceFiles/history/view/history_view_chat_preview.h new file mode 100644 index 000000000..12a35bbba --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.h @@ -0,0 +1,26 @@ +/* +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/unique_qptr.h" + +namespace Dialogs { +class Entry; +} // namespace Dialogs + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace HistoryView { + +[[nodiscard]] base::unique_qptr MakeChatPreview( + QWidget *parent, + not_null entry); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 5dd77998a..c72097f25 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1076,3 +1076,12 @@ liveLocationLongInIconSelected: icon {{ "chat/live_location_long", msgInServiceF liveLocationLongOutIcon: icon {{ "chat/live_location_long", msgOutServiceFg }}; liveLocationLongOutIconSelected: icon {{ "chat/live_location_long", msgOutServiceFgSelected }}; liveLocationRemainingSize: 28px; + +previewMenu: PopupMenu(defaultPopupMenu) { + scrollPadding: margins(0px, 0px, 0px, 0px); + menu: Menu(defaultMenu) { + widthMin: 380px; + widthMax: 380px; + } + maxHeight: 420px; +}