diff --git a/Telegram/Resources/icons/chat/menu_sidebar1.svg b/Telegram/Resources/icons/chat/menu_sidebar1.svg new file mode 100644 index 0000000000..ae5d1b0c45 --- /dev/null +++ b/Telegram/Resources/icons/chat/menu_sidebar1.svg @@ -0,0 +1,7 @@ + + + General / menu_sidebar1 + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/menu_sidebar2.svg b/Telegram/Resources/icons/chat/menu_sidebar2.svg new file mode 100644 index 0000000000..177ce3e81d --- /dev/null +++ b/Telegram/Resources/icons/chat/menu_sidebar2.svg @@ -0,0 +1,7 @@ + + + General / menu_sidebar2 + + + + \ No newline at end of file diff --git a/Telegram/Resources/icons/chat/menu_sidebar3.svg b/Telegram/Resources/icons/chat/menu_sidebar3.svg new file mode 100644 index 0000000000..7d47fa7682 --- /dev/null +++ b/Telegram/Resources/icons/chat/menu_sidebar3.svg @@ -0,0 +1,7 @@ + + + General / menu_sidebar3 + + + + \ No newline at end of file diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 429828155c..e52226ce51 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7034,7 +7034,8 @@ void HistoryWidget::updateHistoryGeometry( const auto subsectionTabsTop = _topBar->bottomNoMargins(); auto newScrollHeight = height() - subsectionTabsTop - - (_subsectionTabs ? _subsectionTabs->topSkip() : 0); + - (_subsectionTabs ? _subsectionTabs->topSkip() : 0) + - (_subsectionTabs ? _subsectionTabs->bottomSkip() : 0); if (_translateBar) { newScrollHeight -= _translateBar->height(); } @@ -7113,8 +7114,11 @@ void HistoryWidget::updateHistoryGeometry( controller()->floatPlayerAreaUpdated(); } if (_subsectionTabs) { + const auto tabsBottomSkip = _subsectionTabs->bottomSkip(); const auto scrollBottom = _scroll->y() + newScrollHeight; - const auto areaHeight = scrollBottom - subsectionTabsTop; + const auto areaHeight = scrollBottom + + tabsBottomSkip + - subsectionTabsTop; _subsectionTabs->setBoundingRect( { 0, subsectionTabsTop, width(), areaHeight }); } diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index aa446c58d8..1d301c1504 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -2645,6 +2645,9 @@ void ChatWidget::updateControlsGeometry() { const auto tabsLeftSkip = _subsectionTabs ? _subsectionTabs->leftSkip() : 0; + const auto tabsBottomSkip = _subsectionTabs + ? _subsectionTabs->bottomSkip() + : 0; const auto innerWidth = contentWidth - tabsLeftSkip; const auto subsectionTabsTop = _topBar->bottomNoMargins(); _topBars->move(tabsLeftSkip, subsectionTabsTop @@ -2682,6 +2685,8 @@ void ChatWidget::updateControlsGeometry() { } else { bottom -= _composeControls->heightCurrent(); } + const auto composeTop = bottom; + bottom -= tabsBottomSkip; _topBars->resize(innerWidth, top + st::lineWidth); top += _topBars->y(); @@ -2704,12 +2709,14 @@ void ChatWidget::updateControlsGeometry() { } updateInnerVisibleArea(); } - _composeControls->move(0, bottom); + _composeControls->move(0, composeTop); _composeControls->setAutocompleteBoundingRect(_scroll->geometry()); if (_subsectionTabs) { const auto scrollBottom = _scroll->y() + scrollHeight; - const auto areaHeight = scrollBottom - subsectionTabsTop; + const auto areaHeight = scrollBottom + + tabsBottomSkip + - subsectionTabsTop; _subsectionTabs->setBoundingRect( { 0, subsectionTabsTop, width(), areaHeight }); } diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp index 5c4049307a..3d48814554 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -80,22 +80,31 @@ SubsectionTabs::SubsectionTabs( SubsectionTabs::~SubsectionTabs() { delete base::take(_horizontal); delete base::take(_vertical); + delete base::take(_bottom); delete base::take(_shadow); } void SubsectionTabs::setup(not_null parent) { const auto peerId = _history->peer->id; - if (session().settings().verticalSubsectionTabs(peerId)) { + const auto mode = session().settings().subsectionTabsMode(peerId); + if (mode == qint32(SubsectionTabsMode::Left)) { setupVertical(parent); + } else if (mode == qint32(SubsectionTabsMode::Bottom)) { + setupHorizontal(parent, true); } else { - setupHorizontal(parent); + setupHorizontal(parent, false); } } -void SubsectionTabs::setupHorizontal(not_null parent) { +void SubsectionTabs::setupHorizontal( + not_null parent, + bool bottom) { delete base::take(_vertical); - _horizontal = Ui::CreateChild(parent); - _horizontal->show(); + delete base::take(bottom ? _horizontal : _bottom); + auto &widgetRef = bottom ? _bottom : _horizontal; + widgetRef = Ui::CreateChild(parent); + widgetRef->show(); + const auto widget = widgetRef; if (!_shadow) { _shadow = Ui::CreateChild(parent); @@ -105,19 +114,24 @@ void SubsectionTabs::setupHorizontal(not_null parent) { } const auto toggle = Ui::CreateChild( - _horizontal, + widget, st::chatTabsToggle); toggle->show(); + toggle->setIconOverride( + bottom ? &st::chatTabsToggleIconBottom : &st::chatTabsToggleIconTop, + (bottom + ? &st::chatTabsToggleIconBottomOver + : &st::chatTabsToggleIconTopOver)); toggle->setClickedCallback([=] { toggleModes(); }); toggle->move(0, 0); const auto scroll = Ui::CreateChild( - _horizontal, + widget, st::chatTabsScroll, true); scroll->show(); - const auto shadow = Ui::CreateChild(_horizontal); + const auto shadow = Ui::CreateChild(widget); const auto slider = scroll->setOwnedWidget( object_ptr(scroll)); _reorder = std::make_unique(slider, scroll); @@ -130,8 +144,8 @@ void SubsectionTabs::setupHorizontal(not_null parent) { ) | rpl::map([=] { return scroll->scrollLeft() > 0; })); shadow->setAttribute(Qt::WA_TransparentForMouseEvents); - _horizontal->resize( - _horizontal->width(), + widget->resize( + widget->width(), std::max(toggle->height(), slider->height())); scroll->setCustomWheelProcess([=](not_null e) { @@ -145,7 +159,7 @@ void SubsectionTabs::setupHorizontal(not_null parent) { return true; }); - _horizontal->sizeValue( + widget->sizeValue( ) | rpl::on_next([=](QSize size) { const auto togglew = toggle->width(); const auto height = size.height(); @@ -153,13 +167,26 @@ void SubsectionTabs::setupHorizontal(not_null parent) { shadow->setGeometry(togglew, 0, st::lineWidth, height); }, scroll->lifetime()); - _horizontal->paintRequest() | rpl::on_next([=](QRect clip) { - QPainter(_horizontal).fillRect( + widget->paintRequest() | rpl::on_next([=](QRect clip) { + auto p = QPainter(widget); + + const auto line = st::lineWidth; + p.fillRect( clip.intersected( - _horizontal->rect().marginsRemoved( - { 0, 0, 0, st::lineWidth })), + widget->rect().marginsRemoved( + { 0, bottom ? line : 0, 0, bottom ? 0 : line })), st::windowBg); - }, _horizontal->lifetime()); + if (bottom) { + const auto shadow = QRect( + 0, + widget->height() - line, + widget->width(), + line); + if (clip.intersects(shadow)) { + p.fillRect(clip.intersected(shadow), st::shadowFg); + } + } + }, widget->lifetime()); _layoutRequests.fire({}); @@ -168,6 +195,7 @@ void SubsectionTabs::setupHorizontal(not_null parent) { void SubsectionTabs::setupVertical(not_null parent) { delete base::take(_horizontal); + delete base::take(_bottom); _vertical = Ui::CreateChild(parent); _vertical->show(); @@ -180,8 +208,9 @@ void SubsectionTabs::setupVertical(not_null parent) { _vertical, st::chatTabsToggle); toggle->show(); - const auto active = &st::chatTabsToggleActive; - toggle->setIconOverride(active, active); + toggle->setIconOverride( + &st::chatTabsToggleIconLeft, + &st::chatTabsToggleIconLeftOver); toggle->setClickedCallback([=] { toggleModes(); }); @@ -566,7 +595,7 @@ void SubsectionTabs::startFillingSlider( void SubsectionTabs::showThreadContextMenu(not_null thread) { _menu = nullptr; _menu = base::make_unique_q( - _horizontal ? _horizontal : _vertical, + activeWidget(), st::popupMenuExpandedSeparator); const auto addAction = Ui::Menu::CreateAddActionCallback(_menu); @@ -605,17 +634,27 @@ rpl::producer<> SubsectionTabs::dataChanged() const { } void SubsectionTabs::toggleModes() { - Expects((_horizontal || _vertical) && _shadow); + Expects((_horizontal || _vertical || _bottom) && _shadow); const auto peerId = _history->peer->id; - const auto nowVertical = (_horizontal != nullptr); - session().settings().setVerticalSubsectionTabs(peerId, nowVertical); + const auto parent = activeWidget()->parentWidget(); + const auto current = session().settings().subsectionTabsMode(peerId); + const auto next = (current == qint32(SubsectionTabsMode::Top)) + ? SubsectionTabsMode::Bottom + : (current == qint32(SubsectionTabsMode::Bottom)) + ? SubsectionTabsMode::Left + : SubsectionTabsMode::Top; + session().settings().setSubsectionTabsMode( + peerId, + qint32(next)); session().saveSettingsDelayed(); - if (_horizontal) { - setupVertical(_horizontal->parentWidget()); + if (next == SubsectionTabsMode::Left) { + setupVertical(parent); + } else if (next == SubsectionTabsMode::Bottom) { + setupHorizontal(parent, true); } else { - setupHorizontal(_vertical->parentWidget()); + setupHorizontal(parent, false); } } @@ -634,21 +673,17 @@ rpl::producer<> SubsectionTabs::removeRequests() const { } void SubsectionTabs::extractToParent(not_null parent) { - Expects((_horizontal || _vertical) && _shadow); + Expects((_horizontal || _vertical || _bottom) && _shadow); - if (_vertical) { - _vertical->hide(); - _vertical->setParent(parent); - } else { - _horizontal->hide(); - _horizontal->setParent(parent); - } + const auto widget = activeWidget(); + widget->hide(); + widget->setParent(parent); _shadow->hide(); _shadow->setParent(parent); } void SubsectionTabs::setBoundingRect(QRect boundingRect) { - Expects((_horizontal || _vertical) && _shadow); + Expects((_horizontal || _vertical || _bottom) && _shadow); if (_horizontal) { _horizontal->setGeometry( @@ -661,7 +696,7 @@ void SubsectionTabs::setBoundingRect(QRect boundingRect) { _horizontal->y() + _horizontal->height() - st::lineWidth, boundingRect.width(), st::lineWidth); - } else { + } else if (_vertical) { _vertical->setGeometry( boundingRect.x(), boundingRect.y(), @@ -672,6 +707,18 @@ void SubsectionTabs::setBoundingRect(QRect boundingRect) { boundingRect.y(), st::lineWidth, boundingRect.height()); + } else { + _bottom->setGeometry( + boundingRect.x(), + boundingRect.y() + boundingRect.height() + - _bottom->height(), + boundingRect.width(), + _bottom->height()); + _shadow->setGeometry( + boundingRect.x(), + _bottom->y(), + boundingRect.width(), + st::lineWidth); } } @@ -687,14 +734,14 @@ int SubsectionTabs::topSkip() const { return _horizontal ? (_horizontal->height() - st::lineWidth) : 0; } -void SubsectionTabs::raise() { - Expects((_horizontal || _vertical) && _shadow); +int SubsectionTabs::bottomSkip() const { + return _bottom ? (_bottom->height() - st::lineWidth) : 0; +} - if (_horizontal) { - _horizontal->raise(); - } else { - _vertical->raise(); - } +void SubsectionTabs::raise() { + Expects((_horizontal || _vertical || _bottom) && _shadow); + + activeWidget()->raise(); _shadow->raise(); } @@ -707,13 +754,9 @@ void SubsectionTabs::hide() { } void SubsectionTabs::setVisible(bool shown) { - Expects((_horizontal || _vertical) && _shadow); + Expects((_horizontal || _vertical || _bottom) && _shadow); - if (_horizontal) { - _horizontal->setVisible(shown); - } else { - _vertical->setVisible(shown); - } + activeWidget()->setVisible(shown); _shadow->setVisible(shown); } @@ -829,7 +872,7 @@ void SubsectionTabs::refreshSlice() { if (_slice != slice) { _slice = std::move(slice); _refreshed.fire({}); - Assert((!_horizontal && !_vertical) + Assert((!_horizontal && !_vertical && !_bottom) || (_slice.size() == _sectionsSlice.size())); } }); @@ -908,22 +951,22 @@ Main::Session &SubsectionTabs::session() { return _history->session(); } +Ui::RpWidget *SubsectionTabs::activeWidget() const { + return _horizontal ? _horizontal : _vertical ? _vertical : _bottom; +} + bool SubsectionTabs::switchTo( not_null thread, not_null parent) { - Expects((_horizontal || _vertical) && _shadow); + Expects((_horizontal || _vertical || _bottom) && _shadow); if (thread->owningHistory() != _history) { return false; } _active = thread; - if (_vertical) { - _vertical->setParent(parent); - _vertical->show(); - } else { - _horizontal->setParent(parent); - _horizontal->show(); - } + const auto widget = activeWidget(); + widget->setParent(parent); + widget->show(); _shadow->setParent(parent); _shadow->show(); _refreshed.fire({}); diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h index 01be891f4b..987b9ceb26 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.h @@ -34,6 +34,12 @@ class SubsectionSliderReorder; namespace HistoryView { +enum class SubsectionTabsMode : qint32 { + Top = 0, + Left = 1, + Bottom = 2, +}; + class SubsectionTabs final { public: SubsectionTabs( @@ -59,6 +65,7 @@ public: [[nodiscard]] rpl::producer<> layoutRequests() const; [[nodiscard]] int leftSkip() const; [[nodiscard]] int topSkip() const; + [[nodiscard]] int bottomSkip() const; void raise(); void show(); @@ -80,7 +87,7 @@ private: }; void track(); - void setupHorizontal(not_null parent); + void setupHorizontal(not_null parent, bool bottom); void setupVertical(not_null parent); void toggleModes(); void setVisible(bool shown); @@ -92,6 +99,7 @@ private: void loadMore(); void setup(not_null parent); [[nodiscard]] rpl::producer<> dataChanged() const; + [[nodiscard]] Ui::RpWidget *activeWidget() const; void setupSlider( not_null scroll, @@ -118,6 +126,7 @@ private: Ui::RpWidget *_horizontal = nullptr; Ui::RpWidget *_vertical = nullptr; + Ui::RpWidget *_bottom = nullptr; Ui::RpWidget *_shadow = nullptr; std::unique_ptr _reorder; int _reordering = 0; diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 9e1b1cb7bc..18ad7cb0f7 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -46,7 +46,6 @@ QByteArray SessionSettings::serialize() const { + sizeof(qint32) * 3 + _hiddenPinnedMessages.size() * (sizeof(quint64) * 4) + sizeof(qint32) - + _verticalSubsectionTabs.size() * sizeof(quint64) + sizeof(qint32) // _ringtoneDefaultVolumes size + (_ringtoneDefaultVolumes.size() * (0 @@ -69,6 +68,8 @@ QByteArray SessionSettings::serialize() const { size += sizeof(qint32) // _moderateCommonGroups size + (_moderateCommonGroups.size() * sizeof(qint32)); size += sizeof(qint32); + size += sizeof(qint32) + + _subsectionTabsModes.size() * (sizeof(quint64) + sizeof(qint32)); auto result = QByteArray(); result.reserve(size); @@ -120,10 +121,7 @@ QByteArray SessionSettings::serialize() const { << SerializePeerId(key.monoforumPeerId) << qint64(value.bare); } - stream << qint32(_verticalSubsectionTabs.size()); - for (const auto &peerId : _verticalSubsectionTabs) { - stream << SerializePeerId(peerId); - } + stream << qint32(0); stream << qint32(_ringtoneDefaultVolumes.size()); for (const auto &[key, value] : _ringtoneDefaultVolumes) { stream << uint8_t(key) << ushort(value); @@ -155,6 +153,10 @@ QByteArray SessionSettings::serialize() const { stream << qint32(filterId); } stream << qint32(_disableSharingBoxShowsCount); + stream << qint32(_subsectionTabsModes.size()); + for (const auto &[peerId, mode] : _subsectionTabsModes) { + stream << SerializePeerId(peerId) << qint32(mode); + } } Ensures(result.size() == size); @@ -214,7 +216,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { std::vector appDictionariesEnabled; qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0; base::flat_map hiddenPinnedMessages; - base::flat_set verticalSubsectionTabs; + base::flat_map subsectionTabsModes; qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0; qint32 supportAllSilent = _supportAllSilent ? 1 : 0; qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount; @@ -547,7 +549,8 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { "Bad data for SessionSettings::addFromSerialized()")); return; } - verticalSubsectionTabs.emplace(DeserializePeerId(peerId)); + subsectionTabsModes.emplace( + DeserializePeerId(peerId), qint32(1)); } } } @@ -665,6 +668,24 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> disableSharingBoxShowsCount; } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + auto peerId = quint64(); + auto mode = qint32(0); + stream >> peerId >> mode; + if (stream.status() != QDataStream::Ok) { + LOG(("App Error: " + "Bad data for SessionSettings::addFromSerialized()")); + return; + } + subsectionTabsModes.emplace( + DeserializePeerId(peerId), mode); + } + } + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -711,7 +732,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { _mutePeriods = std::move(mutePeriods); _lastNonPremiumLimitDownload = lastNonPremiumLimitDownload; _lastNonPremiumLimitUpload = lastNonPremiumLimitUpload; - _verticalSubsectionTabs = std::move(verticalSubsectionTabs); + _subsectionTabsModes = std::move(subsectionTabsModes); _ringtoneDefaultVolumes = std::move(ringtoneDefaultVolumes); _ringtoneVolumes = std::move(ringtoneVolumes); _ratedTranscriptions = std::move(ratedTranscriptions); @@ -864,17 +885,18 @@ void SessionSettings::setHiddenPinnedMessageId( } } -bool SessionSettings::verticalSubsectionTabs(PeerId peerId) const { - return _verticalSubsectionTabs.contains(peerId); +qint32 SessionSettings::subsectionTabsMode(PeerId peerId) const { + const auto i = _subsectionTabsModes.find(peerId); + return (i != end(_subsectionTabsModes)) ? i->second : 0; } -void SessionSettings::setVerticalSubsectionTabs( +void SessionSettings::setSubsectionTabsMode( PeerId peerId, - bool vertical) { - if (vertical) { - _verticalSubsectionTabs.emplace(peerId); + qint32 mode) { + if (mode) { + _subsectionTabsModes[peerId] = mode; } else { - _verticalSubsectionTabs.remove(peerId); + _subsectionTabsModes.remove(peerId); } } diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index 34d13ccb2c..750c149a4d 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -124,8 +124,8 @@ public: PeerId monoforumPeerId, MsgId msgId); - [[nodiscard]] bool verticalSubsectionTabs(PeerId peerId) const; - void setVerticalSubsectionTabs(PeerId peerId, bool vertical); + [[nodiscard]] qint32 subsectionTabsMode(PeerId peerId) const; + void setSubsectionTabsMode(PeerId peerId, qint32 mode); [[nodiscard]] bool dialogsFiltersEnabled() const { return _dialogsFiltersEnabled; @@ -211,7 +211,7 @@ private: rpl::variable _archiveInMainMenu = false; rpl::variable _skipArchiveInSearch = false; base::flat_map _hiddenPinnedMessages; - base::flat_set _verticalSubsectionTabs; + base::flat_map _subsectionTabsModes; base::flat_map _ringtoneDefaultVolumes; base::flat_map _ringtoneVolumes; bool _dialogsFiltersEnabled = false; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index f7116837c7..d3228885a3 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1376,7 +1376,12 @@ chatTabsToggle: IconButton(defaultIconButton) { iconOver: icon {{ "top_bar_profile-flip_horizontal", menuIconFgOver }}; ripple: emptyRippleAnimation; } -chatTabsToggleActive: icon {{ "top_bar_profile-flip_horizontal", windowActiveTextFg }}; +chatTabsToggleIconTop: icon {{ "chat/menu_sidebar2-24x24", menuIconFg }}; +chatTabsToggleIconTopOver: icon {{ "chat/menu_sidebar2-24x24", menuIconFgOver }}; +chatTabsToggleIconLeft: icon {{ "chat/menu_sidebar3-24x24", menuIconFg }}; +chatTabsToggleIconLeftOver: icon {{ "chat/menu_sidebar3-24x24", menuIconFgOver }}; +chatTabsToggleIconBottom: icon {{ "chat/menu_sidebar1-24x24", menuIconFg }}; +chatTabsToggleIconBottomOver: icon {{ "chat/menu_sidebar1-24x24", menuIconFgOver }}; chatTabsScroll: ScrollArea(defaultScrollArea) { barHidden: true; }