diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 85e7076fb..d6c7c3029 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -19,6 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Api { namespace { +constexpr auto kSearchPerPage = 50; + [[nodiscard]] MessageIdsList HistoryItemsFromTL( not_null data, const QVector &messages) { @@ -94,7 +96,7 @@ void MessagesSearch::searchRequest() { MTP_int(0), // max_date MTP_int(_offsetId), // offset_id MTP_int(0), // add_offset - MTP_int(SearchPerPage), + MTP_int(kSearchPerPage), MTP_int(0), // max_id MTP_int(0), // min_id MTP_long(0) // hash diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 512ee948e..e034a118e 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -20,10 +20,6 @@ enum { RecentInlineBotsLimit = 10, AutoSearchTimeout = 900, // 0.9 secs - SearchPerPage = 50, - SearchManyPerPage = 100, - LinksOverviewPerPage = 12, - MediaOverviewStartPerPage = 5, PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index d3794d7ef..74cad5fde 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2011,12 +2011,16 @@ void InnerWidget::applyFilterUpdate(QString newFilter, bool force) { end(results)); }; if (!_searchInChat && !words.isEmpty()) { - append(session().data().chatsList()->indexed()); - const auto id = Data::Folder::kId; - if (const auto folder = session().data().folderLoaded(id)) { - append(folder->chatsList()->indexed()); + if (_openedForum) { + append(_openedForum->topicsList()->indexed()); + } else { + append(session().data().chatsList()->indexed()); + const auto id = Data::Folder::kId; + if (const auto add = session().data().folderLoaded(id)) { + append(add->chatsList()->indexed()); + } + append(session().data().contactsNoChatsList()); } - append(session().data().contactsNoChatsList()); } refresh(true); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index fdffb40e7..d4c359870 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "core/application.h" #include "core/update_checker.h" +#include "core/shortcuts.h" #include "boxes/peer_list_box.h" #include "boxes/peers/edit_participants_box.h" #include "window/window_adaptive.h" @@ -72,8 +73,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs { namespace { -QString SwitchToChooseFromQuery() { - return qsl("from:"); +constexpr auto kSearchPerPage = 50; + +[[nodiscard]] QString SwitchToChooseFromQuery() { + return u"from:"_q; } } // namespace @@ -296,12 +299,12 @@ Widget::Widget( Ui::PostponeCall(this, [=] { listScrollUpdated(); }); }, lifetime()); - QObject::connect(_filter, &Ui::InputField::cancelled, [=] { - escape(); - }); QObject::connect(_filter, &Ui::InputField::changed, [=] { applyFilterUpdate(); }); + QObject::connect(_filter, &Ui::InputField::submitted, [=] { + submit(); + }); QObject::connect( _filter->rawTextEdit().get(), &QTextEdit::cursorPositionChanged, @@ -339,6 +342,7 @@ Widget::Widget( }); setupMainMenuToggle(); + setupShortcuts(); _searchForNarrowFilters->setClickedCallback([=] { Ui::showChatsList(&session()); }); @@ -616,6 +620,29 @@ void Widget::setupMainMenuToggle() { }, _mainMenuToggle->lifetime()); } +void Widget::setupShortcuts() { + Shortcuts::Requests( + ) | rpl::filter([=] { + return isActiveWindow() + && Ui::InFocusChain(this) + && !Ui::isLayerShown() + && !controller()->window().locked(); + }) | rpl::start_with_next([=](not_null request) { + using Command = Shortcuts::Command; + + if (controller()->selectingPeer()) { + return; + } + if (_openedForum && !controller()->activeChatCurrent()) { + request->check(Command::Search) && request->handle([=] { + const auto history = _openedForum->forum()->history(); + controller()->content()->searchInChat(history); + return true; + }); + } + }, lifetime()); +} + void Widget::fullSearchRefreshOn(rpl::producer<> events) { std::move( events @@ -642,14 +669,14 @@ void Widget::updateControlsVisibility(bool fast) { _forwardCancel->show(); } if ((_openedFolder || _openedForum) && _filter->hasFocus()) { - setFocus(); + setInnerFocus(); } if (_updateTelegram) { _updateTelegram->show(); } _searchControls->setVisible(!_openedFolder && !_openedForum); if (_openedFolder || _openedForum) { - _folderTopBar->show(); + _subsectionTopBar->show(); if (_forumTopShadow) { _forumTopShadow->show(); } @@ -694,6 +721,7 @@ void Widget::changeOpenedSubsection( change(); refreshTopBars(); updateControlsVisibility(true); + _peerSearchRequest = 0; if (animated == anim::type::normal) { _connecting->setForceHidden(true); _cacheOver = grabForFolderSlideAnimation(); @@ -721,23 +749,42 @@ void Widget::changeOpenedForum(ChannelData *forum, anim::type animated) { void Widget::refreshTopBars() { if (_openedFolder || _openedForum) { - if (!_folderTopBar) { - _folderTopBar.create(this, controller()); + if (!_subsectionTopBar) { + _subsectionTopBar.create(this, controller()); + _subsectionTopBar->searchCancelled( + ) | rpl::start_with_next([=] { + escape(); + }, _subsectionTopBar->lifetime()); + _subsectionTopBar->searchSubmitted( + ) | rpl::start_with_next([=] { + submit(); + }, _subsectionTopBar->lifetime()); + _subsectionTopBar->searchQuery( + ) | rpl::start_with_next([=](QString query) { + applyFilterUpdate(); + }, lifetime()); updateControlsGeometry(); } const auto history = _openedForum ? session().data().history(_openedForum).get() : nullptr; - _folderTopBar->setActiveChat( + _subsectionTopBar->setActiveChat( HistoryView::TopBarWidget::ActiveChat{ .key = (_openedForum ? Dialogs::Key(history) : Dialogs::Key(_openedFolder)), .section = Dialogs::EntryState::Section::ChatsList, }, history ? history->sendActionPainter().get() : nullptr); - } else { - _folderTopBar.destroy(); + if (_forumSearchRequested) { + _subsectionTopBar->toggleSearch(true, anim::type::instant); + } + } else if (_subsectionTopBar) { + if (_subsectionTopBar->searchHasFocus()) { + setFocus(); + } + _subsectionTopBar.destroy(); } + _forumSearchRequested = false; if (_openedForum) { _openedForum->updateFull(); @@ -857,10 +904,10 @@ void Widget::checkUpdateStatus() { } void Widget::setInnerFocus() { - if (_openedFolder || _openedForum) { - setFocus(); - } else { + if (!_openedFolder && !_openedForum) { _filter->setFocus(); + } else if (!_subsectionTopBar->searchSetFocus()) { + setFocus(); } } @@ -868,7 +915,7 @@ void Widget::jumpToTop(bool belowPinned) { if (session().supportMode()) { return; } - if ((_filter->getLastText().trimmed().isEmpty() && !_searchInChat)) { + if ((currentSearchQuery().trimmed().isEmpty() && !_searchInChat)) { auto to = 0; if (belowPinned) { const auto list = _openedForum @@ -981,8 +1028,8 @@ void Widget::startSlideAnimation() { _forwardCancel->hide(); } _searchControls->hide(); - if (_folderTopBar) { - _folderTopBar->hide(); + if (_subsectionTopBar) { + _subsectionTopBar->hide(); } if (_forumTopShadow) { _forumTopShadow->hide(); @@ -1018,19 +1065,20 @@ void Widget::animationCallback() { updateControlsVisibility(true); - if (!_filter->hasFocus()) { + if ((!_subsectionTopBar || !_subsectionTopBar->searchHasFocus()) + && !_filter->hasFocus()) { controller()->widget()->setInnerFocus(); } } } void Widget::escape() { - if (controller()->openedFolder().current()) { - controller()->closeFolder(); - } else if (controller()->openedForum().current()) { - controller()->closeForum(); - } else if (!cancelSearch()) { - if (controller()->activeChatEntryCurrent().key) { + if (!cancelSearch()) { + if (controller()->openedFolder().current()) { + controller()->closeFolder(); + } else if (controller()->openedForum().current()) { + controller()->closeForum(); + } else if (controller()->activeChatEntryCurrent().key) { controller()->content()->dialogsCancelled(); } else { const auto filters = &session().data().chatsFilters(); @@ -1047,6 +1095,21 @@ void Widget::escape() { } } +void Widget::submit() { + if (_inner->chooseRow()) { + return; + } + const auto state = _inner->state(); + if (state == WidgetState::Default + || (state == WidgetState::Filtered + && (!_inner->waitingForSearch() || _inner->hasFilteredResults()))) { + _inner->selectSkip(1); + _inner->chooseRow(); + } else { + searchMessages(); + } +} + void Widget::refreshLoadMoreButton(bool mayBlock, bool isBlocked) { if (!mayBlock) { if (_loadMoreChats) { @@ -1084,7 +1147,7 @@ void Widget::loadMoreBlockedByDate() { bool Widget::searchMessages(bool searchCache) { auto result = false; - auto q = _filter->getLastText().trimmed(); + auto q = currentSearchQuery().trimmed(); if (q.isEmpty() && !_searchFromAuthor) { cancelSearchRequest(); _api.request(base::take(_peerSearchRequest)).cancel(); @@ -1105,9 +1168,9 @@ bool Widget::searchMessages(bool searchCache) { _searchFull = _searchFullMigrated = false; cancelSearchRequest(); searchReceived( - _searchInChat + ((_searchInChat || _openedForum) ? SearchRequestType::PeerFromStart - : SearchRequestType::FromStart, + : SearchRequestType::FromStart), i->second, 0); result = true; @@ -1118,29 +1181,29 @@ bool Widget::searchMessages(bool searchCache) { _searchNextRate = 0; _searchFull = _searchFullMigrated = false; cancelSearchRequest(); - if (const auto peer = _searchInChat.peer()) { + if (const auto peer = searchInPeer()) { + const auto topic = searchInTopic(); auto &histories = session().data().histories(); const auto type = Data::Histories::RequestType::History; const auto history = session().data().history(peer); _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { const auto type = SearchRequestType::PeerFromStart; - const auto flags = _searchQueryFrom - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); + using Flag = MTPmessages_Search::Flag; _searchRequest = session().api().request(MTPmessages_Search( - flags, + MTP_flags((topic ? Flag::f_top_msg_id : Flag()) + | (_searchQueryFrom ? Flag::f_from_id : Flag())), peer->input, MTP_string(_searchQuery), (_searchQueryFrom ? _searchQueryFrom->input : MTP_inputPeerEmpty()), - MTPint(), // top_msg_id + MTP_int(topic ? topic->rootId() : 0), MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date MTP_int(0), // max_date MTP_int(0), // offset_id MTP_int(0), // add_offset - MTP_int(SearchPerPage), + MTP_int(kSearchPerPage), MTP_int(0), // max_id MTP_int(0), // min_id MTP_long(0) // hash @@ -1172,7 +1235,7 @@ bool Widget::searchMessages(bool searchCache) { MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), - MTP_int(SearchPerPage) + MTP_int(kSearchPerPage) )).done([=](const MTPmessages_Messages &result) { searchReceived(type, result, _searchRequest); }).fail([=](const MTP::Error &error) { @@ -1219,7 +1282,7 @@ bool Widget::searchMessages(bool searchCache) { } bool Widget::searchForPeersRequired(const QString &query) const { - if (_searchInChat || query.isEmpty()) { + if (_searchInChat || _openedForum || query.isEmpty()) { return false; } return (query[0] != '#'); @@ -1238,11 +1301,17 @@ void Widget::showMainMenu() { void Widget::searchMessages( const QString &query, Key inChat) { - auto inChatChanged = [&] { - if (inChat == _searchInChat) { + const auto inChatChanged = [&] { + const auto inPeer = inChat.peer(); + const auto inTopic = inChat.topic(); + if (!inTopic && inPeer == _openedForum) { + return false; + } else if ((inTopic || (inPeer && !inPeer->isForum())) + && (inChat == _searchInChat)) { return false; } else if (const auto inPeer = inChat.peer()) { - if (inPeer->migrateTo() == _searchInChat.peer()) { + if (inPeer->migrateTo() == _searchInChat.peer() + && !_searchInChat.topic()) { return false; } } @@ -1269,19 +1338,19 @@ void Widget::searchMore() { if (!_searchFull) { auto offsetPeer = _inner->lastSearchPeer(); auto offsetId = _inner->lastSearchId(); - if (const auto peer = _searchInChat.peer()) { + if (const auto peer = searchInPeer()) { auto &histories = session().data().histories(); + const auto topic = searchInTopic(); const auto type = Data::Histories::RequestType::History; const auto history = session().data().history(peer); _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { const auto type = offsetId ? SearchRequestType::PeerFromOffset : SearchRequestType::PeerFromStart; - auto flags = _searchQueryFrom - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); + using Flag = MTPmessages_Search::Flag; _searchRequest = session().api().request(MTPmessages_Search( - flags, + MTP_flags((topic ? Flag::f_top_msg_id : Flag()) + | (_searchQueryFrom ? Flag::f_from_id : Flag())), peer->input, MTP_string(_searchQuery), (_searchQueryFrom @@ -1293,7 +1362,7 @@ void Widget::searchMore() { MTP_int(0), // max_date MTP_int(offsetId), MTP_int(0), // add_offset - MTP_int(SearchPerPage), + MTP_int(kSearchPerPage), MTP_int(0), // max_id MTP_int(0), // min_id MTP_long(0) // hash @@ -1331,7 +1400,7 @@ void Widget::searchMore() { ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), - MTP_int(SearchPerPage) + MTP_int(kSearchPerPage) )).done([=](const MTPmessages_Messages &result) { searchReceived(type, result, _searchRequest); }).fail([=](const MTP::Error &error) { @@ -1366,7 +1435,7 @@ void Widget::searchMore() { MTP_int(0), // max_date MTP_int(offsetMigratedId), MTP_int(0), // add_offset - MTP_int(SearchPerPage), + MTP_int(kSearchPerPage), MTP_int(0), // max_id MTP_int(0), // min_id MTP_long(0) // hash @@ -1451,7 +1520,7 @@ void Widget::searchReceived( case mtpc_messages_channelMessages: { auto &d = result.c_messages_channelMessages(); - if (const auto peer = _searchInChat.peer()) { + if (const auto peer = _openedForum ? _openedForum : _searchInChat.peer()) { if (const auto channel = peer->asChannel()) { channel->ptsReceived(d.vpts().v); } else { @@ -1642,7 +1711,7 @@ void Widget::applyFilterUpdate(bool force) { return; } - auto filterText = _filter->getLastText(); + const auto filterText = currentSearchQuery(); _inner->applyFilterUpdate(filterText, force); if (filterText.isEmpty() && !_searchFromAuthor) { clearSearchCache(); @@ -1677,6 +1746,16 @@ void Widget::searchInChat(Key chat) { } void Widget::setSearchInChat(Key chat, PeerData *from) { + const auto peer = chat.peer(); + if (const auto forum = peer ? peer->forum() : nullptr) { + if (controller()->openedForum().current() == peer) { + _subsectionTopBar->toggleSearch(true, anim::type::normal); + } else { + _forumSearchRequested = true; + controller()->openForum(forum->channel()); + } + return; + } if (chat.folder()) { chat = Key(); } @@ -1685,7 +1764,9 @@ void Widget::setSearchInChat(Key chat, PeerData *from) { if (const auto migrateTo = peer->migrateTo()) { return setSearchInChat(peer->owner().history(migrateTo), from); } else if (const auto migrateFrom = peer->migrateFrom()) { - _searchInMigrated = peer->owner().history(migrateFrom); + if (!chat.topic()) { + _searchInMigrated = peer->owner().history(migrateFrom); + } } } const auto searchInPeerUpdated = (_searchInChat != chat); @@ -1816,7 +1897,7 @@ void Widget::updateLoadMoreChatsVisibility() { } const auto hidden = (_openedFolder != nullptr) || (_openedForum != nullptr) - || !_filter->getLastText().isEmpty(); + || !currentSearchQuery().isEmpty(); if (_loadMoreChats->isHidden() != hidden) { _loadMoreChats->setVisible(!hidden); updateControlsGeometry(); @@ -1866,8 +1947,8 @@ void Widget::updateControlsGeometry() { auto filterWidth = qMax(width(), st::columnMinimalWidthLeft) - filterLeft - filterRight; auto filterAreaHeight = st::topBarHeight; _searchControls->setGeometry(0, filterAreaTop, width(), filterAreaHeight); - if (_folderTopBar) { - _folderTopBar->setGeometry(_searchControls->geometry()); + if (_subsectionTopBar) { + _subsectionTopBar->setGeometry(_searchControls->geometry()); } auto filterTop = (filterAreaHeight - _filter->height()) / 2; @@ -1983,25 +2064,16 @@ RowDescriptor Widget::resolveChatPrevious(RowDescriptor from) const { void Widget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { - if (_openedForum) { - controller()->closeForum(); - } else if (_openedFolder) { - controller()->closeFolder(); - } else { - e->ignore(); - } + escape(); + //if (_openedForum) { + // controller()->closeForum(); + //} else if (_openedFolder) { + // controller()->closeFolder(); + //} else { + // e->ignore(); + //} } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { - if (!_inner->chooseRow()) { - const auto state = _inner->state(); - if (state == WidgetState::Default - || (state == WidgetState::Filtered - && (!_inner->waitingForSearch() || _inner->hasFilteredResults()))) { - _inner->selectSkip(1); - _inner->chooseRow(); - } else { - searchMessages(); - } - } + submit(); } else if (e->key() == Qt::Key_Down) { _inner->selectSkip(1); } else if (e->key() == Qt::Key_Up) { @@ -2082,22 +2154,51 @@ void Widget::cancelSearchRequest() { base::take(_searchInHistoryRequest)); } +PeerData *Widget::searchInPeer() const { + return _openedForum ? _openedForum : _searchInChat.peer(); +} + +Data::ForumTopic *Widget::searchInTopic() const { + return _searchInChat.topic(); +} + +QString Widget::currentSearchQuery() const { + return _subsectionTopBar + ? _subsectionTopBar->searchQueryCurrent() + : _filter->getLastText(); +} + +void Widget::clearSearchField() { + if (_subsectionTopBar) { + _subsectionTopBar->searchClear(); + } else { + _filter->clear(); + } +} + bool Widget::cancelSearch() { - bool clearing = !_filter->getLastText().isEmpty(); + bool clearing = !currentSearchQuery().isEmpty(); cancelSearchRequest(); - if (_searchInChat && !clearing) { + if (!clearing && _searchInChat) { if (controller()->adaptive().isOneColumn()) { - if (const auto peer = _searchInChat.peer()) { - Ui::showPeerHistory(peer, ShowAtUnreadMsgId); + if (const auto topic = _searchInChat.topic()) { + //controller()->showTopic(topic); // #TODO forum search + } else if (const auto peer = _searchInChat.peer()) { + controller()->showPeer(peer, ShowAtUnreadMsgId); } else { Unexpected("Empty key in cancelSearch()."); } } setSearchInChat(Key()); clearing = true; + } else if (!clearing + && _subsectionTopBar + && _subsectionTopBar->toggleSearch(false, anim::type::normal)) { + setFocus(); + return true; } _inner->clearFilter(); - _filter->clear(); + clearSearchField(); applyFilterUpdate(); return clearing; } @@ -2108,8 +2209,10 @@ void Widget::cancelSearchInChat() { if (_searchInChat) { if (isOneColumn && !controller()->selectingPeer() - && _filter->getLastText().trimmed().isEmpty()) { - if (const auto peer = _searchInChat.peer()) { + && currentSearchQuery().trimmed().isEmpty()) { + if (const auto topic = _searchInChat.topic()) { + // #TODO forum search + } else if (const auto peer = _searchInChat.peer()) { Ui::showPeerHistory(peer, ShowAtUnreadMsgId); } else { Unexpected("Empty key in cancelSearchInPeer()."); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 2afb362e4..f6e975352 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -126,6 +126,8 @@ private: void filterCursorMoved(); void completeHashtag(QString tag); + [[nodiscard]] QString currentSearchQuery() const; + void clearSearchField(); bool searchMessages(bool searchCache = false); void needSearchMessages(); @@ -138,12 +140,16 @@ private: const MTPcontacts_Found &result, mtpRequestId requestId); void escape(); + void submit(); void cancelSearchRequest(); + [[nodiscard]] PeerData *searchInPeer() const; + [[nodiscard]] Data::ForumTopic *searchInTopic() const; void setupSupportMode(); void setupConnectingWidget(); void setupMainMenuToggle(); void setupDownloadBar(); + void setupShortcuts(); bool searchForPeersRequired(const QString &query) const; void setSearchInChat(Key chat, PeerData *from = nullptr); void showCalendar(); @@ -192,7 +198,7 @@ private: object_ptr _forwardCancel = { nullptr }; object_ptr _searchControls; - object_ptr _folderTopBar = { nullptr } ; + object_ptr _subsectionTopBar = { nullptr } ; object_ptr _mainMenuToggle; object_ptr _searchForNarrowFilters; object_ptr _filter; @@ -221,8 +227,9 @@ private: ShowAnimation _showAnimationType = ShowAnimation::External; Ui::Animations::Simple _scrollToTopShown; - bool _scrollToTopIsShown = false; object_ptr _scrollToTop; + bool _scrollToTopIsShown = false; + bool _forumSearchRequested = false; Data::Folder *_openedFolder = nullptr; ChannelData *_openedForum = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 18b93a112..f540ace3d 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/effects/radial_animation.h" @@ -426,12 +427,16 @@ void TopBarWidget::paintEvent(QPaintEvent *e) { } Painter p(this); - auto selectedButtonsTop = countSelectedButtonsTop( + const auto selectedButtonsTop = countSelectedButtonsTop( _selectedShown.value(showSelectedActions() ? 1. : 0.)); + const auto searchFieldTop = _searchField + ? countSelectedButtonsTop(_searchShown.value(_searchMode ? 1. : 0.)) + : -st::topBarHeight; + const auto slidingTop = std::max(selectedButtonsTop, searchFieldTop); p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBg); - if (selectedButtonsTop < 0) { - p.translate(0, selectedButtonsTop + st::topBarHeight); + if (slidingTop < 0) { + p.translate(0, slidingTop + st::topBarHeight); paintTopBar(p); } } @@ -883,9 +888,19 @@ void TopBarWidget::updateControlsGeometry() { if (!_activeChat.key) { return; } - auto hasSelected = showSelectedActions(); - auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.value(hasSelected ? 1. : 0.)); - auto otherButtonsTop = selectedButtonsTop + st::topBarHeight; + const auto hasSelected = showSelectedActions(); + auto selectedButtonsTop = countSelectedButtonsTop( + _selectedShown.value(hasSelected ? 1. : 0.)); + if (!_searchMode && !_searchShown.animating() && _searchField) { + _searchField.destroy(); + _searchCancel.destroy(); + } + auto searchFieldTop = _searchField + ? countSelectedButtonsTop(_searchShown.value(_searchMode ? 1. : 0.)) + : -st::topBarHeight; + const auto otherButtonsTop = std::max(selectedButtonsTop, searchFieldTop) + + st::topBarHeight; + const auto backButtonTop = selectedButtonsTop + st::topBarHeight; auto buttonsLeft = st::topBarActionSkip + (_controller->adaptive().isOneColumn() ? 0 : st::lineWidth); auto buttonsWidth = (_forward->isHidden() ? 0 : _forward->contentWidth()) @@ -923,7 +938,7 @@ void TopBarWidget::updateControlsGeometry() { _leftTaken = st::topBarArrowPadding.right(); } else { _leftTaken = _narrowMode ? (width() - _back->width()) / 2 : 0; - _back->moveToLeft(_leftTaken, otherButtonsTop); + _back->moveToLeft(_leftTaken, backButtonTop); _leftTaken += _back->width(); } if (_info && !_info->isHidden()) { @@ -934,6 +949,26 @@ void TopBarWidget::updateControlsGeometry() { _leftTaken += st::normalFont->spacew; } + if (_searchField) { + const auto fieldLeft = _leftTaken; + const auto fieldTop = searchFieldTop + + (height() - _searchField->height()) / 2; + const auto fieldRight = st::dialogsFilterSkip + + st::dialogsFilterPadding.x(); + const auto fieldWidth = width() - fieldLeft - fieldRight; + _searchField->setGeometryToLeft( + fieldLeft, + fieldTop, + fieldWidth, + _searchField->height()); + + auto right = fieldLeft + fieldWidth; + _searchCancel->moveToLeft( + right - _searchCancel->width(), + _searchField->y()); + right -= _searchCancel->width(); + } + _rightTaken = 0; _menuToggle->moveToRight(_rightTaken, otherButtonsTop); if (_menuToggle->isHidden()) { @@ -1146,9 +1181,86 @@ void TopBarWidget::showSelected(SelectedState state) { } } +bool TopBarWidget::toggleSearch(bool shown, anim::type animated) { + if (_searchMode == shown) { + if (animated == anim::type::instant) { + _searchShown.stop(); + } + return false; + } + _searchMode = shown; + if (shown && !_searchField) { + _searchField.create(this, st::dialogsFilter, tr::lng_dlg_filter()); + _searchField->setFocusPolicy(Qt::StrongFocus); + _searchField->customUpDown(true); + _searchField->show(); + _searchCancel.create(this, st::dialogsCancelSearch); + _searchCancel->show(anim::type::instant); + _searchCancel->setClickedCallback([=] { _searchCancelled.fire({}); }); + QObject::connect(_searchField, &Ui::InputField::submitted, [=] { + _searchSubmitted.fire({}); + }); + QObject::connect(_searchField, &Ui::InputField::changed, [=] { + _searchQuery = _searchField->getLastText(); + }); + } else { + Assert(_searchField != nullptr); + } + _searchQuery = shown ? _searchField->getLastText() : QString(); + if (animated == anim::type::normal) { + _searchShown.start( + [=] { slideAnimationCallback(); }, + shown ? 0. : 1., + shown ? 1. : 0., + st::slideWrapDuration, + anim::easeOutCirc); + } else { + _searchShown.stop(); + slideAnimationCallback(); + } + if (shown) { + _searchField->setFocusFast(); + } + return true; +} + +bool TopBarWidget::searchSetFocus() { + if (!_searchMode) { + return false; + } + _searchField->setFocus(); + return true; +} + +bool TopBarWidget::searchHasFocus() const { + return _searchMode && _searchField->hasFocus(); +} + +rpl::producer<> TopBarWidget::searchCancelled() const { + return _searchCancelled.events(); +} + +rpl::producer<> TopBarWidget::searchSubmitted() const { + return _searchSubmitted.events(); +} + +rpl::producer TopBarWidget::searchQuery() const { + return _searchQuery.value(); +} + +QString TopBarWidget::searchQueryCurrent() const { + return _searchQuery.current(); +} + +void TopBarWidget::searchClear() { + if (_searchMode) { + _searchField->clear(); + } +} + void TopBarWidget::toggleSelectedControls(bool shown) { _selectedShown.start( - [this] { selectedShowCallback(); }, + [this] { slideAnimationCallback(); }, shown ? 0. : 1., shown ? 1. : 0., st::slideWrapDuration, @@ -1159,7 +1271,7 @@ bool TopBarWidget::showSelectedActions() const { return showSelectedState() && !_chooseForReportReason; } -void TopBarWidget::selectedShowCallback() { +void TopBarWidget::slideAnimationCallback() { updateControlsGeometry(); update(); } diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index ee7bfe741..29333510b 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -24,8 +24,12 @@ class RoundButton; class IconButton; class PopupMenu; class UnreadBadge; +class InputField; +class CrossButton; class InfiniteRadialAnimation; enum class ReportReason; +template +class FadeWrapScaled; } // namespace Ui namespace Window { @@ -72,6 +76,15 @@ public: void showChooseMessagesForReport(Ui::ReportReason reason); void clearChooseMessagesForReport(); + bool toggleSearch(bool shown, anim::type animated); + bool searchSetFocus(); + [[nodiscard]] bool searchHasFocus() const; + [[nodiscard]] rpl::producer<> searchCancelled() const; + [[nodiscard]] rpl::producer<> searchSubmitted() const; + [[nodiscard]] rpl::producer searchQuery() const; + [[nodiscard]] QString searchQueryCurrent() const; + void searchClear(); + [[nodiscard]] rpl::producer<> forwardSelectionRequest() const { return _forwardSelection.events(); } @@ -104,7 +117,7 @@ private: void refreshLang(); void updateSearchVisibility(); void updateControlsGeometry(); - void selectedShowCallback(); + void slideAnimationCallback(); void updateInfoToggleActive(); void call(); @@ -169,11 +182,22 @@ private: bool _canDelete = false; bool _canForward = false; bool _canSendNow = false; + bool _searchMode = false; Ui::Animations::Simple _selectedShown; + Ui::Animations::Simple _searchShown; object_ptr _clear; object_ptr _forward, _sendNow, _delete; + object_ptr _searchField = { nullptr }; + object_ptr> _chooseFromUser + = { nullptr }; + object_ptr> _jumpToDate + = { nullptr }; + object_ptr _searchCancel = { nullptr }; + rpl::variable _searchQuery; + rpl::event_stream<> _searchCancelled; + rpl::event_stream<> _searchSubmitted; object_ptr _back; object_ptr _cancelChoose; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 547f8680e..06be3da9a 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -83,7 +83,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Window { namespace { -constexpr auto kTopicsSearchMinCount = 10; +constexpr auto kTopicsSearchMinCount = 1; } // namespace @@ -1005,8 +1005,14 @@ void Filler::addViewAsMessages() { } void Filler::addSearchTopics() { + const auto forum = _peer ? _peer->forum() : nullptr; + if (!forum) { + return; + } + const auto history = forum->history(); + const auto controller = _controller; _addAction(tr::lng_dlg_filter(tr::now), [=] { - + controller->content()->searchInChat(history); }, &st::menuIconSearch); }