/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "info/info_wrap_widget.h" #include #include #include "info/profile/info_profile_widget.h" #include "info/media/info_media_widget.h" #include "info/info_content_widget.h" #include "info/info_memento.h" #include "info/info_top_bar.h" #include "info/info_top_bar_override.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "auth_session.h" #include "lang/lang_keys.h" #include "styles/style_info.h" #include "styles/style_profile.h" namespace Info { namespace { const style::InfoTopBar &TopBarStyle(Wrap wrap) { return (wrap == Wrap::Layer) ? st::infoLayerTopBar : st::infoTopBar; } } // namespace struct WrapWidget::StackItem { std::unique_ptr section; std::unique_ptr anotherTab; }; WrapWidget::WrapWidget( QWidget *parent, not_null controller, Wrap wrap, not_null memento) : SectionWidget(parent, controller) , _wrap(wrap) , _topShadow(this) { _topShadow->toggleOn(topShadowToggledValue()); selectedListValue() | rpl::start_with_next([this](SelectedItems &&items) { InvokeQueued(this, [this, items = std::move(items)]() mutable { refreshTopBarOverride(std::move(items)); }); }, lifetime()); showNewContent(memento->content()); } not_null WrapWidget::peer() const { return _content->peer(); } Wrap WrapWidget::wrap() const { return _wrap.current(); } void WrapWidget::setWrap(Wrap wrap) { if (_wrap.current() != wrap) { _wrap = wrap; setupTop(_content->section(), _content->peer()->id); finishShowContent(); } } void WrapWidget::createTabs() { _topTabs.create(this, st::infoTabs); auto sections = QStringList(); sections.push_back(lang(lng_profile_info_section).toUpper()); sections.push_back(lang(lng_info_tab_media).toUpper()); _topTabs->setSections(sections); _topTabs->setActiveSection(static_cast(_tab)); _topTabs->finishAnimating(); _topTabs->sectionActivated() | rpl::map([](int index) { return static_cast(index); }) | rpl::start_with_next( [this](Tab tab) { showTab(tab); }, lifetime()); _topTabs->move(0, 0); _topTabs->resizeToWidth(width()); _topTabs->show(); _topTabsBackground.create(this, st::profileBg); _topTabsBackground->setAttribute(Qt::WA_OpaquePaintEvent); _topTabsBackground->move(0, 0); _topTabsBackground->resize( width(), _topTabs->height() - st::lineWidth); _topTabsBackground->show(); } void WrapWidget::forceContentRepaint() { // WA_OpaquePaintEvent on TopBar creates render glitches when // animating the LayerWidget's height :( Fixing by repainting. if (_topTabs) { _topTabsBackground->update(); } else if (_topBar) { _topBar->update(); } _content->update(); } void WrapWidget::showTab(Tab tab) { if (_tab == tab) { return; } Expects(_content != nullptr); auto direction = (tab > _tab) ? SlideDirection::FromRight : SlideDirection::FromLeft; auto newAnotherMemento = _content->createMemento(); auto newContent = _anotherTabMemento ? createContent(_anotherTabMemento.get()) : createContent(tab); auto animationParams = SectionSlideParams(); // animationParams.withFade = (wrap() == Wrap::Layer); animationParams.withTabs = true; animationParams.withTopBarShadow = hasTopBarShadow() && newContent->hasTopBarShadow(); animationParams.oldContentCache = grabForShowAnimation( animationParams); showContent(std::move(newContent)); showAnimated(direction, animationParams); _anotherTabMemento = std::move(newAnotherMemento); _tab = tab; } void WrapWidget::setupTabbedTop(const Section §ion) { switch (section.type()) { case Section::Type::Profile: setupTabs(Tab::Profile); break; case Section::Type::Media: switch (section.mediaType()) { case Section::MediaType::Photo: case Section::MediaType::Video: case Section::MediaType::File: setupTabs(Tab::Media); break; default: setupTabs(Tab::None); break; } break; case Section::Type::CommonGroups: setupTabs(Tab::None); break; } } void WrapWidget::setupTop( const Section §ion, PeerId peerId) { if (wrap() == Wrap::Side && _historyStack.empty()) { setupTabbedTop(section); } else { setupTabs(Tab::None); } if (_topTabs) { _topBar.destroy(); } else { createTopBar(section, peerId); } refreshTopBarOverride(); } void WrapWidget::createTopBar( const Section §ion, PeerId peerId) { _topBar.create(this, TopBarStyle(wrap())); _topBar->setTitle(TitleValue( section, peerId)); if (wrap() != Wrap::Layer || !_historyStack.empty()) { _topBar->enableBackButton(true); _topBar->backRequest() | rpl::start_with_next([this] { showBackFromStack(); }, _topBar->lifetime()); } if (wrap() == Wrap::Layer) { auto close = _topBar->addButton(object_ptr( _topBar, st::infoLayerTopBarClose)); close->addClickHandler([this] { controller()->hideSpecialLayer(); }); } _topBar->move(0, 0); _topBar->resizeToWidth(width()); _topBar->show(); } void WrapWidget::refreshTopBarOverride(SelectedItems &&items) { if (items.list.empty()) { destroyTopBarOverride(); } else if (_topBarOverride) { _topBarOverride->setItems(std::move(items)); } else { createTopBarOverride(std::move(items)); } } void WrapWidget::refreshTopBarOverride() { if (_topBarOverride) { auto items = _topBarOverride->takeItems(); destroyTopBarOverride(); createTopBarOverride(std::move(items)); } } void WrapWidget::destroyTopBarOverride() { if (!_topBarOverride) { return; } auto widget = std::exchange(_topBarOverride, nullptr); auto handle = weak(widget.data()); _topBarOverrideAnimation.start([this, handle] { }, 1., 0., st::slideWrapDuration); widget.destroy(); if (_topTabs) { _topTabs->show(); } else if (_topBar) { _topBar->show(); } } void WrapWidget::createTopBarOverride(SelectedItems &&items) { Expects(_topBarOverride == nullptr); _topBarOverride.create( this, TopBarStyle(wrap()), std::move(items)); if (_topTabs) { _topTabs->hide(); } else if (_topBar) { _topBar->hide(); } _topBarOverride->cancelRequests() | rpl::start_with_next([this](auto) { _content->cancelSelection(); }, _topBarOverride->lifetime()); _topBarOverride->moveToLeft(0, 0); _topBarOverride->resizeToWidth(width()); _topBarOverride->show(); } void WrapWidget::showBackFromStack() { auto params = Window::SectionShow( Window::SectionShow::Way::Backward); if (!_historyStack.empty()) { auto last = std::move(_historyStack.back()); _historyStack.pop_back(); showNewContent( last.section.get(), params); _anotherTabMemento = std::move(last.anotherTab); } else { controller()->showBackFromStack(params); } } not_null WrapWidget::topWidget() const { if (_topTabs) { return _topTabsBackground; } return _topBar; } void WrapWidget::showContent(object_ptr content) { _content = std::move(content); _content->show(); _anotherTabMemento = nullptr; finishShowContent(); } void WrapWidget::finishShowContent() { _content->setIsStackBottom(_historyStack.empty()); updateContentGeometry(); _desiredHeights.fire(desiredHeightForContent()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); _selectedLists.fire(_content->selectedListValue()); _topShadow->raise(); _topShadow->finishAnimating(); if (_topTabs) { _topTabs->raise(); } } rpl::producer WrapWidget::topShadowToggledValue() const { using namespace rpl::mappers; return rpl::combine( _wrap.value(), _desiredShadowVisibilities.events() | rpl::flatten_latest(), ($1 == Wrap::Side) || $2); } rpl::producer WrapWidget::desiredHeightForContent() const { using namespace rpl::mappers; return rpl::combine( _content->desiredHeightValue(), topWidget()->heightValue(), $1 + $2); } rpl::producer WrapWidget::selectedListValue() const { return _selectedLists.events() | rpl::flatten_latest(); } object_ptr WrapWidget::createContent(Tab tab) { switch (tab) { case Tab::Profile: return createProfileWidget(); case Tab::Media: return createMediaWidget(); } Unexpected("Tab value in Info::WrapWidget::createInner()"); } object_ptr WrapWidget::createProfileWidget() { auto result = object_ptr( this, _wrap.value(), controller(), _content->peer()); return result; } object_ptr WrapWidget::createMediaWidget() { auto result = object_ptr( this, _wrap.value(), controller(), _content->peer(), Media::Widget::Type::Photo); return result; } object_ptr WrapWidget::createContent( not_null memento) { return memento->createWidget( this, _wrap.value(), controller(), contentGeometry()); } bool WrapWidget::hasTopBarShadow() const { return _topShadow->toggled(); } QPixmap WrapWidget::grabForShowAnimation( const Window::SectionSlideParams ¶ms) { if (params.withTopBarShadow) { _topShadow->setVisible(false); } else { _topShadow->toggle(_topShadow->toggled(), anim::type::instant); } if (params.withTabs && _topTabs) { _topTabs->hide(); } auto result = myGrab(this); if (params.withTopBarShadow) { _topShadow->setVisible(true); } if (params.withTabs && _topTabs) { _topTabs->show(); } return result; } void WrapWidget::showAnimatedHook( const Window::SectionSlideParams ¶ms) { if (params.withTabs && _topTabs) { _topTabs->show(); _topTabsBackground->show(); } } void WrapWidget::doSetInnerFocus() { _content->setInnerFocus(); } void WrapWidget::showFinishedHook() { // Restore shadow visibility after showChildren() call. _topShadow->toggle(_topShadow->toggled(), anim::type::instant); } bool WrapWidget::showInternal( not_null memento, const Window::SectionShow ¶ms) { if (auto infoMemento = dynamic_cast(memento.get())) { auto content = infoMemento->content(); if (!_content->showInternal(content)) { showNewContent( content, params); } return true; } return false; } std::unique_ptr WrapWidget::createMemento() { auto result = std::make_unique(_content->peer()->id); saveState(result.get()); return std::move(result); } rpl::producer WrapWidget::desiredHeightValue() const { return rpl::single(desiredHeightForContent()) | rpl::then(_desiredHeights.events()) | rpl::flatten_latest(); } void WrapWidget::saveState(not_null memento) { memento->setInner(_content->createMemento()); } QRect WrapWidget::contentGeometry() const { return rect().marginsRemoved({ 0, topWidget()->height(), 0, 0 }); } void WrapWidget::showNewContent( not_null memento, const Window::SectionShow ¶ms) { auto saveToStack = (_content != nullptr) && (params.way == Window::SectionShow::Way::Forward); auto needAnimation = (_content != nullptr) && (params.animated != anim::type::instant); auto animationParams = SectionSlideParams(); auto newContent = object_ptr(nullptr); if (needAnimation) { newContent = createContent(memento); animationParams.withTopBarShadow = hasTopBarShadow() && newContent->hasTopBarShadow(); animationParams.oldContentCache = grabForShowAnimation( animationParams); // animationParams.withFade = (wrap() == Wrap::Layer); } if (saveToStack) { auto item = StackItem(); item.section = _content->createMemento(); if (_anotherTabMemento) { item.anotherTab = std::move(_anotherTabMemento); } _historyStack.push_back(std::move(item)); } else if (params.way == Window::SectionShow::Way::ClearStack) { _historyStack.clear(); } if (newContent) { setupTop(newContent->section(), newContent->peer()->id); showContent(std::move(newContent)); } else { showNewContent(memento); } if (animationParams) { showAnimated( saveToStack ? SlideDirection::FromRight : SlideDirection::FromLeft, animationParams); } } void WrapWidget::showNewContent(not_null memento) { // Validates contentGeometry(). setupTop(memento->section(), memento->peerId()); showContent(createContent(memento)); } void WrapWidget::setupTabs(Tab tab) { _tab = tab; if (_tab == Tab::None) { _topTabs.destroy(); _topTabsBackground.destroy(); } else if (!_topTabs) { createTabs(); } else { _topTabs->setActiveSection(static_cast(tab)); } } void WrapWidget::resizeEvent(QResizeEvent *e) { if (_topTabs) { _topTabs->resizeToWidth(width()); _topTabsBackground->resize( width(), _topTabs->height() - st::lineWidth); } else if (_topBar) { _topBar->resizeToWidth(width()); } if (_topBarOverride) { _topBarOverride->resizeToWidth(width()); } updateContentGeometry(); } void WrapWidget::updateContentGeometry() { if (_content) { _topShadow->resizeToWidth(width()); _topShadow->moveToLeft(0, topWidget()->height()); _content->setGeometry(contentGeometry()); } } bool WrapWidget::wheelEventFromFloatPlayer(QEvent *e) { return _content->wheelEventFromFloatPlayer(e); } QRect WrapWidget::rectForFloatPlayer() const { return _content->rectForFloatPlayer(); } WrapWidget::~WrapWidget() = default; } // namespace Info