From fdbdeeb95694cfd0f9a22634dc0195126a8010c2 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 May 2025 14:06:46 +0400 Subject: [PATCH] Start new tabs for monoforums. --- Telegram/CMakeLists.txt | 2 + .../chat_helpers/ttl_media_layer_widget.cpp | 7 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 60 +-- .../dialogs/ui/dialogs_topics_view.cpp | 2 +- .../admin_log/history_admin_log_inner.cpp | 5 +- .../admin_log/history_admin_log_inner.h | 2 +- .../history/history_inner_widget.cpp | 20 +- .../history/history_inner_widget.h | 5 +- .../SourceFiles/history/history_widget.cpp | 165 +++++--- Telegram/SourceFiles/history/history_widget.h | 8 + .../view/history_view_chat_section.cpp | 103 +++-- .../history/view/history_view_chat_section.h | 5 + .../history/view/history_view_element.cpp | 20 +- .../history/view/history_view_element.h | 16 +- .../history/view/history_view_list_widget.cpp | 10 +- .../history/view/history_view_list_widget.h | 6 +- .../history/view/history_view_message.cpp | 47 ++- .../view/history_view_service_message.cpp | 6 +- .../view/history_view_subsection_tabs.cpp | 384 ++++++++++++++++++ .../view/history_view_subsection_tabs.h | 84 ++++ .../info/profile/info_profile_actions.cpp | 7 +- Telegram/SourceFiles/mainwidget.cpp | 4 +- .../business/settings_shortcut_messages.cpp | 2 +- Telegram/SourceFiles/ui/chat/chat.style | 31 ++ .../SourceFiles/window/section_widget.cpp | 2 + Telegram/SourceFiles/window/section_widget.h | 3 + .../window/window_session_controller.cpp | 30 ++ .../window/window_session_controller.h | 14 + 28 files changed, 869 insertions(+), 181 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp create mode 100644 Telegram/SourceFiles/history/view/history_view_subsection_tabs.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 85a7703fd6..3184b11ae4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -888,6 +888,8 @@ PRIVATE history/view/history_view_sponsored_click_handler.h history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.h + history/view/history_view_subsection_tabs.cpp + history/view/history_view_subsection_tabs.h history/view/history_view_text_helper.cpp history/view/history_view_text_helper.h history/view/history_view_transcribe_button.cpp diff --git a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp index 583fed29ae..65940c6b00 100644 --- a/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/ttl_media_layer_widget.cpp @@ -52,7 +52,7 @@ public: bool elementAnimationsPaused() override; not_null elementPathShiftGradient() override; HistoryView::Context elementContext() override; - bool elementIsChatWide() override; + HistoryView::ElementChatMode elementChatMode() override; private: const not_null _parent; @@ -83,8 +83,9 @@ HistoryView::Context PreviewDelegate::elementContext() { return HistoryView::Context::TTLViewer; } -bool PreviewDelegate::elementIsChatWide() { - return _chatWide.current(); +HistoryView::ElementChatMode PreviewDelegate::elementChatMode() { + using Mode = HistoryView::ElementChatMode; + return _chatWide.current() ? Mode::Wide : Mode::Default; } class PreviewWrap final : public Ui::RpWidget { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index ac7bff41db..4ecbf32115 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -940,27 +940,27 @@ void Widget::chosenRow(const ChosenRow &row) { } } return; - } else if (history - && history->peer->amMonoforumAdmin() - && !row.message.fullId) { - const auto monoforum = history->peer->monoforum(); - if (controller()->shownMonoforum().current() == monoforum) { - controller()->closeMonoforum(); - //} else if (row.newWindow) { // #TODO monoforum - // controller()->showInNewWindow( - // Window::SeparateId(Window::SeparateType::Forum, history)); - } else { - controller()->showMonoforum( - monoforum, - Window::SectionShow().withChildColumn()); - if (!controller()->adaptive().isOneColumn()) { - controller()->showThread( - history, - ShowAtUnreadMsgId, - Window::SectionShow::Way::ClearStack); - } - } - return; + //} else if (history + // && history->peer->amMonoforumAdmin() + // && !row.message.fullId) { + // const auto monoforum = history->peer->monoforum(); + // if (controller()->shownMonoforum().current() == monoforum) { + // controller()->closeMonoforum(); + // //} else if (row.newWindow) { // #TODO monoforum + // // controller()->showInNewWindow( + // // Window::SeparateId(Window::SeparateType::Forum, history)); + // } else { + // controller()->showMonoforum( + // monoforum, + // Window::SectionShow().withChildColumn()); + // if (!controller()->adaptive().isOneColumn()) { + // controller()->showThread( + // history, + // ShowAtUnreadMsgId, + // Window::SectionShow::Way::ClearStack); + // } + // } + // return; } else if (history) { const auto peer = history->peer; const auto showAtMsgId = controller()->uniqueChatsInSearchResults() @@ -999,13 +999,17 @@ void Widget::chosenRow(const ChosenRow &row) { using namespace Window; auto params = SectionShow(SectionShow::Way::Forward); params.dropSameFromStack = true; - using namespace HistoryView; - controller()->showSection( - std::make_shared(ChatViewId{ - .history = sublist->owningHistory(), - .sublist = sublist, - }), - params); + params.highlightPart.text = _searchState.query; + if (!params.highlightPart.empty()) { + params.highlightPartOffsetHint = kSearchQueryOffsetHint; + } + if (false && row.newWindow) { // #TODO monoforum + controller()->showInNewWindow( + Window::SeparateId(sublist), + row.message.fullId.msg); + } else { + controller()->showThread(sublist, row.message.fullId.msg, params); + } } if (row.filteredRow && !session().supportMode()) { if (_subsectionTopBar) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp index ba42d90cc0..4ce796c92e 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_topics_view.cpp @@ -152,7 +152,7 @@ void TopicsView::prepare(PeerId frontPeerId, Fn customEmojiRepaint) { Ui::Text::SingleCustomEmoji( manager->peerUserpicEmojiData(peer), u"@"_q) - ).append(peer->shortName()); + ).append(' ').append(peer->shortName()); title.key = key; title.version = peer->nameVersion(); title.unread = unread; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 61a599cac4..0d917654d4 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -740,8 +740,9 @@ void InnerWidget::elementSearchInList( void InnerWidget::elementHandleViaClick(not_null bot) { } -bool InnerWidget::elementIsChatWide() { - return _isChatWide; +HistoryView::ElementChatMode InnerWidget::elementChatMode() { + using Mode = HistoryView::ElementChatMode; + return _isChatWide ? Mode::Wide : Mode::Default; } not_null InnerWidget::elementPathShiftGradient() { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index 1f571edffc..9f58afb921 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -131,7 +131,7 @@ public: const QString &query, const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; - bool elementIsChatWide() override; + HistoryView::ElementChatMode elementChatMode() override; not_null elementPathShiftGradient() override; void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction( diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 6d01dc5710..933de0d20c 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -241,8 +241,9 @@ public: _widget->elementHandleViaClick(bot); } } - bool elementIsChatWide() override { - return _widget ? _widget->elementIsChatWide() : false; + HistoryView::ElementChatMode elementChatMode() override { + using Mode = HistoryView::ElementChatMode; + return _widget ? _widget->elementChatMode() : Mode::Default; } not_null elementPathShiftGradient() override { Expects(_widget != nullptr); @@ -808,7 +809,11 @@ bool HistoryInner::canHaveFromUserpics() const { } else if (const auto channel = _peer->asBroadcast()) { return channel->signatureProfiles(); } - return true; + return !_removeFromUserpics; +} + +void HistoryInner::toggleRemoveFromUserpics(bool remove) { + _removeFromUserpics = remove; } template @@ -3930,8 +3935,13 @@ void HistoryInner::elementHandleViaClick(not_null bot) { _widget->insertBotCommand('@' + bot->username()); } -bool HistoryInner::elementIsChatWide() { - return _isChatWide; +HistoryView::ElementChatMode HistoryInner::elementChatMode() { + using Mode = HistoryView::ElementChatMode; + return _isChatWide + ? Mode::Wide + : _removeFromUserpics + ? Mode::Narrow + : Mode::Default; } not_null HistoryInner::elementPathShiftGradient() { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 1b603babcb..0da2dc7657 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -35,6 +35,7 @@ struct SelectionModeResult; struct StateRequest; enum class CursorState : char; enum class PointState : char; +enum class ElementChatMode : char; class EmptyPainter; class Element; class TranslateTracker; @@ -165,7 +166,7 @@ public: const QString &query, const FullMsgId &context); void elementHandleViaClick(not_null bot); - bool elementIsChatWide(); + HistoryView::ElementChatMode elementChatMode(); not_null elementPathShiftGradient(); void elementReplyTo(const FullReplyTo &to); void elementStartInteraction(not_null view); @@ -193,6 +194,7 @@ public: void setChooseReportReason(Data::ReportInput reportInput); void clearChooseReportReason(); + void toggleRemoveFromUserpics(bool remove); // -1 if should not be visible, -2 if bad history() [[nodiscard]] int itemTop(const HistoryItem *item) const; @@ -493,6 +495,7 @@ private: const std::unique_ptr _pathGradient; QPainterPath _highlightPathCache; bool _isChatWide = false; + bool _removeFromUserpics = false; base::flat_set> _animatedStickersPlayed; base::flat_map, Ui::PeerUserpicView> _userpics; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 4007594a05..a295569910 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -117,6 +117,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_reply.h" #include "history/view/history_view_requests_bar.h" #include "history/view/history_view_sticker_toast.h" +#include "history/view/history_view_subsection_tabs.h" #include "history/view/history_view_translate_bar.h" #include "history/view/media/history_view_media.h" #include "profile/profile_block_group_members.h" @@ -236,6 +237,7 @@ HistoryWidget::HistoryWidget( , _api(&controller->session().mtp()) , _updateEditTimeLeftDisplay([=] { updateField(); }) , _fieldBarCancel(this, st::historyReplyCancel) +, _topBars(std::make_unique(this)) , _topBar(this, controller) , _scroll( this, @@ -1713,6 +1715,7 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { void HistoryWidget::orderWidgets() { _voiceRecordBar->raise(); _send->raise(); + _topBars->raise(); if (_businessBotStatus) { _businessBotStatus->bar().raise(); } @@ -1740,6 +1743,9 @@ void HistoryWidget::orderWidgets() { if (_chooseTheme) { _chooseTheme->raise(); } + if (_subsectionTabs) { + _subsectionTabs->raise(); + } _topShadow->raise(); if (_autocomplete) { _autocomplete->raise(); @@ -2467,6 +2473,11 @@ void HistoryWidget::showHistory( _fieldDisabled = nullptr; _silent.destroy(); updateBotKeyboard(); + + if (_subsectionTabs) { + _subsectionTabsLifetime.destroy(); + controller()->saveSubsectionTabs(base::take(_subsectionTabs)); + } } else { Assert(_list == nullptr); } @@ -2501,7 +2512,7 @@ void HistoryWidget::showHistory( _peer = session().data().peer(peerId); _contactStatus = std::make_unique( controller(), - this, + _topBars.get(), _peer, false); _contactStatus->bar().heightValue( @@ -2514,7 +2525,7 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { _paysStatus = std::make_unique( controller(), - this, + _topBars.get(), user); _paysStatus->bar().heightValue( ) | rpl::start_with_next([=] { @@ -2522,7 +2533,7 @@ void HistoryWidget::showHistory( }, _paysStatus->bar().lifetime()); _businessBotStatus = std::make_unique( controller(), - this, + _topBars.get(), user); _businessBotStatus->bar().heightValue( ) | rpl::start_with_next([=] { @@ -3194,29 +3205,12 @@ void HistoryWidget::updateControlsVisibility() { } else if (!_firstLoadRequest && _scroll->isHidden()) { _scroll->show(); } - if (_pinnedBar) { - _pinnedBar->show(); - } + _topBars->show(); if (_sponsoredMessageBar && checkSponsoredMessageBarVisibility()) { _sponsoredMessageBar->toggle(true, anim::type::normal); } - if (_translateBar) { - _translateBar->show(); - } - if (_groupCallBar) { - _groupCallBar->show(); - } - if (_requestsBar) { - _requestsBar->show(); - } - if (_paysStatus) { - _paysStatus->show(); - } - if (_contactStatus) { - _contactStatus->show(); - } - if (_businessBotStatus) { - _businessBotStatus->show(); + if (_subsectionTabs) { + _subsectionTabs->show(); } if (isChoosingTheme() || (!editingMessage() @@ -4431,20 +4425,12 @@ void HistoryWidget::hideChildWidgets() { if (_tabbedPanel) { _tabbedPanel->hideFast(); } - if (_pinnedBar) { - _pinnedBar->hide(); - } if (_sponsoredMessageBar) { _sponsoredMessageBar->toggle(false, anim::type::instant); } - if (_translateBar) { - _translateBar->hide(); - } - if (_groupCallBar) { - _groupCallBar->hide(); - } - if (_requestsBar) { - _requestsBar->hide(); + _topBars->hide(); + if (_subsectionTabs) { + _subsectionTabs->hide(); } if (_voiceRecordBar) { _voiceRecordBar->hideFast(); @@ -4455,15 +4441,6 @@ void HistoryWidget::hideChildWidgets() { if (_chooseTheme) { _chooseTheme->hide(); } - if (_paysStatus) { - _paysStatus->hide(); - } - if (_contactStatus) { - _contactStatus->hide(); - } - if (_businessBotStatus) { - _businessBotStatus->hide(); - } hideChildren(); } @@ -4747,6 +4724,8 @@ MsgId HistoryWidget::msgId() const { void HistoryWidget::showAnimated( Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { + validateSubsectionTabs(); + _showAnimation = nullptr; // If we show pinned bar here, we don't want it to change the @@ -4791,6 +4770,11 @@ void HistoryWidget::showAnimated( activate(); } +void HistoryWidget::showFast() { + validateSubsectionTabs(); + show(); +} + void HistoryWidget::showFinished() { _cornerButtons.finishAnimations(); if (_pinnedBar) { @@ -6419,40 +6403,50 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { } void HistoryWidget::updateControlsGeometry() { - _topBar->resizeToWidth(width()); + const auto width = this->width(); + + _topBar->resizeToWidth(width); _topBar->moveToLeft(0, 0); - _voiceRecordBar->resizeToWidth(width()); + + const auto tabsLeftSkip = _subsectionTabs + ? _subsectionTabs->leftSkip() + : 0; + const auto innerWidth = width - tabsLeftSkip; + + _voiceRecordBar->resizeToWidth(width); moveFieldControls(); - const auto groupCallTop = _topBar->bottomNoMargins(); + _topBars->move(tabsLeftSkip, _topBar->bottomNoMargins() + + (_subsectionTabs ? _subsectionTabs->topSkip() : 0)); + const auto groupCallTop = 0; if (_groupCallBar) { _groupCallBar->move(0, groupCallTop); - _groupCallBar->resizeToWidth(width()); + _groupCallBar->resizeToWidth(innerWidth); } const auto requestsTop = groupCallTop + (_groupCallBar ? _groupCallBar->height() : 0); if (_requestsBar) { _requestsBar->move(0, requestsTop); - _requestsBar->resizeToWidth(width()); + _requestsBar->resizeToWidth(innerWidth); } const auto pinnedBarTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0); if (_pinnedBar) { _pinnedBar->move(0, pinnedBarTop); - _pinnedBar->resizeToWidth(width()); + _pinnedBar->resizeToWidth(innerWidth); } const auto sponsoredMessageBarTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0); if (_sponsoredMessageBar) { _sponsoredMessageBar->move(0, sponsoredMessageBarTop); - _sponsoredMessageBar->resizeToWidth(width()); + _sponsoredMessageBar->resizeToWidth(innerWidth); } const auto translateTop = sponsoredMessageBarTop + (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0); if (_translateBar) { _translateBar->move(0, translateTop); - _translateBar->resizeToWidth(width()); + _translateBar->resizeToWidth(innerWidth); } const auto paysStatusTop = translateTop + (_translateBar ? _translateBar->height() : 0); @@ -6462,17 +6456,19 @@ void HistoryWidget::updateControlsGeometry() { const auto contactStatusTop = paysStatusTop + (_paysStatus ? _paysStatus->bar().height() : 0); if (_contactStatus) { - _contactStatus->bar().move(0, contactStatusTop); + _contactStatus->bar().move(tabsLeftSkip, contactStatusTop); } const auto businessBotTop = contactStatusTop + (_contactStatus ? _contactStatus->bar().height() : 0); if (_businessBotStatus) { - _businessBotStatus->bar().move(0, businessBotTop); + _businessBotStatus->bar().move(tabsLeftSkip, businessBotTop); } - const auto scrollAreaTop = businessBotTop + const auto scrollAreaTop = _topBars->y() + + businessBotTop + (_businessBotStatus ? _businessBotStatus->bar().height() : 0); + _topBars->resize(innerWidth, scrollAreaTop - _topBars->y()); if (_scroll->y() != scrollAreaTop) { - _scroll->moveToLeft(0, scrollAreaTop); + _scroll->moveToLeft(tabsLeftSkip, scrollAreaTop); if (_autocomplete) { _autocomplete->setBoundings(_scroll->geometry()); } @@ -6502,7 +6498,7 @@ void HistoryWidget::updateControlsGeometry() { _topShadow->setGeometryToLeft( topShadowLeft, _topBar->bottomNoMargins(), - width() - topShadowLeft - topShadowRight, + width - topShadowLeft - topShadowRight, st::lineWidth); } @@ -6704,7 +6700,12 @@ void HistoryWidget::updateHistoryGeometry( return; } - auto newScrollHeight = height() - _topBar->height(); + const auto newScrollWidth = width() + - (_subsectionTabs ? _subsectionTabs->leftSkip() : 0); + const auto subsectionTabsTop = _topBar->bottomNoMargins(); + auto newScrollHeight = height() + - subsectionTabsTop + - (_subsectionTabs ? _subsectionTabs->topSkip() : 0); if (_translateBar) { newScrollHeight -= _translateBar->height(); } @@ -6760,10 +6761,10 @@ void HistoryWidget::updateHistoryGeometry( } const auto wasScrollTop = _scroll->scrollTop(); const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax()); - const auto needResize = (_scroll->width() != width()) + const auto needResize = (_scroll->width() != newScrollWidth) || (_scroll->height() != newScrollHeight); if (needResize) { - _scroll->resize(width(), newScrollHeight); + _scroll->resize(newScrollWidth, newScrollHeight); // on initial updateListSize we didn't put the _scroll->scrollTop // correctly yet so visibleAreaUpdated() call will erase it // with the new (undefined) value @@ -6781,6 +6782,12 @@ void HistoryWidget::updateHistoryGeometry( _cornerButtons.updatePositions(); controller()->floatPlayerAreaUpdated(); } + if (_subsectionTabs) { + const auto scrollBottom = _scroll->y() + newScrollHeight; + const auto areaHeight = scrollBottom - subsectionTabsTop; + _subsectionTabs->setBoundingRect( + { 0, subsectionTabsTop, width(), areaHeight }); + } updateListSize(); _updateHistoryGeometryRequired = false; @@ -7625,7 +7632,7 @@ void HistoryWidget::setupTranslateBar() { Expects(_history != nullptr); _translateBar = std::make_unique( - this, + _topBars.get(), controller(), _history); @@ -7700,7 +7707,7 @@ void HistoryWidget::checkPinnedBarState() { } clearHidingPinnedBar(); - _pinnedBar = std::make_unique(this, [=] { + _pinnedBar = std::make_unique(_topBars.get(), [=] { return controller()->isGifPausedAtLeastFor( Window::GifPauseReason::Any); }, controller()->gifPauseLevelChanged()); @@ -7921,7 +7928,7 @@ void HistoryWidget::setupGroupCallBar() { return; } _groupCallBar = std::make_unique( - this, + _topBars.get(), HistoryView::GroupCallBarContentByPeer( peer, st::historyGroupCallUserpics.size, @@ -7974,7 +7981,7 @@ void HistoryWidget::setupRequestsBar() { return; } _requestsBar = std::make_unique( - this, + _topBars.get(), HistoryView::RequestsBarContentByPeer( peer, st::historyRequestsUserpics.size, @@ -8087,7 +8094,7 @@ void HistoryWidget::checkSponsoredMessageBar() { void HistoryWidget::createSponsoredMessageBar() { _sponsoredMessageBar = base::make_unique_q>( - this, + _topBars.get(), object_ptr(this)); _sponsoredMessageBar->entity()->resizeToWidth(_scroll->width()); @@ -8250,6 +8257,34 @@ void HistoryWidget::showPremiumToast(not_null document) { _stickerToast->showFor(document); } +void HistoryWidget::validateSubsectionTabs() { + if (!_history || !HistoryView::SubsectionTabs::UsedFor(_history)) { + _subsectionTabsLifetime.destroy(); + _subsectionTabs = nullptr; + return; + } else if (_subsectionTabs) { + return; + } + _subsectionTabs = controller()->restoreSubsectionTabsFor(this, _history); + if (!_subsectionTabs) { + _subsectionTabs = std::make_unique( + controller(), + this, + _history); + } + _subsectionTabs->removeRequests() | rpl::start_with_next([=] { + _subsectionTabs = nullptr; + updateControlsGeometry(); + }, _subsectionTabsLifetime); + _subsectionTabs->layoutRequests() | rpl::start_with_next([=] { + _list->toggleRemoveFromUserpics(_subsectionTabs->leftSkip() > 0); + updateControlsGeometry(); + orderWidgets(); + }, _subsectionTabsLifetime); + updateControlsGeometry(); + orderWidgets(); +} + void HistoryWidget::checkCharsCount() { _fieldCharsCountManager.setCount(Ui::ComputeFieldCharacterCount(_field)); checkCharsLimitation(); @@ -9439,5 +9474,7 @@ HistoryWidget::~HistoryWidget() { session().data().itemVisibilitiesUpdated(); } + _subsectionTabsLifetime.destroy(); + _subsectionTabs = nullptr; setTabbedPanel(nullptr); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index c11ecdc7c0..e9f536e403 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -107,6 +107,7 @@ class Element; class PinnedTracker; class TranslateBar; class ComposeSearch; +class SubsectionTabs; struct SelectedQuote; } // namespace HistoryView @@ -183,6 +184,7 @@ public: void showAnimated( Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); + void showFast(); void finishAnimating(); void doneShow(); @@ -684,6 +686,8 @@ private: void switchToSearch(QString query); + void validateSubsectionTabs(); + void checkCharsCount(); void checkCharsLimitation(); @@ -707,6 +711,8 @@ private: object_ptr _fieldBarCancel; + std::unique_ptr _topBars; + std::unique_ptr _translateBar; int _translateBarHeight = 0; @@ -821,6 +827,8 @@ private: const std::unique_ptr _voiceRecordBar; const std::unique_ptr _forwardPanel; std::unique_ptr _composeSearch; + std::unique_ptr _subsectionTabs; + rpl::lifetime _subsectionTabsLifetime; bool _cmdStartShown = false; object_ptr _field; base::unique_qptr _fieldDisabled; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index 7545a3e79f..a833cb6cd4 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_contact_status.h" #include "history/view/history_view_scheduled_section.h" #include "history/view/history_view_service_message.h" +#include "history/view/history_view_subsection_tabs.h" #include "history/view/history_view_pinned_tracker.h" #include "history/view/history_view_pinned_section.h" #include "history/view/history_view_translate_bar.h" @@ -237,6 +238,7 @@ ChatWidget::ChatWidget( : nullptr) , _topBar(this, controller) , _topBarShadow(this) +, _topBars(std::make_unique(this)) , _composeControls(std::make_unique( this, ComposeControlsDescriptor{ @@ -256,7 +258,8 @@ ChatWidget::ChatWidget( }) | rpl::type_erased() : rpl::single(false), })) -, _translateBar(std::make_unique(this, controller, _history)) +, _translateBar( + std::make_unique(_topBars.get(), controller, _history)) , _scroll(std::make_unique( this, controller->chatStyle()->value(lifetime(), st::historyScroll), @@ -444,6 +447,10 @@ ChatWidget::~ChatWidget() { if (_repliesRootId) { controller()->sendingAnimation().clear(); } + if (_subsectionTabs) { + _subsectionTabsLifetime.destroy(); + controller()->saveSubsectionTabs(base::take(_subsectionTabs)); + } if (_topic) { if (_topic->creating()) { _emptyPainter = nullptr; @@ -471,9 +478,10 @@ void ChatWidget::orderWidgets() { if (_pinnedBar) { _pinnedBar->raise(); } - if (_topBar) { - _topBar->raise(); + if (_subsectionTabs) { + _subsectionTabs->raise(); } + _topBar->raise(); _topBarShadow->raise(); _composeControls->raisePanels(); } @@ -499,7 +507,7 @@ void ChatWidget::setupRootView() { if (_topic || !_repliesRootId) { return; } - _repliesRootView = std::make_unique(this, [=] { + _repliesRootView = std::make_unique(_topBars.get(), [=] { return controller()->isGifPausedAtLeastFor( Window::GifPauseReason::Any); }, controller()->gifPauseLevelChanged()); @@ -577,7 +585,9 @@ void ChatWidget::setupTopicViewer() { void ChatWidget::subscribeToTopic() { Expects(_topic != nullptr); - _topicReopenBar = std::make_unique(this, _topic); + _topicReopenBar = std::make_unique( + _topBars.get(), + _topic); _topicReopenBar->bar().setVisible(!animatingShow()); _topicReopenBarHeight = _topicReopenBar->bar().height(); _topicReopenBar->bar().heightValue( @@ -1509,6 +1519,37 @@ void ChatWidget::edit( doSetInnerFocus(); } +void ChatWidget::validateSubsectionTabs() { + if (!HistoryView::SubsectionTabs::UsedFor(_history)) { + _subsectionTabsLifetime.destroy(); + _subsectionTabs = nullptr; + return; + } else if (_subsectionTabs) { + return; + } + const auto thread = _topic ? (Data::Thread*)_topic : _sublist; + _subsectionTabs = controller()->restoreSubsectionTabsFor(this, thread); + if (!_subsectionTabs) { + _subsectionTabs = std::make_unique( + controller(), + this, + thread); + } + _subsectionTabs->removeRequests() | rpl::start_with_next([=] { + _subsectionTabs = nullptr; + updateControlsGeometry(); + }, _subsectionTabsLifetime); + _subsectionTabs->layoutRequests() | rpl::start_with_next([=] { + _inner->overrideChatMode((_subsectionTabs->leftSkip() > 0) + ? ElementChatMode::Narrow + : std::optional()); + updateControlsGeometry(); + orderWidgets(); + }, _subsectionTabsLifetime); + updateControlsGeometry(); + orderWidgets(); +} + void ChatWidget::refreshJoinGroupButton() { if (!_repliesRootId) { return; @@ -1937,7 +1978,7 @@ void ChatWidget::checkPinnedBarState() { } clearHidingPinnedBar(); - _pinnedBar = std::make_unique(this, [=] { + _pinnedBar = std::make_unique(_topBars.get(), [=] { return controller()->isGifPausedAtLeastFor( Window::GifPauseReason::Any); }, controller()->gifPauseLevelChanged()); @@ -2252,13 +2293,10 @@ QPixmap ChatWidget::grabForShowAnimation(const Window::SectionSlideParams ¶m if (params.withTopBarShadow) { _topBarShadow->show(); } - if (_repliesRootView) { - _repliesRootView->hide(); + _topBars->hide(); + if (_subsectionTabs) { + _subsectionTabs->hide(); } - if (_pinnedBar) { - _pinnedBar->hide(); - } - _translateBar->hide(); return result; } @@ -2529,13 +2567,20 @@ void ChatWidget::updateControlsGeometry() { : 0; _topBar->resizeToWidth(contentWidth); _topBarShadow->resize(contentWidth, st::lineWidth); + const auto tabsLeftSkip = _subsectionTabs + ? _subsectionTabs->leftSkip() + : 0; + const auto innerWidth = contentWidth - tabsLeftSkip; + const auto subsectionTabsTop = _topBar->bottomNoMargins(); + _topBars->move(tabsLeftSkip, subsectionTabsTop + + (_subsectionTabs ? _subsectionTabs->topSkip() : 0)); if (_repliesRootView) { - _repliesRootView->resizeToWidth(contentWidth); + _repliesRootView->resizeToWidth(innerWidth); } - auto top = _topBar->height() + _repliesRootViewHeight; + auto top = _repliesRootViewHeight; if (_pinnedBar) { _pinnedBar->move(0, top); - _pinnedBar->resizeToWidth(contentWidth); + _pinnedBar->resizeToWidth(innerWidth); top += _pinnedBarHeight; } if (_topicReopenBar) { @@ -2543,7 +2588,7 @@ void ChatWidget::updateControlsGeometry() { top += _topicReopenBar->bar().height(); } _translateBar->move(0, top); - _translateBar->resizeToWidth(contentWidth); + _translateBar->resizeToWidth(innerWidth); top += _translateBarHeight; auto bottom = height(); @@ -2563,15 +2608,18 @@ void ChatWidget::updateControlsGeometry() { bottom -= _composeControls->heightCurrent(); } + _topBars->resize(innerWidth, top); + top += _topBars->y(); + const auto scrollHeight = bottom - top; - const auto scrollSize = QSize(contentWidth, scrollHeight); + const auto scrollSize = QSize(innerWidth, scrollHeight); if (_scroll->size() != scrollSize) { _skipScrollEvent = true; _scroll->resize(scrollSize); _inner->resizeToWidth(scrollSize.width(), _scroll->height()); _skipScrollEvent = false; } - _scroll->move(0, top); + _scroll->move(tabsLeftSkip, top); if (!_scroll->isHidden()) { if (newScrollTop) { _scroll->scrollToY(*newScrollTop); @@ -2581,6 +2629,13 @@ void ChatWidget::updateControlsGeometry() { _composeControls->move(0, bottom); _composeControls->setAutocompleteBoundingRect(_scroll->geometry()); + if (_subsectionTabs) { + const auto scrollBottom = _scroll->y() + scrollHeight; + const auto areaHeight = scrollBottom - subsectionTabsTop; + _subsectionTabs->setBoundingRect( + { 0, subsectionTabsTop, width(), areaHeight }); + } + _cornerButtons.updatePositions(); } @@ -2700,15 +2755,9 @@ void ChatWidget::showFinishedHook() { _composeControls->showFinished(); } _inner->showFinished(); - if (_repliesRootView) { - _repliesRootView->show(); - } - if (_pinnedBar) { - _pinnedBar->show(); - } - _translateBar->show(); - if (_topicReopenBar) { - _topicReopenBar->bar().show(); + _topBars->show(); + if (_subsectionTabs) { + _subsectionTabs->show(); } // We should setup the drag area only after diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.h b/Telegram/SourceFiles/history/view/history_view_chat_section.h index a62a30a2e9..a4fb8fb012 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.h @@ -73,6 +73,7 @@ class TopicReopenBar; class EmptyPainter; class PinnedTracker; class TranslateBar; +class SubsectionTabs; struct ChatViewId { not_null history; @@ -372,6 +373,7 @@ private: Api::SendOptions options, std::optional localMessageId); + void validateSubsectionTabs() override; void setupEmptyPainter(); void refreshJoinGroupButton(); [[nodiscard]] bool emptyShown() const; @@ -396,6 +398,7 @@ private: QPointer _inner; object_ptr _topBar; object_ptr _topBarShadow; + std::unique_ptr _topBars; std::unique_ptr _composeControls; std::unique_ptr _composeSearch; std::unique_ptr _joinGroup; @@ -404,6 +407,8 @@ private: std::unique_ptr _openChatButton; std::unique_ptr _aboutHiddenAuthor; std::unique_ptr _emptyPainter; + std::unique_ptr _subsectionTabs; + rpl::lifetime _subsectionTabsLifetime; bool _canSendTexts = false; bool _skipScrollEvent = false; bool _synteticScrollEvent = false; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index cef5cf4793..3e7377f7ea 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -262,8 +262,8 @@ void DefaultElementDelegate::elementHandleViaClick( not_null bot) { } -bool DefaultElementDelegate::elementIsChatWide() { - return false; +ElementChatMode DefaultElementDelegate::elementChatMode() { + return ElementChatMode::Default; } void DefaultElementDelegate::elementReplyTo(const FullReplyTo &to) { @@ -410,7 +410,7 @@ void UnreadBar::paint( const PaintContext &context, int y, int w, - bool chatWide) const { + ElementChatMode mode) const { const auto previousTranslation = p.transform().dx(); if (previousTranslation != 0) { p.translate(-previousTranslation, 0); @@ -434,7 +434,7 @@ void UnreadBar::paint( p.setPen(st->historyUnreadBarFg()); int maxwidth = w; - if (chatWide) { + if (mode == ElementChatMode::Wide) { maxwidth = qMin( maxwidth, st::msgMaxWidth @@ -609,9 +609,9 @@ void ServicePreMessage::init(PreparedServiceText string) { } } -int ServicePreMessage::resizeToWidth(int newWidth, bool chatWide) { +int ServicePreMessage::resizeToWidth(int newWidth, ElementChatMode mode) { width = newWidth; - if (chatWide) { + if (mode == ElementChatMode::Wide) { accumulate_min( width, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); @@ -644,7 +644,7 @@ void ServicePreMessage::paint( Painter &p, const PaintContext &context, QRect g, - bool chatWide) const { + ElementChatMode mode) const { const auto top = g.top() - height - st::msgMargin.top(); p.translate(0, top); @@ -987,7 +987,8 @@ not_null Element::enforcePurchasedTag() { int Element::AdditionalSpaceForSelectionCheckbox( not_null view, QRect countedGeometry) { - if (!view->hasOutLayout() || view->delegate()->elementIsChatWide()) { + if (!view->hasOutLayout() + || view->delegate()->elementChatMode() == ElementChatMode::Wide) { return 0; } if (countedGeometry.isEmpty()) { @@ -1698,7 +1699,8 @@ bool Element::hasOutLayout() const { } bool Element::hasRightLayout() const { - return hasOutLayout() && !_delegate->elementIsChatWide(); + return hasOutLayout() + && (_delegate->elementChatMode() != ElementChatMode::Wide); } bool Element::drawBubble() const { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index f3a05a8ed2..ea4d9f0f19 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -78,6 +78,12 @@ struct SelectionModeResult { float64 progress = 0.0; }; +enum class ElementChatMode : char { + Default, + Wide, + Narrow, // monoforum with left tabs +}; + class Element; class ElementDelegate { public: @@ -114,7 +120,7 @@ public: const QString &query, const FullMsgId &context) = 0; virtual void elementHandleViaClick(not_null bot) = 0; - virtual bool elementIsChatWide() = 0; + virtual ElementChatMode elementChatMode() = 0; virtual not_null elementPathShiftGradient() = 0; virtual void elementReplyTo(const FullReplyTo &to) = 0; virtual void elementStartInteraction(not_null view) = 0; @@ -169,7 +175,7 @@ public: const QString &query, const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; - bool elementIsChatWide() override; + ElementChatMode elementChatMode() override; void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( @@ -233,7 +239,7 @@ struct UnreadBar : RuntimeComponent { const PaintContext &context, int y, int w, - bool chatWide) const; + ElementChatMode mode) const; QString text; int width = 0; @@ -305,13 +311,13 @@ private: struct ServicePreMessage : RuntimeComponent { void init(PreparedServiceText string); - int resizeToWidth(int newWidth, bool chatWide); + int resizeToWidth(int newWidth, ElementChatMode mode); void paint( Painter &p, const PaintContext &context, QRect g, - bool chatWide) const; + ElementChatMode mode) const; [[nodiscard]] ClickHandlerPtr textState( QPoint point, const StateRequest &request, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 697b96f7ea..7a86adb72c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1898,8 +1898,10 @@ void ListWidget::elementHandleViaClick(not_null bot) { _delegate->listHandleViaClick(bot); } -bool ListWidget::elementIsChatWide() { - return _overrideIsChatWide.value_or(_isChatWide); +ElementChatMode ListWidget::elementChatMode() { + return _overrideChatMode.value_or(_isChatWide + ? ElementChatMode::Wide + : ElementChatMode::Default); } not_null ListWidget::elementPathShiftGradient() { @@ -4284,8 +4286,8 @@ void ListWidget::setEmptyInfoWidget(base::unique_qptr &&w) { } } -void ListWidget::overrideIsChatWide(bool isWide) { - _overrideIsChatWide = isWide; +void ListWidget::overrideChatMode(std::optional mode) { + _overrideChatMode = mode; } ListWidget::~ListWidget() { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 71db29cd53..8f4a354b8c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -428,7 +428,7 @@ public: const QString &query, const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; - bool elementIsChatWide() override; + ElementChatMode elementChatMode() override; not_null elementPathShiftGradient() override; void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; @@ -443,7 +443,7 @@ public: bool elementHideTopicButton(not_null view) override; void setEmptyInfoWidget(base::unique_qptr &&w); - void overrideIsChatWide(bool isWide); + void overrideChatMode(std::optional mode); ~ListWidget(); @@ -834,7 +834,7 @@ private: bool _refreshingViewer = false; bool _showFinished = false; bool _resizePending = false; - std::optional _overrideIsChatWide; + std::optional _overrideChatMode; // _menu must be destroyed before _whoReactedMenuLifetime. rpl::lifetime _whoReactedMenuLifetime; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 43e97b6125..47ec86cb44 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1142,18 +1142,13 @@ void Message::draw(Painter &p, const PaintContext &context) const { } if (context.clip.intersects(QRect(0, aboveh, width(), unreadbarh))) { p.translate(0, aboveh); - bar->paint( - p, - context, - 0, - width(), - delegate()->elementIsChatWide()); + bar->paint(p, context, 0, width(), delegate()->elementChatMode()); p.translate(0, -aboveh); } } if (const auto service = Get()) { - service->paint(p, context, g, delegate()->elementIsChatWide()); + service->paint(p, context, g, delegate()->elementChatMode()); } if (isHidden()) { @@ -1549,8 +1544,8 @@ void Message::draw(Painter &p, const PaintContext &context) const { constexpr auto kMaxHeightRatio = 3.5; constexpr auto kStrokeWidth = 2.; constexpr auto kWaveWidth = 10.; - const auto isLeftSize = (!context.outbg) - || delegate()->elementIsChatWide(); + const auto isLeftSize = !context.outbg + || (delegate()->elementChatMode() == ElementChatMode::Wide); const auto ratio = std::min(context.gestureHorizontal.ratio, 1.); const auto reachRatio = context.gestureHorizontal.reachRatio; const auto size = st::historyFastShareSize; @@ -1635,7 +1630,8 @@ void Message::draw(Painter &p, const PaintContext &context) const { } const auto o = ScopedPainterOpacity(p, progress); const auto &st = st::msgSelectionCheck; - const auto right = delegate()->elementIsChatWide() + const auto right = (delegate()->elementChatMode() + == ElementChatMode::Wide) ? std::min( int(_bubbleWidthLimit + st::msgPhotoSkip @@ -2465,7 +2461,7 @@ bool Message::hasFromPhoto() const { case Context::AdminLog: return true; case Context::Monoforum: - return delegate()->elementIsChatWide(); + return (delegate()->elementChatMode() == ElementChatMode::Wide); case Context::History: case Context::ChatPreview: case Context::TTLViewer: @@ -2484,8 +2480,10 @@ bool Message::hasFromPhoto() const { || item->isFakeAboutView() || (context() == Context::Replies && item->isDiscussionPost())) { return false; - } else if (delegate()->elementIsChatWide()) { - return true; + } + const auto mode = delegate()->elementChatMode(); + if (mode != ElementChatMode::Default) { + return (mode == ElementChatMode::Wide); } else if (item->history()->peer->isVerifyCodes()) { return !hasOutLayout(); } else if (item->Has()) { @@ -4385,12 +4383,15 @@ QRect Message::countGeometry() const { ? media->width() : width(); const auto outbg = hasOutLayout(); + const auto useMoreSpace = (delegate()->elementChatMode() + == ElementChatMode::Narrow); + const auto wideSkip = useMoreSpace + ? st::msgMargin.left() + : st::msgMargin.right(); const auto availableWidth = width() - st::msgMargin.left() - - (centeredView ? st::msgMargin.left() : st::msgMargin.right()); - auto contentLeft = hasRightLayout() - ? st::msgMargin.right() - : st::msgMargin.left(); + - (centeredView ? st::msgMargin.left() : wideSkip); + auto contentLeft = hasRightLayout() ? wideSkip : st::msgMargin.left(); auto contentWidth = availableWidth; if (hasFromPhoto()) { contentLeft += st::msgPhotoSkip; @@ -4411,7 +4412,8 @@ QRect Message::countGeometry() const { contentWidth = mediaWidth; } } - if (contentWidth < availableWidth && !delegate()->elementIsChatWide()) { + if (contentWidth < availableWidth + && delegate()->elementChatMode() != ElementChatMode::Wide) { if (outbg) { contentLeft += availableWidth - contentWidth; } else if (centeredView) { @@ -4500,7 +4502,7 @@ int Message::resizeContentGetHeight(int newWidth) { auto newHeight = minHeight(); if (const auto service = Get()) { - service->resizeToWidth(newWidth, delegate()->elementIsChatWide()); + service->resizeToWidth(newWidth, delegate()->elementChatMode()); } const auto botTop = item->isFakeAboutView() @@ -4515,9 +4517,14 @@ int Message::resizeContentGetHeight(int newWidth) { // This code duplicates countGeometry() but also resizes media. const auto centeredView = item->isFakeAboutView() || (context() == Context::Replies && item->isDiscussionPost()); + const auto useMoreSpace = (delegate()->elementChatMode() + == ElementChatMode::Narrow); + const auto wideSkip = useMoreSpace + ? st::msgMargin.left() + : st::msgMargin.right(); auto contentWidth = newWidth - st::msgMargin.left() - - (centeredView ? st::msgMargin.left() : st::msgMargin.right()); + - (centeredView ? st::msgMargin.left() : wideSkip); if (hasFromPhoto()) { if (const auto size = rightActionSize()) { contentWidth -= size->width() + (st::msgPhotoSkip - st::historyFastShareSize); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index d215be432a..02470dc925 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -423,7 +423,7 @@ bool Service::consumeHorizontalScroll(QPoint position, int delta) { QRect Service::countGeometry() const { auto result = QRect(0, 0, width(), height()); - if (delegate()->elementIsChatWide()) { + if (delegate()->elementChatMode() == ElementChatMode::Wide) { result.setWidth(qMin(result.width(), st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } auto margins = st::msgServiceMargin; @@ -469,7 +469,7 @@ QSize Service::performCountCurrentSize(int newWidth) { + media->resizeGetHeight(newWidth) + st::msgServiceMargin.bottom(); } else if (!text().isEmpty()) { - if (delegate()->elementIsChatWide()) { + if (delegate()->elementChatMode() == ElementChatMode::Wide) { accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); } contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.left(); // two small margins @@ -561,7 +561,7 @@ void Service::draw(Painter &p, const PaintContext &context) const { context, 0, width(), - delegate()->elementIsChatWide()); + delegate()->elementChatMode()); p.translate(0, -aboveh); } } diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp new file mode 100644 index 0000000000..9d942cb026 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -0,0 +1,384 @@ +/* +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_subsection_tabs.h" + +#include "core/ui_integration.h" +#include "data/stickers/data_custom_emoji.h" +#include "data/data_channel.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" +#include "data/data_session.h" +#include "data/data_thread.h" +#include "dialogs/dialogs_main_list.h" +#include "history/history.h" +#include "lang/lang_keys.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/shadow.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +constexpr auto kDefaultLimit = 10; + +} // namespace + +SubsectionTabs::SubsectionTabs( + not_null controller, + not_null parent, + not_null thread) +: _controller(controller) +, _history(thread->owningHistory()) +, _active(thread) +, _around(thread) +, _beforeLimit(kDefaultLimit) +, _afterLimit(kDefaultLimit) { + track(); + refreshSlice(); + setupHorizontal(parent); +} + +SubsectionTabs::~SubsectionTabs() { + delete base::take(_horizontal); + delete base::take(_vertical); + delete base::take(_shadow); +} + +void SubsectionTabs::setupHorizontal(not_null parent) { + delete base::take(_vertical); + _horizontal = Ui::CreateChild(parent); + _horizontal->show(); + + if (!_shadow) { + _shadow = Ui::CreateChild(parent); + _shadow->show(); + } + + const auto toggle = Ui::CreateChild( + _horizontal, + st::chatTabsToggle); + toggle->show(); + toggle->setClickedCallback([=] { + toggleModes(); + }); + toggle->move(0, 0); + const auto scroll = Ui::CreateChild( + _horizontal, + st::chatTabsScroll, + true); + scroll->show(); + const auto tabs = scroll->setOwnedWidget( + object_ptr(scroll, st::chatTabsSlider)); + tabs->sectionActivated() | rpl::start_with_next([=](int active) { + if (active >= 0 + && active < _slice.size() + && _active != _slice[active]) { + auto params = Window::SectionShow(); + params.way = Window::SectionShow::Way::ClearStack; + params.animated = anim::type::instant; + _controller->showThread(_slice[active], {}, params); + } + }, tabs->lifetime()); + + _horizontal->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto togglew = toggle->width(); + const auto height = size.height(); + scroll->setGeometry(togglew, 0, size.width() - togglew, height); + }, scroll->lifetime()); + + _horizontal->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(_horizontal).fillRect(clip, st::windowBg); + }, _horizontal->lifetime()); + + _refreshed.events_starting_with_copy( + rpl::empty + ) | rpl::start_with_next([=] { + auto sections = std::vector(); + const auto manager = &_history->owner().customEmojiManager(); + auto activeIndex = -1; + for (const auto &thread : _slice) { + if (thread == _active) { + activeIndex = int(sections.size()); + } + if (const auto topic = thread->asTopic()) { + sections.push_back(topic->titleWithIcon()); + } else if (const auto sublist = thread->asSublist()) { + const auto peer = sublist->sublistPeer(); + sections.push_back(TextWithEntities().append( + Ui::Text::SingleCustomEmoji( + manager->peerUserpicEmojiData(peer), + u"@"_q) + ).append(' ').append(peer->shortName())); + } else { + sections.push_back(tr::lng_filters_all_short( + tr::now, + Ui::Text::WithEntities)); + } + } + tabs->setSections(sections, Core::TextContext({ + .session = &_history->session(), + })); + tabs->fitWidthToSections(); + tabs->setActiveSectionFast(activeIndex); + _horizontal->resize( + tabs->width(), + std::max(toggle->height(), tabs->height())); + }, _horizontal->lifetime()); +} + +void SubsectionTabs::setupVertical(not_null parent) { + delete base::take(_horizontal); + _vertical = Ui::CreateChild(parent); + _vertical->show(); + + if (!_shadow) { + _shadow = Ui::CreateChild(parent); + _shadow->show(); + } + + const auto toggle = Ui::CreateChild( + _vertical, + st::chatTabsToggle); + toggle->show(); + const auto active = &st::chatTabsToggleActive; + toggle->setIconOverride(active, active); + toggle->setClickedCallback([=] { + toggleModes(); + }); + toggle->move(0, 0); + const auto scroll = Ui::CreateChild(_vertical); + scroll->show(); + + _vertical->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto toggleh = toggle->height(); + const auto width = size.width(); + scroll->setGeometry(0, toggleh, width, size.height() - toggleh); + }, scroll->lifetime()); + + _vertical->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(_vertical).fillRect(clip, st::windowBg); + }, _vertical->lifetime()); + + _refreshed.events_starting_with_copy( + rpl::empty + ) | rpl::start_with_next([=] { + _vertical->resize(std::max(toggle->width(), 0), 0); + }, _vertical->lifetime()); +} + +void SubsectionTabs::toggleModes() { + Expects((_horizontal || _vertical) && _shadow); + + if (_horizontal) { + setupVertical(_horizontal->parentWidget()); + } else { + setupHorizontal(_vertical->parentWidget()); + } + _layoutRequests.fire({}); +} + +rpl::producer<> SubsectionTabs::removeRequests() const { + if (const auto forum = _history->peer->forum()) { + return forum->destroyed(); + } else if (const auto monoforum = _history->peer->monoforum()) { + return monoforum->destroyed(); + } else { + Unexpected("Peer in SubsectionTabs::removeRequests."); + } +} + +void SubsectionTabs::extractToParent(not_null parent) { + Expects((_horizontal || _vertical) && _shadow); + + if (_vertical) { + _vertical->hide(); + _vertical->setParent(parent); + } else { + _horizontal->hide(); + _horizontal->setParent(parent); + } + _shadow->hide(); + _shadow->setParent(parent); +} + +void SubsectionTabs::setBoundingRect(QRect boundingRect) { + Expects((_horizontal || _vertical) && _shadow); + + if (_horizontal) { + _horizontal->setGeometry( + boundingRect.x(), + boundingRect.y(), + boundingRect.width(), + _horizontal->height()); + _shadow->setGeometry( + boundingRect.x(), + _horizontal->y() + _horizontal->height(), + boundingRect.width(), + st::lineWidth); + } else { + _vertical->setGeometry( + boundingRect.x(), + boundingRect.y(), + _vertical->width(), + boundingRect.height()); + _shadow->setGeometry( + _vertical->x() + _vertical->width(), + boundingRect.y(), + st::lineWidth, + boundingRect.height()); + } +} + +rpl::producer<> SubsectionTabs::layoutRequests() const { + return _layoutRequests.events(); +} + +int SubsectionTabs::leftSkip() const { + return _vertical ? _vertical->width() : 0; +} + +int SubsectionTabs::topSkip() const { + return _horizontal ? _horizontal->height() : 0; +} + +void SubsectionTabs::raise() { + Expects((_horizontal || _vertical) && _shadow); + + if (_horizontal) { + _horizontal->raise(); + } else { + _vertical->raise(); + } + _shadow->raise(); +} + +void SubsectionTabs::show() { + setVisible(true); +} + +void SubsectionTabs::hide() { + setVisible(false); +} + +void SubsectionTabs::setVisible(bool shown) { + Expects((_horizontal || _vertical) && _shadow); + + if (_horizontal) { + _horizontal->setVisible(shown); + } else { + _vertical->setVisible(shown); + } + _shadow->setVisible(shown); +} + +void SubsectionTabs::track() { + if (const auto forum = _history->peer->forum()) { + forum->topicDestroyed( + ) | rpl::start_with_next([=](not_null topic) { + if (_around == topic) { + _around = _history; + refreshSlice(); + } + }, _lifetime); + } else if (const auto monoforum = _history->peer->monoforum()) { + monoforum->sublistDestroyed( + ) | rpl::start_with_next([=](not_null sublist) { + if (_around == sublist) { + _around = _history; + refreshSlice(); + } + }, _lifetime); + } else { + Unexpected("Peer in SubsectionTabs::track."); + } +} + +void SubsectionTabs::refreshSlice() { + const auto forum = _history->peer->forum(); + const auto monoforum = _history->peer->monoforum(); + Assert(forum || monoforum); + + const auto list = forum + ? forum->topicsList() + : monoforum->chatsList(); + auto slice = std::vector>(); + const auto guard = gsl::finally([&] { + if (_slice != slice) { + _slice = std::move(slice); + _refreshed.fire({}); + } + }); + if (!list) { + slice.push_back(_history); + return; + } + const auto &chats = list->indexed()->all(); + auto i = (_around == _history) + ? chats.end() + : ranges::find(chats, _around, [](not_null row) { + return not_null(row->thread()); + }); + if (i == chats.end()) { + i = chats.begin(); + } + const auto takeBefore = std::min(_beforeLimit, int(i - chats.begin())); + const auto takeAfter = std::min(_afterLimit, int(chats.end() - i)); + const auto from = i - takeBefore; + const auto till = i + takeAfter; + _beforeSkipped = std::max(0, int(from - chats.begin())); + _afterSkipped = list->loaded() + ? std::max(0, int(chats.end() - till)) + : std::optional(); + if (from == chats.begin()) { + slice.push_back(_history); + } + for (auto i = from; i != till; ++i) { + slice.push_back((*i)->thread()); + } +} + +bool SubsectionTabs::switchTo( + not_null thread, + not_null parent) { + Expects((_horizontal || _vertical) && _shadow); + + if (thread->owningHistory() != _history) { + return false; + } + if (_vertical) { + _vertical->setParent(parent); + _vertical->show(); + } else { + _horizontal->setParent(parent); + _horizontal->show(); + } + _shadow->setParent(parent); + _shadow->show(); + return true; +} + +bool SubsectionTabs::UsedFor(not_null thread) { + const auto history = thread->owningHistory(); + if (history->amMonoforumAdmin()) { + return true; + } + const auto channel = history->peer->asChannel(); + return channel + && channel->isForum() + && ((channel->flags() & ChannelDataFlag::ForumTabs) || true); AssertIsDebug(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h new file mode 100644 index 0000000000..d896b1778c --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h @@ -0,0 +1,84 @@ +/* +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 + +class History; + +namespace Data { +class Thread; +} // namespace Data + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace HistoryView { + +class SubsectionTabs final { +public: + SubsectionTabs( + not_null controller, + not_null parent, + not_null thread); + ~SubsectionTabs(); + + [[nodiscard]] bool switchTo( + not_null thread, + not_null parent); + + [[nodiscard]] static bool UsedFor(not_null thread); + + [[nodiscard]] rpl::producer<> removeRequests() const; + + void extractToParent(not_null parent); + + void setBoundingRect(QRect boundingRect); + [[nodiscard]] rpl::producer<> layoutRequests() const; + [[nodiscard]] int leftSkip() const; + [[nodiscard]] int topSkip() const; + + void raise(); + void show(); + void hide(); + +private: + void track(); + void setupHorizontal(not_null parent); + void setupVertical(not_null parent); + void toggleModes(); + void setVisible(bool shown); + void refreshSlice(); + + const not_null _controller; + const not_null _history; + + Ui::RpWidget *_horizontal = nullptr; + Ui::RpWidget *_vertical = nullptr; + Ui::RpWidget *_shadow = nullptr; + + std::vector> _slice; + + not_null _active; + not_null _around; + int _beforeLimit = 0; + int _afterLimit = 0; + std::optional _beforeSkipped; + std::optional _afterSkipped; + + rpl::event_stream<> _layoutRequests; + rpl::event_stream<> _refreshed; + + rpl::lifetime _lifetime; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index dd760a3e07..6a902916eb 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -2193,9 +2193,10 @@ Ui::MultiSlideTracker DetailsFiller::fillChannelButtons( }) | rpl::distinct_until_changed(); auto viewDirect = [=] { if (const auto linked = channel->monoforumLink()) { - if (const auto monoforum = linked->monoforum()) { - window->showMonoforum(monoforum); - } + window->showPeerHistory(linked); + //if (const auto monoforum = linked->monoforum()) { + // window->showMonoforum(monoforum); + //} } }; AddMainButton( // #TODO monoforum diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index e2de892e69..b759bdcfd4 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1516,7 +1516,7 @@ void MainWidget::showHistory( : Window::SlideDirection::FromRight, animationParams); } else { - _history->show(); + _history->showFast(); crl::on_main(this, [=] { _controller->widget()->setInnerFocus(); }); @@ -1536,6 +1536,8 @@ void MainWidget::showHistory( } floatPlayerCheckVisibility(); + + controller()->dropSubsectionTabs(); } void MainWidget::showMessage( diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index db5a4d99ad..8963cff5b3 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -373,7 +373,7 @@ ShortcutMessages::ShortcutMessages( this, &controller->session(), static_cast(this)); - _inner->overrideIsChatWide(false); + _inner->overrideChatMode(ElementChatMode::Default); _scroll->sizeValue() | rpl::filter([](QSize size) { return !size.isEmpty(); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index ef30b941de..76b725da93 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1253,3 +1253,34 @@ newPeerUserpicsPadding: margins(0px, 3px, 0px, 0px); newPeerWidth: 320px; swipeBackSize: 150px; + +chatTabsToggle: IconButton(defaultIconButton) { + width: 56px; + height: 36px; + icon: icon {{ "top_bar_profile-flip_horizontal", menuIconFg }}; + iconOver: icon {{ "top_bar_profile-flip_horizontal", menuIconFgOver }}; + ripple: emptyRippleAnimation; +} +chatTabsToggleActive: icon {{ "top_bar_profile-flip_horizontal", windowActiveTextFg }}; +chatTabsScroll: ScrollArea(defaultScrollArea) { + barHidden: true; +} +chatTabsSlider: SettingsSlider(defaultSettingsSlider) { + padding: 0px; + height: 36px; + barTop: 33px; + barSkip: 0px; + barStroke: 6px; + barRadius: 2px; + barFg: transparent; + barSnapToLabel: true; + strictSkip: 18px; + labelTop: 9px; + labelStyle: semiboldTextStyle; + labelFg: windowSubTextFg; + labelFgActive: lightButtonFg; + rippleBottomSkip: 1px; + rippleBg: windowBgOver; + rippleBgActive: lightButtonBgOver; + ripple: defaultRippleAnimation; +} diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 5a72a69847..95944d2f90 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -279,6 +279,7 @@ void SectionWidget::setGeometryWithTopMoved( void SectionWidget::showAnimated( SlideDirection direction, const SectionSlideParams ¶ms) { + validateSubsectionTabs(); if (_showAnimation) { return; } @@ -309,6 +310,7 @@ std::shared_ptr SectionWidget::createMemento() { } void SectionWidget::showFast() { + validateSubsectionTabs(); show(); showFinished(); } diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 24761f0971..b1d6f41cbc 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -194,6 +194,9 @@ public: return nullptr; } + virtual void validateSubsectionTabs() { + } + static void PaintBackground( not_null controller, not_null theme, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a505ce3e8c..b32b8c6b04 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_chat_section.h" #include "history/view/history_view_scheduled_section.h" +#include "history/view/history_view_subsection_tabs.h" #include "media/player/media_player_instance.h" #include "media/view/media_view_open_common.h" #include "data/stickers/data_custom_emoji.h" @@ -3440,8 +3441,37 @@ std::shared_ptr SessionController::uiShow() { return _cachedShow; } +void SessionController::saveSubsectionTabs( + std::unique_ptr tabs) { + _savedSubsectionTabsLifetime.destroy(); + _savedSubsectionTabs = std::move(tabs); + _savedSubsectionTabs->extractToParent(widget()); + _savedSubsectionTabs->removeRequests() | rpl::start_with_next([=] { + _savedSubsectionTabs = nullptr; + }, _savedSubsectionTabsLifetime); +} + +auto SessionController::restoreSubsectionTabsFor( + not_null parent, + not_null thread) +-> std::unique_ptr { + if (!_savedSubsectionTabs) { + return nullptr; + } else if (_savedSubsectionTabs->switchTo(thread, parent)) { + _savedSubsectionTabsLifetime.destroy(); + return base::take(_savedSubsectionTabs); + } + return nullptr; +} + +void SessionController::dropSubsectionTabs() { + _savedSubsectionTabsLifetime.destroy(); + base::take(_savedSubsectionTabs); +} + SessionController::~SessionController() { resetFakeUnreadWhileOpened(); + dropSubsectionTabs(); } bool CheckAndJumpToNearChatsFilter( diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 391fbbafd3..151fa7de7a 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -78,6 +78,10 @@ class SavedSublist; class WallPaper; } // namespace Data +namespace HistoryView { +class SubsectionTabs; +} // namespace HistoryView + namespace HistoryView::Reactions { class CachedIconFactory; } // namespace HistoryView::Reactions @@ -659,6 +663,14 @@ public: [[nodiscard]] std::shared_ptr uiShow() override; + void saveSubsectionTabs( + std::unique_ptr tabs); + [[nodiscard]] auto restoreSubsectionTabsFor( + not_null parent, + not_null thread) + -> std::unique_ptr; + void dropSubsectionTabs(); + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -774,6 +786,8 @@ private: base::has_weak_ptr _storyOpenGuard; QString _premiumRef; + std::unique_ptr _savedSubsectionTabs; + rpl::lifetime _savedSubsectionTabsLifetime; rpl::lifetime _lifetime;