/* 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 "info/info_wrap_widget.h" #include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_values.h" #include "info/media/info_media_widget.h" #include "info/info_content_widget.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "info/info_top_bar.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/dropdown_menu.h" #include "ui/wrap/fade_wrap.h" #include "ui/search_field_controller.h" #include "calls/calls_instance.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "window/window_peer_menu.h" #include "boxes/peer_list_box.h" #include "auth_session.h" #include "data/data_session.h" #include "mainwidget.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 window, Wrap wrap, not_null memento) : SectionWidget(parent, window) , _wrap(wrap) , _controller(createController(window, memento->content())) , _topShadow(this) { _topShadow->toggleOn( topShadowToggledValue( ) | rpl::filter([](bool shown) { return true; })); _wrap.changes( ) | rpl::start_with_next([this] { setupTop(); finishShowContent(); }, lifetime()); selectedListValue( ) | rpl::start_with_next([this](SelectedItems &&items) { InvokeQueued(this, [this, items = std::move(items)]() mutable { if (_topBar) _topBar->setSelectedItems(std::move(items)); }); }, lifetime()); restoreHistoryStack(memento->takeStack()); } void WrapWidget::restoreHistoryStack( std::vector> stack) { Expects(!stack.empty()); Expects(!hasStackHistory()); auto content = std::move(stack.back()); stack.pop_back(); if (!stack.empty()) { _historyStack.reserve(stack.size()); for (auto &stackItem : stack) { auto item = StackItem(); item.section = std::move(stackItem); _historyStack.push_back(std::move(item)); } } startInjectingActivePeerProfiles(); showNewContent(content.get()); } void WrapWidget::startInjectingActivePeerProfiles() { using namespace rpl::mappers; rpl::combine( _wrap.value(), _controller->parentController()->activeChatValue() ) | rpl::filter( (_1 == Wrap::Side) && _2 ) | rpl::map( _2 ) | rpl::start_with_next([this](Dialogs::Key key) { injectActiveProfile(key); }, lifetime()); } void WrapWidget::injectActiveProfile(Dialogs::Key key) { if (const auto peer = key.peer()) { injectActivePeerProfile(peer); } else if (const auto feed = key.feed()) { injectActiveFeedProfile(feed); } } void WrapWidget::injectActivePeerProfile(not_null peer) { const auto firstPeerId = hasStackHistory() ? _historyStack.front().section->peerId() : _controller->peerId(); const auto firstSectionType = hasStackHistory() ? _historyStack.front().section->section().type() : _controller->section().type(); const auto firstSectionMediaType = [&] { if (firstSectionType == Section::Type::Profile) { return Section::MediaType::kCount; } return hasStackHistory() ? _historyStack.front().section->section().mediaType() : _controller->section().mediaType(); }(); const auto expectedType = peer->isSelf() ? Section::Type::Media : Section::Type::Profile; const auto expectedMediaType = peer->isSelf() ? Section::MediaType::Photo : Section::MediaType::kCount; if (firstSectionType != expectedType || firstSectionMediaType != expectedMediaType || firstPeerId != peer->id) { auto section = peer->isSelf() ? Section(Section::MediaType::Photo) : Section(Section::Type::Profile); injectActiveProfileMemento(std::move( Memento(peer->id, section).takeStack().front())); } } void WrapWidget::injectActiveFeedProfile(not_null feed) { const auto firstFeed = hasStackHistory() ? _historyStack.front().section->feed() : _controller->feed(); const auto firstSectionType = hasStackHistory() ? _historyStack.front().section->section().type() : _controller->section().type(); const auto expectedType = Section::Type::Profile; if (firstSectionType != expectedType || firstFeed != feed) { auto section = Section(Section::Type::Profile); injectActiveProfileMemento(std::move( Memento(feed, section).takeStack().front())); } } void WrapWidget::injectActiveProfileMemento( std::unique_ptr memento) { auto injected = StackItem(); injected.section = std::move(memento); _historyStack.insert( _historyStack.begin(), std::move(injected)); if (_content) { setupTop(); finishShowContent(); } } std::unique_ptr WrapWidget::createController( not_null window, not_null memento) { auto result = std::make_unique( this, window, memento); return result; } Key WrapWidget::key() const { return _controller->key(); } Dialogs::RowDescriptor WrapWidget::activeChat() const { if (const auto peer = key().peer()) { return Dialogs::RowDescriptor(App::history(peer), FullMsgId()); } else if (const auto feed = key().feed()) { return Dialogs::RowDescriptor(feed, FullMsgId()); } else if (key().settingsSelf()) { return Dialogs::RowDescriptor(); } Unexpected("Owner in WrapWidget::activeChat()."); } // This was done for tabs support. // //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. // This was done for tabs support. // //if (_topTabs) { // _topTabsBackground->update(); //} if (_topBar) { _topBar->update(); } _content->update(); } // This was done for tabs support. // //void WrapWidget::showTab(Tab tab) { // if (_tab == tab) { // return; // } // Expects(_content != nullptr); // auto direction = (tab > _tab) // ? SlideDirection::FromRight // : SlideDirection::FromLeft; // auto newAnotherMemento = _content->createMemento(); // if (!_anotherTabMemento) { // _anotherTabMemento = createTabMemento(tab); // } // auto newController = createController( // _controller->parentController(), // _anotherTabMemento.get()); // auto newContent = createContent( // _anotherTabMemento.get(), // newController.get()); // auto animationParams = SectionSlideParams(); //// animationParams.withFade = (wrap() == Wrap::Layer); // animationParams.withTabs = true; // animationParams.withTopBarShadow = hasTopBarShadow() // && newContent->hasTopBarShadow(); // animationParams.oldContentCache = grabForShowAnimation( // animationParams); // // _controller = std::move(newController); // showContent(std::move(newContent)); // // showAnimated(direction, animationParams); // // _anotherTabMemento = std::move(newAnotherMemento); // _tab = tab; //} // //void WrapWidget::setupTabbedTop() { // auto section = _controller->section(); // 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: // case Section::Type::Members: // setupTabs(Tab::None); // break; // } //} void WrapWidget::setupTop() { // This was done for tabs support. // //if (wrap() == Wrap::Side && !hasStackHistory()) { // setupTabbedTop(); //} else { // setupTabs(Tab::None); //} //if (_topTabs) { // _topBar.destroy(); //} else { // createTopBar(); //} createTopBar(); } void WrapWidget::createTopBar() { const auto wrapValue = wrap(); auto selectedItems = _topBar ? _topBar->takeSelectedItems() : SelectedItems(Section::MediaType::kCount); _topBar.create(this, TopBarStyle(wrapValue), std::move(selectedItems)); _topBar->cancelSelectionRequests( ) | rpl::start_with_next([this] { _content->cancelSelection(); }, _topBar->lifetime()); _topBar->setTitle(TitleValue( _controller->section(), _controller->key(), !hasStackHistory())); if (wrapValue == Wrap::Narrow || hasStackHistory()) { _topBar->enableBackButton(); _topBar->backRequest( ) | rpl::start_with_next([this] { _controller->showBackFromStack(); }, _topBar->lifetime()); } else if (wrapValue == Wrap::Side) { auto close = _topBar->addButton( base::make_unique_q( _topBar, st::infoTopBarClose)); close->addClickHandler([this] { _controller->parentController()->closeThirdSection(); }); } if (wrapValue == Wrap::Layer) { auto close = _topBar->addButton( base::make_unique_q( _topBar, st::infoLayerTopBarClose)); close->addClickHandler([this] { _controller->parentController()->hideSpecialLayer(); }); } else if (requireTopBarSearch()) { auto search = _controller->searchFieldController(); Assert(search != nullptr); _topBar->createSearchView( search, _controller->searchEnabledByContent(), _controller->takeSearchStartsFocused()); } const auto section = _controller->section(); if (section.type() == Section::Type::Profile && (wrapValue != Wrap::Side || hasStackHistory())) { addTopBarMenuButton(); addProfileCallsButton(); // addProfileNotificationsButton(); } else if (section.type() == Section::Type::Settings && section.settingsType() == Section::SettingsType::Main) { addTopBarMenuButton(); } else if (section.type() == Section::Type::Settings && section.settingsType() == Section::SettingsType::Information) { addContentSaveButton(); } _topBar->lower(); _topBar->resizeToWidth(width()); _topBar->finishAnimating(); _topBar->show(); } void WrapWidget::addTopBarMenuButton() { Expects(_topBar != nullptr); _topBarMenuToggle.reset(_topBar->addButton( base::make_unique_q( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarMenu : st::infoTopBarMenu)))); _topBarMenuToggle->addClickHandler([this] { showTopBarMenu(); }); } void WrapWidget::addContentSaveButton() { Expects(_topBar != nullptr); _topBar->addButtonWithVisibility( base::make_unique_q( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarSave : st::infoTopBarSave)), _controller->canSaveChanges() )->addClickHandler([=] { _content->saveChanges(crl::guard(_content.data(), [=] { _controller->showBackFromStack(); })); }); } void WrapWidget::addProfileCallsButton() { Expects(_topBar != nullptr); const auto peer = key().peer(); const auto user = peer ? peer->asUser() : nullptr; if (!user || user->isSelf() || !Global::PhoneCallsEnabled()) { return; } Notify::PeerUpdateValue( user, Notify::PeerUpdate::Flag::UserHasCalls ) | rpl::filter([=] { return user->hasCalls(); }) | rpl::take( 1 ) | rpl::start_with_next([=] { _topBar->addButton( base::make_unique_q( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarCall : st::infoTopBarCall)) )->addClickHandler([=] { Calls::Current().startOutgoingCall(user); }); }, _topBar->lifetime()); if (user && user->callsStatus() == UserData::CallsStatus::Unknown) { user->updateFull(); } } void WrapWidget::addProfileNotificationsButton() { Expects(_topBar != nullptr); const auto peer = key().peer(); if (!peer) { return; } auto notifications = _topBar->addButton( base::make_unique_q( _topBar, (wrap() == Wrap::Layer ? st::infoLayerTopBarNotifications : st::infoTopBarNotifications))); notifications->addClickHandler([peer] { const auto muteForSeconds = Auth().data().notifyIsMuted(peer) ? 0 : Data::NotifySettings::kDefaultMutePeriod; Auth().data().updateNotifySettings(peer, muteForSeconds); }); Profile::NotificationsEnabledValue( peer ) | rpl::start_with_next([notifications](bool enabled) { const auto iconOverride = enabled ? &st::infoNotificationsActive : nullptr; const auto rippleOverride = enabled ? &st::lightButtonBgOver : nullptr; notifications->setIconOverride(iconOverride, iconOverride); notifications->setRippleColorOverride(rippleOverride); }, notifications->lifetime()); } void WrapWidget::showTopBarMenu() { if (_topBarMenu) { _topBarMenu->hideAnimated( Ui::InnerDropdown::HideOption::IgnoreShow); return; } _topBarMenu = base::make_unique_q(this); _topBarMenu->setHiddenCallback([this] { InvokeQueued(this, [this] { _topBarMenu = nullptr; }); if (auto toggle = _topBarMenuToggle.get()) { toggle->setForceRippled(false); } }); _topBarMenu->setShowStartCallback([this] { if (auto toggle = _topBarMenuToggle.get()) { toggle->setForceRippled(true); } }); _topBarMenu->setHideStartCallback([this] { if (auto toggle = _topBarMenuToggle.get()) { toggle->setForceRippled(false); } }); _topBarMenuToggle->installEventFilter(_topBarMenu.get()); const auto addAction = [=]( const QString &text, Fn callback) { return _topBarMenu->addAction(text, std::move(callback)); }; if (const auto peer = key().peer()) { Window::FillPeerMenu( _controller->parentController(), peer, addAction, Window::PeerMenuSource::Profile); } else if (const auto feed = key().feed()) { Window::FillFeedMenu( _controller->parentController(), feed, addAction, Window::PeerMenuSource::Profile); } else if (const auto self = key().settingsSelf()) { const auto showOther = [=](::Settings::Type type) { const auto controller = _controller.get(); _topBarMenu = nullptr; controller->showSettings(type); }; ::Settings::FillMenu(showOther, addAction); } else { _topBarMenu = nullptr; return; } auto position = (wrap() == Wrap::Layer) ? st::infoLayerTopBarMenuPosition : st::infoTopBarMenuPosition; _topBarMenu->moveToRight(position.x(), position.y()); _topBarMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } bool WrapWidget::requireTopBarSearch() const { if (!_controller->searchFieldController()) { return false; } else if (_controller->wrap() == Wrap::Layer || _controller->section().type() == Section::Type::Profile) { return false; } else if (hasStackHistory()) { return true; } // This was for top-level tabs support. // //auto section = _controller->section(); //return (section.type() != Section::Type::Media) // || !Media::TypeToTabIndex(section.mediaType()).has_value(); return false; } bool WrapWidget::showBackFromStackInternal( const Window::SectionShow ¶ms) { if (hasStackHistory()) { auto last = std::move(_historyStack.back()); _historyStack.pop_back(); showNewContent( last.section.get(), params.withWay(Window::SectionShow::Way::Backward)); //_anotherTabMemento = std::move(last.anotherTab); return true; } return (wrap() == Wrap::Layer); } not_null WrapWidget::topWidget() const { // This was done for tabs support. // //if (_topTabs) { // return _topTabsBackground; //} return _topBar; } void WrapWidget::showContent(object_ptr content) { _content = std::move(content); _content->show(); _additionalScroll = 0; //_anotherTabMemento = nullptr; finishShowContent(); } void WrapWidget::finishShowContent() { _content->setIsStackBottom(!hasStackHistory()); updateContentGeometry(); _desiredHeights.fire(desiredHeightForContent()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); _selectedLists.fire(_content->selectedListValue()); _scrollTillBottomChanges.fire(_content->scrollTillBottomChanges()); _topShadow->raise(); _topShadow->finishAnimating(); _contentChanges.fire({}); // This was done for tabs support. // //if (_topTabs) { // _topTabs->raise(); //} } rpl::producer WrapWidget::topShadowToggledValue() const { // Allows always showing shadow for specific wrap value. // Was done for top level tabs support. // //using namespace rpl::mappers; //return rpl::combine( // _controller->wrapValue(), // _desiredShadowVisibilities.events() | rpl::flatten_latest(), // (_1 == Wrap::Side) || _2); return _desiredShadowVisibilities.events() | rpl::flatten_latest(); } 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(); } // Was done for top level tabs support. // //std::unique_ptr WrapWidget::createTabMemento( // Tab tab) { // switch (tab) { // case Tab::Profile: return std::make_unique( // _controller->peerId(), // _controller->migratedPeerId()); // case Tab::Media: return std::make_unique( // _controller->peerId(), // _controller->migratedPeerId(), // Media::Type::Photo); // } // Unexpected("Tab value in Info::WrapWidget::createInner()"); //} object_ptr WrapWidget::createContent( not_null memento, not_null controller) { return memento->createWidget( this, controller, contentGeometry()); } // Was done for top level tabs support. // //void WrapWidget::convertProfileFromStackToTab() { // if (!hasStackHistory()) { // return; // } // auto &entry = _historyStack[0]; // if (entry.section->section().type() != Section::Type::Profile) { // return; // } // auto convertInsideStack = (_historyStack.size() > 1); // auto checkSection = convertInsideStack // ? _historyStack[1].section->section() // : _controller->section(); // auto &anotherMemento = convertInsideStack // ? _historyStack[1].anotherTab // : _anotherTabMemento; // if (checkSection.type() != Section::Type::Media) { // return; // } // if (!Info::Media::TypeToTabIndex(checkSection.mediaType())) { // return; // } // anotherMemento = std::move(entry.section); // _historyStack.erase(_historyStack.begin()); //} rpl::producer WrapWidget::wrapValue() const { return _wrap.value(); } void WrapWidget::setWrap(Wrap wrap) { // Was done for top level tabs support. // //if (_wrap.current() != Wrap::Side && wrap == Wrap::Side) { // convertProfileFromStackToTab(); //} _wrap = wrap; } rpl::producer<> WrapWidget::contentChanged() const { return _contentChanges.events(); } bool WrapWidget::hasTopBarShadow() const { return _topShadow->toggled(); } QPixmap WrapWidget::grabForShowAnimation( const Window::SectionSlideParams ¶ms) { if (params.withTopBarShadow) { _topShadow->setVisible(false); } else { _topShadow->setVisible(_topShadow->toggled()); } //if (params.withTabs && _topTabs) { // _topTabs->hide(); //} auto result = Ui::GrabWidget(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(); //} if (params.withTopBarShadow) { _topShadow->setVisible(true); } _topBarSurrogate = createTopBarSurrogate(this); } void WrapWidget::doSetInnerFocus() { if (!_topBar->focusSearchField()) { _content->setInnerFocus(); } } void WrapWidget::showFinishedHook() { // Restore shadow visibility after showChildren() call. _topShadow->toggle(_topShadow->toggled(), anim::type::instant); _topBarSurrogate.destroy(); } bool WrapWidget::showInternal( not_null memento, const Window::SectionShow ¶ms) { if (auto infoMemento = dynamic_cast(memento.get())) { if (!_controller || infoMemento->stackSize() > 1) { return false; } auto content = infoMemento->content(); auto skipInternal = hasStackHistory() && (params.way == Window::SectionShow::Way::ClearStack); if (_controller->validateMementoPeer(content)) { if (!skipInternal && _content->showInternal(content)) { highlightTopBar(); return true; // This was done for tabs support. // //} else if (_topTabs) { // // If we open the profile being in the media tab. // // Just switch back to the profile tab. // auto type = content->section().type(); // if (type == Section::Type::Profile // && _tab != Tab::Profile) { // _anotherTabMemento = std::move(infoMemento->takeStack().back()); // _topTabs->setActiveSection(static_cast(Tab::Profile)); // return true; // } else if (type == Section::Type::Media // && _tab != Tab::Media // && Media::TypeToTabIndex(content->section().mediaType()).has_value()) { // _anotherTabMemento = std::move(infoMemento->takeStack().back()); // _topTabs->setActiveSection(static_cast(Tab::Media)); // return true; // } } } // If we're in a nested section and we're asked to show // a chat profile that is at the bottom of the stack we'll // just go back in the stack all the way instead of pushing. if (returnToFirstStackFrame(content, params)) { return true; } showNewContent( content, params); return true; } return false; } void WrapWidget::highlightTopBar() { if (_topBar) { _topBar->highlight(); } } std::unique_ptr WrapWidget::createMemento() { auto stack = std::vector>(); stack.reserve(_historyStack.size() + 1); for (auto &stackItem : base::take(_historyStack)) { stack.push_back(std::move(stackItem.section)); } stack.push_back(_content->createMemento()); // We're not in valid state anymore and supposed to be destroyed. _controller = nullptr; return std::make_unique(std::move(stack)); } rpl::producer WrapWidget::desiredHeightValue() const { return _desiredHeights.events_starting_with(desiredHeightForContent()) | rpl::flatten_latest(); } QRect WrapWidget::contentGeometry() const { return rect().marginsRemoved({ 0, topWidget()->height(), 0, 0 }); } bool WrapWidget::returnToFirstStackFrame( not_null memento, const Window::SectionShow ¶ms) { if (!hasStackHistory()) { return false; } auto firstPeerId = _historyStack.front().section->peerId(); auto firstSection = _historyStack.front().section->section(); if (firstPeerId == memento->peerId() && firstSection.type() == memento->section().type() && firstSection.type() == Section::Type::Profile) { _historyStack.resize(1); _controller->showBackFromStack(); return true; } return false; } 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 newController = createController( _controller->parentController(), memento); auto newContent = object_ptr(nullptr); if (needAnimation) { newContent = createContent(memento, newController.get()); 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(); } _controller = std::move(newController); if (newContent) { setupTop(); showContent(std::move(newContent)); } else { showNewContent(memento); } if (animationParams) { if (Ui::InFocusChain(this)) { setFocus(); } showAnimated( saveToStack ? SlideDirection::FromRight : SlideDirection::FromLeft, animationParams); } } void WrapWidget::showNewContent(not_null memento) { // Validates contentGeometry(). setupTop(); showContent(createContent(memento, _controller.get())); } // This was done for tabs support. // //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) { // This was done for tabs support. // //if (_topTabs) { // _topTabs->resizeToWidth(width()); // _topTabsBackground->resize( // width(), // _topTabs->height() - st::lineWidth); //} if (_topBar) { _topBar->resizeToWidth(width()); } updateContentGeometry(); } void WrapWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Escape) { if (hasStackHistory() || wrap() != Wrap::Layer) { _controller->showBackFromStack(); return; } } SectionWidget::keyPressEvent(e); } 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(); } object_ptr WrapWidget::createTopBarSurrogate( QWidget *parent) { if (hasStackHistory() || wrap() == Wrap::Narrow) { Assert(_topBar != nullptr); auto result = object_ptr(parent); result->addClickHandler([weak = make_weak(this)]{ if (weak) { weak->_controller->showBackFromStack(); } }); result->setGeometry(_topBar->geometry()); result->show(); return std::move(result); } return nullptr; } void WrapWidget::updateGeometry(QRect newGeometry, int additionalScroll) { auto scrollChanged = (_additionalScroll != additionalScroll); auto geometryChanged = (geometry() != newGeometry); auto shrinkingContent = (additionalScroll < _additionalScroll); _additionalScroll = additionalScroll; if (geometryChanged) { if (shrinkingContent) { setGeometry(newGeometry); } if (scrollChanged) { _content->applyAdditionalScroll(additionalScroll); } if (!shrinkingContent) { setGeometry(newGeometry); } } else if (scrollChanged) { _content->applyAdditionalScroll(additionalScroll); } } int WrapWidget::scrollTillBottom(int forHeight) const { return _content->scrollTillBottom(forHeight - topWidget()->height()); } rpl::producer WrapWidget::scrollTillBottomChanges() const { return _scrollTillBottomChanges.events_starting_with( _content->scrollTillBottomChanges() ) | rpl::flatten_latest(); } WrapWidget::~WrapWidget() = default; } // namespace Info