Start new tabs for monoforums.

This commit is contained in:
John Preston 2025-05-23 14:06:46 +04:00
parent 3dbdecf73d
commit fdbdeeb956
28 changed files with 869 additions and 181 deletions

View file

@ -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

View file

@ -52,7 +52,7 @@ public:
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
HistoryView::Context elementContext() override;
bool elementIsChatWide() override;
HistoryView::ElementChatMode elementChatMode() override;
private:
const not_null<QWidget*> _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 {

View file

@ -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<ChatMemento>(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) {

View file

@ -152,7 +152,7 @@ void TopicsView::prepare(PeerId frontPeerId, Fn<void()> 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;

View file

@ -740,8 +740,9 @@ void InnerWidget::elementSearchInList(
void InnerWidget::elementHandleViaClick(not_null<UserData*> bot) {
}
bool InnerWidget::elementIsChatWide() {
return _isChatWide;
HistoryView::ElementChatMode InnerWidget::elementChatMode() {
using Mode = HistoryView::ElementChatMode;
return _isChatWide ? Mode::Wide : Mode::Default;
}
not_null<Ui::PathShiftGradient*> InnerWidget::elementPathShiftGradient() {

View file

@ -131,7 +131,7 @@ public:
const QString &query,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override;
HistoryView::ElementChatMode elementChatMode() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullReplyTo &to) override;
void elementStartInteraction(

View file

@ -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<Ui::PathShiftGradient*> 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 <typename Method>
@ -3930,8 +3935,13 @@ void HistoryInner::elementHandleViaClick(not_null<UserData*> 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<Ui::PathShiftGradient*> HistoryInner::elementPathShiftGradient() {

View file

@ -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<UserData*> bot);
bool elementIsChatWide();
HistoryView::ElementChatMode elementChatMode();
not_null<Ui::PathShiftGradient*> elementPathShiftGradient();
void elementReplyTo(const FullReplyTo &to);
void elementStartInteraction(not_null<const Element*> 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<Ui::PathShiftGradient> _pathGradient;
QPainterPath _highlightPathCache;
bool _isChatWide = false;
bool _removeFromUserpics = false;
base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed;
base::flat_map<not_null<PeerData*>, Ui::PeerUserpicView> _userpics;

View file

@ -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<Ui::RpWidget>(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<ContactStatus>(
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<PaysStatus>(
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<BusinessBotStatus>(
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 &params) {
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<HistoryView::TranslateBar>(
this,
_topBars.get(),
controller(),
_history);
@ -7700,7 +7707,7 @@ void HistoryWidget::checkPinnedBarState() {
}
clearHidingPinnedBar();
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
_pinnedBar = std::make_unique<Ui::PinnedBar>(_topBars.get(), [=] {
return controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);
}, controller()->gifPauseLevelChanged());
@ -7921,7 +7928,7 @@ void HistoryWidget::setupGroupCallBar() {
return;
}
_groupCallBar = std::make_unique<Ui::GroupCallBar>(
this,
_topBars.get(),
HistoryView::GroupCallBarContentByPeer(
peer,
st::historyGroupCallUserpics.size,
@ -7974,7 +7981,7 @@ void HistoryWidget::setupRequestsBar() {
return;
}
_requestsBar = std::make_unique<Ui::RequestsBar>(
this,
_topBars.get(),
HistoryView::RequestsBarContentByPeer(
peer,
st::historyRequestsUserpics.size,
@ -8087,7 +8094,7 @@ void HistoryWidget::checkSponsoredMessageBar() {
void HistoryWidget::createSponsoredMessageBar() {
_sponsoredMessageBar = base::make_unique_q<Ui::SlideWrap<>>(
this,
_topBars.get(),
object_ptr<Ui::RpWidget>(this));
_sponsoredMessageBar->entity()->resizeToWidth(_scroll->width());
@ -8250,6 +8257,34 @@ void HistoryWidget::showPremiumToast(not_null<DocumentData*> 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<HistoryView::SubsectionTabs>(
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);
}

View file

@ -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 &params);
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<Ui::IconButton> _fieldBarCancel;
std::unique_ptr<Ui::RpWidget> _topBars;
std::unique_ptr<HistoryView::TranslateBar> _translateBar;
int _translateBarHeight = 0;
@ -821,6 +827,8 @@ private:
const std::unique_ptr<VoiceRecordBar> _voiceRecordBar;
const std::unique_ptr<ForwardPanel> _forwardPanel;
std::unique_ptr<HistoryView::ComposeSearch> _composeSearch;
std::unique_ptr<HistoryView::SubsectionTabs> _subsectionTabs;
rpl::lifetime _subsectionTabsLifetime;
bool _cmdStartShown = false;
object_ptr<Ui::InputField> _field;
base::unique_qptr<Ui::RpWidget> _fieldDisabled;

View file

@ -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<Ui::RpWidget>(this))
, _composeControls(std::make_unique<ComposeControls>(
this,
ComposeControlsDescriptor{
@ -256,7 +258,8 @@ ChatWidget::ChatWidget(
}) | rpl::type_erased()
: rpl::single(false),
}))
, _translateBar(std::make_unique<TranslateBar>(this, controller, _history))
, _translateBar(
std::make_unique<TranslateBar>(_topBars.get(), controller, _history))
, _scroll(std::make_unique<Ui::ScrollArea>(
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<Ui::PinnedBar>(this, [=] {
_repliesRootView = std::make_unique<Ui::PinnedBar>(_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<TopicReopenBar>(this, _topic);
_topicReopenBar = std::make_unique<TopicReopenBar>(
_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<HistoryView::SubsectionTabs>(
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<ElementChatMode>());
updateControlsGeometry();
orderWidgets();
}, _subsectionTabsLifetime);
updateControlsGeometry();
orderWidgets();
}
void ChatWidget::refreshJoinGroupButton() {
if (!_repliesRootId) {
return;
@ -1937,7 +1978,7 @@ void ChatWidget::checkPinnedBarState() {
}
clearHidingPinnedBar();
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
_pinnedBar = std::make_unique<Ui::PinnedBar>(_topBars.get(), [=] {
return controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);
}, controller()->gifPauseLevelChanged());
@ -2252,13 +2293,10 @@ QPixmap ChatWidget::grabForShowAnimation(const Window::SectionSlideParams &param
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

View file

@ -73,6 +73,7 @@ class TopicReopenBar;
class EmptyPainter;
class PinnedTracker;
class TranslateBar;
class SubsectionTabs;
struct ChatViewId {
not_null<History*> history;
@ -372,6 +373,7 @@ private:
Api::SendOptions options,
std::optional<MsgId> localMessageId);
void validateSubsectionTabs() override;
void setupEmptyPainter();
void refreshJoinGroupButton();
[[nodiscard]] bool emptyShown() const;
@ -396,6 +398,7 @@ private:
QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar;
object_ptr<Ui::PlainShadow> _topBarShadow;
std::unique_ptr<Ui::RpWidget> _topBars;
std::unique_ptr<ComposeControls> _composeControls;
std::unique_ptr<ComposeSearch> _composeSearch;
std::unique_ptr<Ui::FlatButton> _joinGroup;
@ -404,6 +407,8 @@ private:
std::unique_ptr<Ui::FlatButton> _openChatButton;
std::unique_ptr<Ui::RpWidget> _aboutHiddenAuthor;
std::unique_ptr<EmptyPainter> _emptyPainter;
std::unique_ptr<SubsectionTabs> _subsectionTabs;
rpl::lifetime _subsectionTabsLifetime;
bool _canSendTexts = false;
bool _skipScrollEvent = false;
bool _synteticScrollEvent = false;

View file

@ -262,8 +262,8 @@ void DefaultElementDelegate::elementHandleViaClick(
not_null<UserData*> 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<PurchasedTag*> Element::enforcePurchasedTag() {
int Element::AdditionalSpaceForSelectionCheckbox(
not_null<const Element*> 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 {

View file

@ -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<UserData*> bot) = 0;
virtual bool elementIsChatWide() = 0;
virtual ElementChatMode elementChatMode() = 0;
virtual not_null<Ui::PathShiftGradient*> elementPathShiftGradient() = 0;
virtual void elementReplyTo(const FullReplyTo &to) = 0;
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
@ -169,7 +175,7 @@ public:
const QString &query,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override;
ElementChatMode elementChatMode() override;
void elementReplyTo(const FullReplyTo &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
void elementStartPremium(
@ -233,7 +239,7 @@ struct UnreadBar : RuntimeComponent<UnreadBar, Element> {
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<ServicePreMessage, Element> {
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,

View file

@ -1898,8 +1898,10 @@ void ListWidget::elementHandleViaClick(not_null<UserData*> 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<Ui::PathShiftGradient*> ListWidget::elementPathShiftGradient() {
@ -4284,8 +4286,8 @@ void ListWidget::setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w) {
}
}
void ListWidget::overrideIsChatWide(bool isWide) {
_overrideIsChatWide = isWide;
void ListWidget::overrideChatMode(std::optional<ElementChatMode> mode) {
_overrideChatMode = mode;
}
ListWidget::~ListWidget() {

View file

@ -428,7 +428,7 @@ public:
const QString &query,
const FullMsgId &context) override;
void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override;
ElementChatMode elementChatMode() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullReplyTo &to) override;
void elementStartInteraction(not_null<const Element*> view) override;
@ -443,7 +443,7 @@ public:
bool elementHideTopicButton(not_null<const Element*> view) override;
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
void overrideIsChatWide(bool isWide);
void overrideChatMode(std::optional<ElementChatMode> mode);
~ListWidget();
@ -834,7 +834,7 @@ private:
bool _refreshingViewer = false;
bool _showFinished = false;
bool _resizePending = false;
std::optional<bool> _overrideIsChatWide;
std::optional<ElementChatMode> _overrideChatMode;
// _menu must be destroyed before _whoReactedMenuLifetime.
rpl::lifetime _whoReactedMenuLifetime;

View file

@ -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<ServicePreMessage>()) {
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<HistoryMessageForwarded>()) {
@ -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<ServicePreMessage>()) {
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);

View file

@ -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);
}
}

View file

@ -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<Window::SessionController*> controller,
not_null<Ui::RpWidget*> parent,
not_null<Data::Thread*> 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<QWidget*> parent) {
delete base::take(_vertical);
_horizontal = Ui::CreateChild<Ui::RpWidget>(parent);
_horizontal->show();
if (!_shadow) {
_shadow = Ui::CreateChild<Ui::PlainShadow>(parent);
_shadow->show();
}
const auto toggle = Ui::CreateChild<Ui::IconButton>(
_horizontal,
st::chatTabsToggle);
toggle->show();
toggle->setClickedCallback([=] {
toggleModes();
});
toggle->move(0, 0);
const auto scroll = Ui::CreateChild<Ui::ScrollArea>(
_horizontal,
st::chatTabsScroll,
true);
scroll->show();
const auto tabs = scroll->setOwnedWidget(
object_ptr<Ui::SettingsSlider>(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<TextWithEntities>();
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<QWidget*> parent) {
delete base::take(_horizontal);
_vertical = Ui::CreateChild<Ui::RpWidget>(parent);
_vertical->show();
if (!_shadow) {
_shadow = Ui::CreateChild<Ui::PlainShadow>(parent);
_shadow->show();
}
const auto toggle = Ui::CreateChild<Ui::IconButton>(
_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<Ui::ScrollArea>(_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<Ui::RpWidget*> 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<Data::ForumTopic*> topic) {
if (_around == topic) {
_around = _history;
refreshSlice();
}
}, _lifetime);
} else if (const auto monoforum = _history->peer->monoforum()) {
monoforum->sublistDestroyed(
) | rpl::start_with_next([=](not_null<Data::SavedSublist*> 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<not_null<Data::Thread*>>();
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<Dialogs::Row*> 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<int>();
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<Data::Thread*> thread,
not_null<Ui::RpWidget*> 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<Data::Thread*> 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

View file

@ -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<Window::SessionController*> controller,
not_null<Ui::RpWidget*> parent,
not_null<Data::Thread*> thread);
~SubsectionTabs();
[[nodiscard]] bool switchTo(
not_null<Data::Thread*> thread,
not_null<Ui::RpWidget*> parent);
[[nodiscard]] static bool UsedFor(not_null<Data::Thread*> thread);
[[nodiscard]] rpl::producer<> removeRequests() const;
void extractToParent(not_null<Ui::RpWidget*> 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<QWidget*> parent);
void setupVertical(not_null<QWidget*> parent);
void toggleModes();
void setVisible(bool shown);
void refreshSlice();
const not_null<Window::SessionController*> _controller;
const not_null<History*> _history;
Ui::RpWidget *_horizontal = nullptr;
Ui::RpWidget *_vertical = nullptr;
Ui::RpWidget *_shadow = nullptr;
std::vector<not_null<Data::Thread*>> _slice;
not_null<Data::Thread*> _active;
not_null<Data::Thread*> _around;
int _beforeLimit = 0;
int _afterLimit = 0;
std::optional<int> _beforeSkipped;
std::optional<int> _afterSkipped;
rpl::event_stream<> _layoutRequests;
rpl::event_stream<> _refreshed;
rpl::lifetime _lifetime;
};
} // namespace HistoryView

View file

@ -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

View file

@ -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(

View file

@ -373,7 +373,7 @@ ShortcutMessages::ShortcutMessages(
this,
&controller->session(),
static_cast<ListDelegate*>(this));
_inner->overrideIsChatWide(false);
_inner->overrideChatMode(ElementChatMode::Default);
_scroll->sizeValue() | rpl::filter([](QSize size) {
return !size.isEmpty();

View file

@ -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;
}

View file

@ -279,6 +279,7 @@ void SectionWidget::setGeometryWithTopMoved(
void SectionWidget::showAnimated(
SlideDirection direction,
const SectionSlideParams &params) {
validateSubsectionTabs();
if (_showAnimation) {
return;
}
@ -309,6 +310,7 @@ std::shared_ptr<SectionMemento> SectionWidget::createMemento() {
}
void SectionWidget::showFast() {
validateSubsectionTabs();
show();
showFinished();
}

View file

@ -194,6 +194,9 @@ public:
return nullptr;
}
virtual void validateSubsectionTabs() {
}
static void PaintBackground(
not_null<SessionController*> controller,
not_null<Ui::ChatTheme*> theme,

View file

@ -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<ChatHelpers::Show> SessionController::uiShow() {
return _cachedShow;
}
void SessionController::saveSubsectionTabs(
std::unique_ptr<HistoryView::SubsectionTabs> tabs) {
_savedSubsectionTabsLifetime.destroy();
_savedSubsectionTabs = std::move(tabs);
_savedSubsectionTabs->extractToParent(widget());
_savedSubsectionTabs->removeRequests() | rpl::start_with_next([=] {
_savedSubsectionTabs = nullptr;
}, _savedSubsectionTabsLifetime);
}
auto SessionController::restoreSubsectionTabsFor(
not_null<Ui::RpWidget*> parent,
not_null<Data::Thread*> thread)
-> std::unique_ptr<HistoryView::SubsectionTabs> {
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(

View file

@ -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<ChatHelpers::Show> uiShow() override;
void saveSubsectionTabs(
std::unique_ptr<HistoryView::SubsectionTabs> tabs);
[[nodiscard]] auto restoreSubsectionTabsFor(
not_null<Ui::RpWidget*> parent,
not_null<Data::Thread*> thread)
-> std::unique_ptr<HistoryView::SubsectionTabs>;
void dropSubsectionTabs();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
@ -774,6 +786,8 @@ private:
base::has_weak_ptr _storyOpenGuard;
QString _premiumRef;
std::unique_ptr<HistoryView::SubsectionTabs> _savedSubsectionTabs;
rpl::lifetime _savedSubsectionTabsLifetime;
rpl::lifetime _lifetime;