diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c0ef61a95..47ad8c12d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5174,6 +5174,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_font_system" = "System font"; "lng_font_not_found" = "Font not found."; +"lng_search_tab_my_messages" = "My Messages"; +"lng_search_tab_this_topic" = "This Topic"; +"lng_search_tab_this_chat" = "This Chat"; +"lng_search_tab_this_channel" = "This Channel"; +"lng_search_tab_this_group" = "This Group"; +"lng_search_tab_public_posts" = "Public Posts"; +"lng_search_tab_no_results" = "No Results"; +"lng_search_tab_no_results_text" = "There were no results for \"{query}\"."; +"lng_search_tab_no_results_retry" = "Try another hashtag."; +"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it."; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 2a4882588..e8a8b9d59 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -42,10 +42,7 @@ namespace { constexpr auto kDefaultIconId = DocumentId(0x7FFF'FFFF'FFFF'FFFFULL); -struct DefaultIcon { - QString title; - int32 colorId = 0; -}; +using DefaultIcon = Data::TopicIconDescriptor; class DefaultIconEmoji final : public Ui::Text::CustomEmoji { public: @@ -89,10 +86,14 @@ QString DefaultIconEmoji::entityData() { void DefaultIconEmoji::paint(QPainter &p, const Context &context) { if (_image.isNull()) { - _image = Data::ForumTopicIconFrame( - _icon.colorId, - _icon.title, - st::defaultForumTopicIcon); + _image = Data::IsForumGeneralIconTitle(_icon.title) + ? Data::ForumTopicGeneralIconFrame( + st::defaultForumTopicIcon.size, + Data::ParseForumGeneralIconColor(_icon.colorId)) + : Data::ForumTopicIconFrame( + _icon.colorId, + _icon.title, + st::defaultForumTopicIcon); } const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio(); const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); @@ -212,7 +213,7 @@ bool DefaultIconEmoji::readyInDefaultState() { ) | rpl::start_with_next([=] { state->frame = Data::ForumTopicGeneralIconFrame( st::largeForumTopicIcon.size, - st::windowSubTextFg); + st::windowSubTextFg->c); result->update(); }, result->lifetime()); @@ -261,7 +262,7 @@ struct IconSelector { if (id == kDefaultIconId) { return std::make_unique( rpl::duplicate(defaultIcon), - repaint); + std::move(repaint)); } return manager->create(id, std::move(repaint), tag); }; @@ -572,3 +573,11 @@ void EditForumTopicBox( box->closeBox(); }); } + +std::unique_ptr MakeTopicIconEmoji( + Data::TopicIconDescriptor descriptor, + Fn repaint) { + return std::make_unique( + rpl::single(descriptor), + std::move(repaint)); +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h index fddc3c052..620ecebaf 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace Data { +struct TopicIconDescriptor; +} // namespace Data + namespace Window { class SessionController; } // namespace Window @@ -25,3 +29,7 @@ void EditForumTopicBox( not_null controller, not_null forum, MsgId rootId); + +[[nodiscard]] std::unique_ptr MakeTopicIconEmoji( + Data::TopicIconDescriptor descriptor, + Fn repaint); diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 7b5391a19..7285c4c39 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -143,7 +143,7 @@ QImage ForumTopicIconFrame( return background; } -QImage ForumTopicGeneralIconFrame(int size, const style::color &color) { +QImage ForumTopicGeneralIconFrame(int size, const QColor &color) { const auto ratio = style::DevicePixelRatio(); auto svg = QSvgRenderer(ForumTopicIconPath(u"general"_q)); auto result = QImage( @@ -172,6 +172,62 @@ TextWithEntities ForumTopicIconWithTitle( : TextWithEntities{ title }; } +QString ForumGeneralIconTitle() { + return QChar(0) + u"general"_q; +} + +bool IsForumGeneralIconTitle(const QString &title) { + return !title.isEmpty() && !title[0].unicode(); +} + +int32 ForumGeneralIconColor(const QColor &color) { + return int32(uint32(color.red()) << 16 + | uint32(color.green()) << 8 + | uint32(color.blue()) + | (uint32(color.alpha() == 255 ? 0 : color.alpha()) << 24)); +} + +QColor ParseForumGeneralIconColor(int32 value) { + const auto alpha = uint32(value) >> 24; + return QColor( + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF, + alpha ? alpha : 255); +} + +QString TopicIconEmojiEntity(TopicIconDescriptor descriptor) { + return IsForumGeneralIconTitle(descriptor.title) + ? u"topic_general:"_q + QString::number(uint32(descriptor.colorId)) + : (u"topic_icon:"_q + + QString::number(uint32(descriptor.colorId)) + + ' ' + + ExtractNonEmojiLetter(descriptor.title)); +} + +TopicIconDescriptor ParseTopicIconEmojiEntity(QStringView entity) { + if (!entity.startsWith(u"topic_")) { + return {}; + } + const auto general = u"topic_general:"_q; + const auto normal = u"topic_icon:"_q; + if (entity.startsWith(general)) { + return { + .title = ForumGeneralIconTitle(), + .colorId = int32(entity.mid(general.size()).toUInt()), + }; + } else if (entity.startsWith(normal)) { + const auto parts = entity.mid(normal.size()).split(' '); + if (parts.size() == 2) { + return { + .title = parts[1].toString(), + .colorId = int32(parts[0].toUInt()), + }; + } + } + return {}; +} + ForumTopic::ForumTopic(not_null forum, MsgId rootId) : Thread(&forum->history()->owner(), Type::ForumTopic) , _forum(forum) @@ -640,7 +696,7 @@ void ForumTopic::validateGeneralIcon( : context.selected ? st::dialogsTextFgOver : st::dialogsTextFg; - _defaultIcon = ForumTopicGeneralIconFrame(size, color); + _defaultIcon = ForumTopicGeneralIconFrame(size, color->c); _flags = (_flags & ~mask) | flags; } diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 275f38fcb..06423e475 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -48,12 +48,33 @@ class Forum; const style::ForumTopicIcon &st); [[nodiscard]] QImage ForumTopicGeneralIconFrame( int size, - const style::color &color); + const QColor &color); [[nodiscard]] TextWithEntities ForumTopicIconWithTitle( MsgId rootId, DocumentId iconId, const QString &title); +[[nodiscard]] QString ForumGeneralIconTitle(); +[[nodiscard]] bool IsForumGeneralIconTitle(const QString &title); +[[nodiscard]] int32 ForumGeneralIconColor(const QColor &color); +[[nodiscard]] QColor ParseForumGeneralIconColor(int32 value); + +struct TopicIconDescriptor { + QString title; + int32 colorId = 0; + + [[nodiscard]] bool empty() const { + return !colorId && title.isEmpty(); + } + explicit operator bool() const { + return !empty(); + } +}; + +[[nodiscard]] QString TopicIconEmojiEntity(TopicIconDescriptor descriptor); +[[nodiscard]] TopicIconDescriptor ParseTopicIconEmojiEntity( + QStringView entity); + class ForumTopic final : public Thread { public: static constexpr auto kGeneralId = 1; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 22b877fb9..7f110b6b1 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/stickers/data_custom_emoji.h" +#include "boxes/peers/edit_forum_topic_box.h" // MakeTopicIconEmoji. #include "chat_helpers/stickers_emoji_pack.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_file_origin.h" +#include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity. #include "data/data_peer.h" #include "data/data_message_reactions.h" #include "data/stickers/data_stickers.h" @@ -539,6 +541,8 @@ std::unique_ptr CustomEmojiManager::create( const auto ratio = style::DevicePixelRatio(); const auto size = EmojiSizeFromTag(tag) / ratio; return userpic(data, std::move(update), size); + } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) { + return MakeTopicIconEmoji(parsed, std::move(update)); } const auto parsed = ParseCustomEmojiData(data); return parsed diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a08c16119..15624e29d 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt/qt_key_modifiers.h" #include "base/options.h" +#include "dialogs/ui/chat_search_tabs.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_suggestions.h" @@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_requests_bar.h" #include "history/view/history_view_group_call_bar.h" #include "boxes/peers/edit_peer_requests_box.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/elastic_scroll.h" #include "ui/widgets/fields/input_field.h" @@ -62,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_channel.h" #include "data/data_chat.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_user.h" #include "data/data_folder.h" #include "data/data_forum.h" @@ -1075,6 +1078,9 @@ void Widget::updateControlsVisibility(bool fast) { updateJumpToDateVisibility(fast); updateSearchFromVisibility(fast); } + if (_searchTabs) { + _searchTabs->show(); + } if (_connecting) { _connecting->setForceHidden(false); } @@ -1218,6 +1224,114 @@ void Widget::updateSuggestions(anim::type animated) { } } +void Widget::updateSearchTabs() { + const auto has = _searchInChat + || _hiddenSearchInChat + || _searchingHashtag; + if (!has) { + _searchTab = ChatSearchTab::MyMessages; + if (_searchTabs) { + _searchTabs = nullptr; + updateControlsGeometry(); + } + return; + } else if (!_searchTabs) { + _searchTabs = std::make_unique(this, _searchTab); + _searchTabs->setVisible(!_showAnimation); + _searchTabs->tabChanges( + ) | rpl::start_with_next([=](ChatSearchTab tab) { + if (_searchTab != tab) { + _searchTab = tab; + applySearchTab(); + } + }, _searchTabs->lifetime()); + } + const auto topic = _searchInChat.topic() + ? _searchInChat.topic() + : _hiddenSearchInChat.topic(); + const auto peer = _searchInChat.owningHistory() + ? _searchInChat.owningHistory()->peer.get() + : _hiddenSearchInChat.owningHistory() + ? _hiddenSearchInChat.owningHistory()->peer.get() + : nullptr; + const auto topicShortLabel = topic + ? Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({ + .title = (topic->isGeneral() + ? Data::ForumGeneralIconTitle() + : topic->title()), + .colorId = (topic->isGeneral() + ? Data::ForumGeneralIconColor(st::windowSubTextFg->c) + : topic->colorId()), + })) + : TextWithEntities(); + const auto peerShortLabel = peer + ? Ui::Text::SingleCustomEmoji( + session().data().customEmojiManager().peerUserpicEmojiData( + peer)) + : TextWithEntities(); + const auto myShortLabel = DefaultShortLabel(ChatSearchTab::MyMessages); + const auto publicShortLabel = _searchingHashtag + ? DefaultShortLabel(ChatSearchTab::PublicPosts) + : TextWithEntities(); + _searchTab = _searchInChat.topic() + ? ChatSearchTab::ThisTopic + : _searchInChat.owningHistory() + ? ChatSearchTab::ThisPeer + : (_searchingHashtag && _searchTab == ChatSearchTab::PublicPosts) + ? ChatSearchTab::PublicPosts + : ChatSearchTab::MyMessages; + _searchTabs->setTabShortLabels({ + { ChatSearchTab::ThisTopic, topicShortLabel }, + { ChatSearchTab::ThisPeer, peerShortLabel }, + { ChatSearchTab::MyMessages, myShortLabel }, + { ChatSearchTab::PublicPosts, publicShortLabel }, + }, _searchTab); + + updateControlsGeometry(); +} + +void Widget::applySearchTab() { + switch (_searchTab) { + case ChatSearchTab::ThisTopic: + if (_searchInChat.topic()) { + } else if (_hiddenSearchInChat.topic()) { + setSearchInChat( + base::take(_hiddenSearchInChat), + base::take(_hiddenSearchFromAuthor), + base::take(_hiddenSearchTags)); + } else { + updateSearchTabs(); + } + case ChatSearchTab::ThisPeer: + if (_searchInChat.topic()) { + _hiddenSearchInChat = _searchInChat; + _hiddenSearchFromAuthor = _searchFromAuthor; + _hiddenSearchTags = _searchTags; + setSearchInChat( + _searchInChat.owningHistory(), + _searchFromAuthor, + _searchTags); + } else if (_searchInChat) { + return; + } else if (_hiddenSearchInChat) { + setSearchInChat( + _hiddenSearchInChat.owningHistory(), + _hiddenSearchFromAuthor, + _hiddenSearchTags); + } + case ChatSearchTab::MyMessages: + if (_searchInChat) { + _hiddenSearchInChat = (_hiddenSearchInChat.topic() + && _hiddenSearchInChat.owningHistory() == _searchInChat.history()) + ? _hiddenSearchInChat + : _searchInChat; + _hiddenSearchFromAuthor = _searchFromAuthor; + _hiddenSearchTags = _searchTags; + setSearchInChat({}); + } + } +} + void Widget::changeOpenedSubsection( FnMut change, bool fromRight, @@ -2619,8 +2733,31 @@ void Widget::updateCancelSearch() { _cancelSearch->toggle(shown, anim::type::normal); } +bool Widget::fixSearchQuery() { + if (_fixingSearchQuery) { + return false; + } + _fixingSearchQuery = true; + const auto query = _search->getLastText(); + if (_searchTab == ChatSearchTab::PublicPosts) { + const auto fixed = FixHashtagSearchQuery( + query, + _search->textCursor().position()); + if (fixed.text != query) { + _search->setText(fixed.text); + _search->setCursorPosition(fixed.cursorPosition); + } + _searchingHashtag = true; + } else if (_searchingHashtag != IsHashtagSearchQuery(query)) { + _searchingHashtag = !_searchingHashtag; + updateSearchTabs(); + } + _fixingSearchQuery = false; + return true; +} + void Widget::applySearchUpdate(bool force) { - if (_showAnimation && !force) { + if (!fixSearchQuery() || (_showAnimation && !force)) { return; } @@ -2852,8 +2989,17 @@ bool Widget::setSearchInChat( } if (searchInPeerUpdated) { _searchInChat = chat; + if (chat) { + _hiddenSearchFromAuthor = {}; + _hiddenSearchTags = {}; + const auto hiddenTopic = _hiddenSearchInChat.topic(); + if (!hiddenTopic || hiddenTopic->history() != chat.history()) { + _hiddenSearchInChat = {}; + } + } controller()->setSearchInChat(_searchInChat); updateSuggestions(anim::type::instant); + updateSearchTabs(); updateJumpToDateVisibility(); updateStoriesVisibility(); } @@ -3190,6 +3336,9 @@ void Widget::updateControlsGeometry() { if (_forumRequestsBar) { _forumRequestsBar->resizeToWidth(barw); } + if (_searchTabs) { + _searchTabs->resizeToWidth(barw); + } _updateScrollGeometryCached = [=] { const auto moreChatsBarTop = expandedStoriesTop + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded); @@ -3211,8 +3360,13 @@ void Widget::updateControlsGeometry() { if (_forumReportBar) { _forumReportBar->bar().move(0, forumReportTop); } - const auto scrollTop = forumReportTop + const auto searchTabsTop = forumReportTop + (_forumReportBar ? _forumReportBar->bar().height() : 0); + if (_searchTabs) { + _searchTabs->move(0, searchTabsTop); + } + const auto scrollTop = searchTabsTop + + (_searchTabs ? _searchTabs->height() : 0); const auto scrollHeight = height() - scrollTop - bottomSkip; const auto wasScrollHeight = _scroll->height(); _scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index b183cb36f..a1bc0e4ea 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -76,6 +76,8 @@ struct ChosenRow; class InnerWidget; enum class SearchRequestType; class Suggestions; +class ChatSearchTabs; +enum class ChatSearchTab : uchar; class Widget final : public Window::AbstractSectionWidget { public: @@ -225,6 +227,7 @@ private: QPixmap newContentCache, Window::SlideDirection direction); + void applySearchTab(); void openChildList( not_null forum, const Window::SectionShow ¶ms); @@ -232,6 +235,7 @@ private: void fullSearchRefreshOn(rpl::producer<> events); void updateCancelSearch(); + bool fixSearchQuery(); void applySearchUpdate(bool force = false); void refreshLoadMoreButton(bool mayBlock, bool isBlocked); void loadMoreBlockedByDate(); @@ -251,6 +255,7 @@ private: void updateScrollUpPosition(); void updateLockUnlockPosition(); void updateSuggestions(anim::type animated); + void updateSearchTabs(); void processSearchFocusChange(); [[nodiscard]] bool redirectToSearchPossible() const; @@ -289,6 +294,7 @@ private: QPointer _inner; std::unique_ptr _suggestions; std::vector> _hidingSuggestions; + std::unique_ptr _searchTabs; class BottomButton; object_ptr _updateTelegram = { nullptr }; object_ptr _loadMoreChats = { nullptr }; @@ -304,6 +310,9 @@ private: object_ptr _scrollToTop; bool _scrollToTopIsShown = false; bool _forumSearchRequested = false; + bool _fixingSearchQuery = false; + bool _searchingHashtag = false; + ChatSearchTab _searchTab = ChatSearchTab(); Data::Folder *_openedFolder = nullptr; Data::Forum *_openedForum = nullptr; @@ -316,6 +325,10 @@ private: bool _searchSuggestionsLocked = false; bool _searchHasFocus = false; + Key _hiddenSearchInChat; + PeerData *_hiddenSearchFromAuthor = nullptr; + std::vector _hiddenSearchTags; + rpl::event_stream> _storiesContents; base::flat_map _storiesUserpicsViewsHidden; base::flat_map _storiesUserpicsViewsShown; diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp new file mode 100644 index 000000000..456183d30 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.cpp @@ -0,0 +1,178 @@ +/* +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 "dialogs/ui/chat_search_tabs.h" + +#include "lang/lang_keys.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/shadow.h" +#include "styles/style_dialogs.h" + +namespace Dialogs { +namespace { + +[[nodiscard]] QString TabLabel( + ChatSearchTab tab, + ChatSearchPeerTabType type = {}) { + switch (tab) { + case ChatSearchTab::MyMessages: + return tr::lng_search_tab_my_messages(tr::now); + case ChatSearchTab::ThisTopic: + return tr::lng_search_tab_this_topic(tr::now); + case ChatSearchTab::ThisPeer: + switch (type) { + case ChatSearchPeerTabType::Chat: + return tr::lng_search_tab_this_chat(tr::now); + case ChatSearchPeerTabType::Channel: + return tr::lng_search_tab_this_channel(tr::now); + case ChatSearchPeerTabType::Group: + return tr::lng_search_tab_this_group(tr::now); + } + Unexpected("Type in Dialogs::TabLabel."); + case ChatSearchTab::PublicPosts: + return tr::lng_search_tab_public_posts(tr::now); + } + Unexpected("Tab in Dialogs::TabLabel."); +} + +} // namespace + +TextWithEntities DefaultShortLabel(ChatSearchTab tab) { + // Return them in QString::fromUtf8 format. + switch (tab) { + case ChatSearchTab::MyMessages: + return { QString::fromUtf8("\xf0\x9f\x93\xa8") }; + case ChatSearchTab::PublicPosts: + return { QString::fromUtf8("\xf0\x9f\x8c\x8e") }; + } + Unexpected("Tab in Dialogs::DefaultShortLabel."); +} + +FixedHashtagSearchQuery FixHashtagSearchQuery( + const QString &query, + int cursorPosition) { + const auto trimmed = query.trimmed(); + const auto hash = trimmed.isEmpty() + ? query.size() + : query.indexOf(trimmed); + const auto start = std::min(cursorPosition, hash); + auto result = query.mid(0, start); + for (const auto &ch : query.mid(start)) { + if (ch.isSpace()) { + if (cursorPosition > result.size()) { + --cursorPosition; + } + continue; + } else if (result.size() == start) { + result += '#'; + if (ch != '#') { + ++cursorPosition; + } + } + if (ch != '#') { + result += ch; + } + } + return { result, cursorPosition }; +} + +bool IsHashtagSearchQuery(const QString &query) { + const auto trimmed = query.trimmed(); + if (trimmed.isEmpty() || trimmed[0] != '#') { + return false; + } + for (const auto &ch : trimmed) { + if (ch.isSpace()) { + return false; + } + } + return true; +} + +ChatSearchTabs::ChatSearchTabs(QWidget *parent, ChatSearchTab active) +: RpWidget(parent) +, _tabs(std::make_unique(this, st::dialogsSearchTabs)) +, _shadow(std::make_unique(this)) +, _active(active) { + for (const auto tab : { + ChatSearchTab::ThisTopic, + ChatSearchTab::ThisPeer, + ChatSearchTab::MyMessages, + ChatSearchTab::PublicPosts, + }) { + _list.push_back({ tab, TabLabel(tab) }); + } + _tabs->move(0, 0); + _tabs->sectionActivated( + ) | rpl::start_with_next([=](int index) { + for (const auto &tab : _list) { + if (!index) { + _active = tab.value; + return; + } + if (!tab.shortLabel.empty()) { + --index; + } + } + }, lifetime()); +} + +ChatSearchTabs::~ChatSearchTabs() = default; + +void ChatSearchTabs::setPeerTabType(ChatSearchPeerTabType type) { + _type = type; + const auto i = ranges::find(_list, ChatSearchTab::ThisPeer, &Tab::value); + Assert(i != end(_list)); + i->label = TabLabel(ChatSearchTab::ThisPeer, type); + if (!i->shortLabel.empty()) { + refreshTabs(_active.current()); + } +} + +void ChatSearchTabs::setTabShortLabels( + std::vector labels, + ChatSearchTab active) { + for (const auto &label : labels) { + const auto i = ranges::find(_list, label.tab, &Tab::value); + Assert(i != end(_list)); + i->shortLabel = std::move(label.label); + } + refreshTabs(active); +} + +rpl::producer ChatSearchTabs::tabChanges() const { + return _active.changes(); +} + +void ChatSearchTabs::refreshTabs(ChatSearchTab active) { + auto index = 0; + auto labels = std::vector(); + for (const auto &tab : _list) { + if (tab.value == active) { + index = int(labels.size()); + Assert(!tab.shortLabel.empty()); + labels.push_back(tab.label); + } else if (!tab.shortLabel.empty()) { + labels.push_back(tab.label); + } + } + _tabs->setSections(labels); + _tabs->setActiveSectionFast(index); + resizeToWidth(width()); +} + +int ChatSearchTabs::resizeGetHeight(int newWidth) { + _tabs->resizeToWidth(newWidth); + _shadow->setGeometry( + _tabs->x(), + _tabs->y() + _tabs->height() - st::lineWidth, + _tabs->width(), + st::lineWidth); + return _tabs->height(); +} + +} // namespace Dialogs \ No newline at end of file diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h new file mode 100644 index 000000000..0e2640f83 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/ui/chat_search_tabs.h @@ -0,0 +1,83 @@ +/* +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 "ui/rp_widget.h" + +namespace Ui { +class SettingsSlider; +class PlainShadow; +} // namespace Ui + +namespace Dialogs { + +enum class ChatSearchTab : uchar { + MyMessages, + ThisTopic, + ThisPeer, + PublicPosts, +}; + +enum class ChatSearchPeerTabType : uchar { + Chat, + Channel, + Group, +}; + +// Available for MyMessages and PublicPosts. +[[nodiscard]] TextWithEntities DefaultShortLabel(ChatSearchTab tab); + +class ChatSearchTabs final : public Ui::RpWidget { +public: + ChatSearchTabs(QWidget *parent, ChatSearchTab active); + ~ChatSearchTabs(); + + void setPeerTabType(ChatSearchPeerTabType type); + + // A [custom] emoji to use when there is not enough space for text. + // Only tabs with available short labels are shown. + struct ShortLabel { + ChatSearchTab tab = {}; + TextWithEntities label; + }; + void setTabShortLabels( + std::vector labels, + ChatSearchTab active); + + [[nodiscard]] rpl::producer tabChanges() const; + +private: + struct Tab { + ChatSearchTab value = {}; + QString label; + TextWithEntities shortLabel; + }; + + void refreshTabs(ChatSearchTab active); + int resizeGetHeight(int newWidth) override; + + const std::unique_ptr _tabs; + const std::unique_ptr _shadow; + + std::vector _list; + rpl::variable _active; + ChatSearchPeerTabType _type = {}; + +}; + +struct FixedHashtagSearchQuery { + QString text; + int cursorPosition = 0; +}; +[[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery( + const QString &query, + int cursorPosition); + +[[nodiscard]] bool IsHashtagSearchQuery(const QString &query); + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index e460b4dc8..9d69f6738 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -211,7 +211,7 @@ void TopicIconView::setupImage(not_null topic) { ) | rpl::start_with_next([=] { _image = ForumTopicGeneralIconFrame( st::infoForumTopicIcon.size, - _generalIconFg); + _generalIconFg->c); _update(); }, _lifetime); return; diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index da2ea224d..533fe85ef 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -85,6 +85,8 @@ PRIVATE data/data_subscription_option.h dialogs/dialogs_three_state_icon.h + dialogs/ui/chat_search_tabs.cpp + dialogs/ui/chat_search_tabs.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h dialogs/ui/top_peers_strip.cpp