info_wrap_widget.cpp 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "info/info_wrap_widget.h"
  8. #include "info/profile/info_profile_widget.h"
  9. #include "info/profile/info_profile_values.h"
  10. #include "info/media/info_media_widget.h"
  11. #include "info/info_content_widget.h"
  12. #include "info/info_controller.h"
  13. #include "info/info_memento.h"
  14. #include "info/info_top_bar.h"
  15. #include "settings/cloud_password/settings_cloud_password_email_confirm.h"
  16. #include "settings/settings_chat.h"
  17. #include "settings/settings_information.h"
  18. #include "settings/settings_main.h"
  19. #include "settings/settings_premium.h"
  20. #include "ui/effects/ripple_animation.h" // MaskByDrawer.
  21. #include "ui/widgets/menu/menu_add_action_callback.h"
  22. #include "ui/widgets/discrete_sliders.h"
  23. #include "ui/widgets/buttons.h"
  24. #include "ui/widgets/shadow.h"
  25. #include "ui/widgets/popup_menu.h"
  26. #include "ui/widgets/menu/menu_add_action_callback_factory.h"
  27. #include "ui/wrap/fade_wrap.h"
  28. #include "ui/search_field_controller.h"
  29. #include "ui/ui_utility.h"
  30. #include "core/application.h"
  31. #include "calls/calls_instance.h"
  32. #include "core/shortcuts.h"
  33. #include "window/window_session_controller.h"
  34. #include "window/window_slide_animation.h"
  35. #include "boxes/peer_list_box.h"
  36. #include "ui/boxes/confirm_box.h"
  37. #include "ui/boxes/peer_qr_box.h"
  38. #include "main/main_session.h"
  39. #include "mtproto/mtproto_config.h"
  40. #include "data/data_download_manager.h"
  41. #include "data/data_session.h"
  42. #include "data/data_changes.h"
  43. #include "data/data_user.h"
  44. #include "data/data_forum_topic.h"
  45. #include "mainwidget.h"
  46. #include "lang/lang_keys.h"
  47. #include "lang/lang_numbers_animation.h"
  48. #include "styles/style_chat.h" // popupMenuExpandedSeparator
  49. #include "styles/style_info.h"
  50. #include "styles/style_profile.h"
  51. #include "styles/style_menu_icons.h"
  52. #include "styles/style_layers.h"
  53. namespace Info {
  54. namespace {
  55. const style::InfoTopBar &TopBarStyle(Wrap wrap) {
  56. return (wrap == Wrap::Layer)
  57. ? st::infoLayerTopBar
  58. : st::infoTopBar;
  59. }
  60. [[nodiscard]] bool HasCustomTopBar(not_null<const Controller*> controller) {
  61. const auto section = controller->section();
  62. return (section.type() == Section::Type::BotStarRef)
  63. || ((section.type() == Section::Type::Settings)
  64. && section.settingsType()->hasCustomTopBar());
  65. }
  66. [[nodiscard]] Fn<Ui::StringWithNumbers(int)> SelectedTitleForMedia(
  67. Section::MediaType type) {
  68. return [type](int count) {
  69. using Type = Storage::SharedMediaType;
  70. return [&] {
  71. switch (type) {
  72. case Type::Photo: return tr::lng_media_selected_photo;
  73. case Type::GIF: return tr::lng_media_selected_gif;
  74. case Type::Video: return tr::lng_media_selected_video;
  75. case Type::File: return tr::lng_media_selected_file;
  76. case Type::MusicFile: return tr::lng_media_selected_song;
  77. case Type::Link: return tr::lng_media_selected_link;
  78. case Type::RoundVoiceFile: return tr::lng_media_selected_audio;
  79. case Type::PhotoVideo: return tr::lng_stories_row_count;
  80. }
  81. Unexpected("Type in TopBar::generateSelectedText()");
  82. }()(tr::now, lt_count, count, Ui::StringWithNumbers::FromString);
  83. };
  84. }
  85. } // namespace
  86. struct WrapWidget::StackItem {
  87. std::shared_ptr<ContentMemento> section;
  88. // std::shared_ptr<ContentMemento> anotherTab;
  89. };
  90. SelectedItems::SelectedItems(Section::MediaType mediaType)
  91. : title(SelectedTitleForMedia(mediaType)) {
  92. }
  93. WrapWidget::WrapWidget(
  94. QWidget *parent,
  95. not_null<Window::SessionController*> window,
  96. Wrap wrap,
  97. not_null<Memento*> memento)
  98. : SectionWidget(parent, window, rpl::producer<PeerData*>())
  99. , _isSeparatedWindow(
  100. window->windowId().type == Window::SeparateType::SharedMedia)
  101. , _wrap(wrap)
  102. , _controller(createController(window, memento->content()))
  103. , _topShadow(this)
  104. , _bottomShadow(this) {
  105. _topShadow->toggleOn(
  106. topShadowToggledValue(
  107. ) | rpl::filter([](bool shown) {
  108. return true;
  109. }));
  110. _bottomShadow->toggleOn(
  111. _desiredBottomShadowVisibilities.events(
  112. ) | rpl::flatten_latest() | rpl::distinct_until_changed());
  113. _wrap.changes(
  114. ) | rpl::start_with_next([this] {
  115. setupTop();
  116. finishShowContent();
  117. }, lifetime());
  118. selectedListValue(
  119. ) | rpl::start_with_next([this](SelectedItems &&items) {
  120. InvokeQueued(this, [this, items = std::move(items)]() mutable {
  121. if (_topBar) {
  122. _topBar->setSelectedItems(std::move(items));
  123. }
  124. });
  125. }, lifetime());
  126. restoreHistoryStack(memento->takeStack());
  127. if (const auto topic = _controller->topic()) {
  128. topic->destroyed(
  129. ) | rpl::start_with_next([=] {
  130. if (_wrap.current() == Wrap::Layer) {
  131. _controller->parentController()->hideSpecialLayer();
  132. } else if (_wrap.current() == Wrap::Narrow) {
  133. _controller->parentController()->showBackFromStack(
  134. Window::SectionShow(
  135. anim::type::normal,
  136. anim::activation::background));
  137. } else {
  138. _removeRequests.fire({});
  139. }
  140. }, lifetime());
  141. }
  142. }
  143. void WrapWidget::setupShortcuts() {
  144. Shortcuts::Requests(
  145. ) | rpl::filter([=] {
  146. return requireTopBarSearch()
  147. && (Core::App().activeWindow()
  148. == &_controller->parentController()->window());
  149. }) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
  150. using Command = Shortcuts::Command;
  151. request->check(Command::Search) && request->handle([=] {
  152. _topBar->showSearch();
  153. return true;
  154. });
  155. }, lifetime());
  156. }
  157. void WrapWidget::restoreHistoryStack(
  158. std::vector<std::shared_ptr<ContentMemento>> stack) {
  159. Expects(!stack.empty());
  160. Expects(!hasStackHistory());
  161. auto content = std::move(stack.back());
  162. stack.pop_back();
  163. if (!stack.empty()) {
  164. _historyStack.reserve(stack.size());
  165. for (auto &stackItem : stack) {
  166. auto item = StackItem();
  167. item.section = std::move(stackItem);
  168. _historyStack.push_back(std::move(item));
  169. }
  170. }
  171. startInjectingActivePeerProfiles();
  172. showNewContent(content.get());
  173. }
  174. void WrapWidget::startInjectingActivePeerProfiles() {
  175. using namespace rpl::mappers;
  176. rpl::combine(
  177. _wrap.value(),
  178. _controller->parentController()->activeChatValue()
  179. ) | rpl::filter(
  180. (_1 == Wrap::Side) && _2
  181. ) | rpl::map(
  182. _2
  183. ) | rpl::start_with_next([this](Dialogs::Key key) {
  184. injectActiveProfile(key);
  185. }, lifetime());
  186. }
  187. void WrapWidget::injectActiveProfile(Dialogs::Key key) {
  188. if (const auto peer = key.peer()) {
  189. injectActivePeerProfile(peer);
  190. }
  191. }
  192. void WrapWidget::injectActivePeerProfile(not_null<PeerData*> peer) {
  193. const auto firstPeer = hasStackHistory()
  194. ? _historyStack.front().section->peer()
  195. : _controller->peer();
  196. const auto firstSectionType = hasStackHistory()
  197. ? _historyStack.front().section->section().type()
  198. : _controller->section().type();
  199. const auto firstSectionMediaType = [&] {
  200. if (firstSectionType == Section::Type::Profile
  201. || firstSectionType == Section::Type::SavedSublists
  202. || firstSectionType == Section::Type::Downloads) {
  203. return Section::MediaType::kCount;
  204. }
  205. return hasStackHistory()
  206. ? _historyStack.front().section->section().mediaType()
  207. : _controller->section().mediaType();
  208. }();
  209. const auto savedSublistsInfo = peer->savedSublistsInfo();
  210. const auto sharedMediaInfo = peer->sharedMediaInfo();
  211. const auto expectedType = savedSublistsInfo
  212. ? Section::Type::SavedSublists
  213. : sharedMediaInfo
  214. ? Section::Type::Media
  215. : Section::Type::Profile;
  216. const auto expectedMediaType = savedSublistsInfo
  217. ? Section::MediaType::kCount
  218. : sharedMediaInfo
  219. ? Section::MediaType::Photo
  220. : Section::MediaType::kCount;
  221. if (firstSectionType != expectedType
  222. || firstSectionMediaType != expectedMediaType
  223. || firstPeer != peer) {
  224. auto section = savedSublistsInfo
  225. ? Section(Section::Type::SavedSublists)
  226. : sharedMediaInfo
  227. ? Section(Section::MediaType::Photo)
  228. : Section(Section::Type::Profile);
  229. injectActiveProfileMemento(std::move(
  230. Memento(peer, section).takeStack().front()));
  231. }
  232. }
  233. void WrapWidget::injectActiveProfileMemento(
  234. std::shared_ptr<ContentMemento> memento) {
  235. auto injected = StackItem();
  236. injected.section = std::move(memento);
  237. _historyStack.insert(
  238. _historyStack.begin(),
  239. std::move(injected));
  240. if (_content) {
  241. setupTop();
  242. finishShowContent();
  243. }
  244. }
  245. std::unique_ptr<Controller> WrapWidget::createController(
  246. not_null<Window::SessionController*> window,
  247. not_null<ContentMemento*> memento) {
  248. auto result = std::make_unique<Controller>(
  249. this,
  250. window,
  251. memento);
  252. return result;
  253. }
  254. Key WrapWidget::key() const {
  255. return _controller->key();
  256. }
  257. Dialogs::RowDescriptor WrapWidget::activeChat() const {
  258. if (const auto peer = key().peer()) {
  259. return Dialogs::RowDescriptor(
  260. peer->owner().history(peer),
  261. FullMsgId());
  262. } else if (const auto storiesPeer = key().storiesPeer()) {
  263. return (key().storiesTab() == Stories::Tab::Saved)
  264. ? Dialogs::RowDescriptor(
  265. storiesPeer->owner().history(storiesPeer),
  266. FullMsgId())
  267. : Dialogs::RowDescriptor();
  268. } else if (key().settingsSelf()
  269. || key().isDownloads()
  270. || key().reactionsContextId()
  271. || key().poll()
  272. || key().starrefPeer()
  273. || key().statisticsTag().peer) {
  274. return Dialogs::RowDescriptor();
  275. }
  276. Unexpected("Owner in WrapWidget::activeChat().");
  277. }
  278. void WrapWidget::forceContentRepaint() {
  279. // WA_OpaquePaintEvent on TopBar creates render glitches when
  280. // animating the LayerWidget's height :( Fixing by repainting.
  281. if (_topBar) {
  282. _topBar->update();
  283. }
  284. _content->update();
  285. }
  286. void WrapWidget::setupTop() {
  287. if (HasCustomTopBar(_controller.get()) || wrap() == Wrap::Search) {
  288. _topBar.destroy();
  289. return;
  290. }
  291. createTopBar();
  292. }
  293. void WrapWidget::createTopBar() {
  294. const auto wrapValue = wrap();
  295. auto selectedItems = _topBar
  296. ? _topBar->takeSelectedItems()
  297. : SelectedItems(Section::MediaType::kCount);
  298. _topBar.create(
  299. this,
  300. _controller.get(),
  301. TopBarStyle(wrapValue),
  302. std::move(selectedItems));
  303. _topBar->selectionActionRequests(
  304. ) | rpl::start_with_next([=](SelectionAction action) {
  305. _content->selectionAction(action);
  306. }, _topBar->lifetime());
  307. if (hasBackButton()) {
  308. _topBar->enableBackButton();
  309. _topBar->backRequest(
  310. ) | rpl::start_with_next([=] {
  311. checkBeforeClose([=] { _controller->showBackFromStack(); });
  312. }, _topBar->lifetime());
  313. } else if (wrapValue == Wrap::Side) {
  314. auto close = _topBar->addButton(
  315. base::make_unique_q<Ui::IconButton>(
  316. _topBar,
  317. st::infoTopBarClose));
  318. close->addClickHandler([this] {
  319. _controller->parentController()->closeThirdSection();
  320. });
  321. }
  322. _topBar->storyClicks() | rpl::start_with_next([=] {
  323. if (const auto peer = _controller->key().peer()) {
  324. _controller->parentController()->openPeerStories(peer->id);
  325. }
  326. }, _topBar->lifetime());
  327. if (wrapValue == Wrap::Layer) {
  328. auto close = _topBar->addButton(
  329. base::make_unique_q<Ui::IconButton>(
  330. _topBar,
  331. st::infoLayerTopBarClose));
  332. close->addClickHandler([this] {
  333. checkBeforeClose([=] {
  334. _controller->parentController()->hideSpecialLayer();
  335. });
  336. });
  337. } else if (requireTopBarSearch()) {
  338. auto search = _controller->searchFieldController();
  339. Assert(search != nullptr);
  340. setupShortcuts();
  341. _topBar->createSearchView(
  342. search,
  343. _controller->searchEnabledByContent(),
  344. _controller->takeSearchStartsFocused());
  345. }
  346. _topBar->lower();
  347. _topBar->resizeToWidth(width());
  348. _topBar->finishAnimating();
  349. _topBar->show();
  350. }
  351. void WrapWidget::setupTopBarMenuToggle() {
  352. Expects(_content != nullptr);
  353. if (!_topBar) {
  354. return;
  355. }
  356. const auto key = _controller->key();
  357. const auto section = _controller->section();
  358. if (section.type() == Section::Type::Profile
  359. && (wrap() != Wrap::Side || hasStackHistory())) {
  360. addTopBarMenuButton();
  361. addProfileCallsButton();
  362. } else if (section.type() == Section::Type::Settings) {
  363. addTopBarMenuButton();
  364. if (section.settingsType() == ::Settings::Information::Id()
  365. || section.settingsType() == ::Settings::Main::Id()) {
  366. const auto controller = _controller->parentController();
  367. const auto self = controller->session().user();
  368. if (!self->username().isEmpty()) {
  369. const auto show = controller->uiShow();
  370. const auto &st = (wrap() == Wrap::Layer)
  371. ? st::infoLayerTopBarQr
  372. : st::infoTopBarQr;
  373. const auto button = _topBar->addButton(
  374. base::make_unique_q<Ui::IconButton>(_topBar, st));
  375. button->addClickHandler([show, self] {
  376. show->show(
  377. Box(Ui::FillPeerQrBox, self, std::nullopt, nullptr));
  378. });
  379. }
  380. }
  381. } else if (key.storiesPeer()
  382. && key.storiesPeer()->isSelf()
  383. && key.storiesTab() == Stories::Tab::Saved) {
  384. const auto &st = (wrap() == Wrap::Layer)
  385. ? st::infoLayerTopBarEdit
  386. : st::infoTopBarEdit;
  387. const auto button = _topBar->addButton(
  388. base::make_unique_q<Ui::IconButton>(_topBar, st));
  389. button->addClickHandler([=] {
  390. _controller->showSettings(::Settings::Information::Id());
  391. });
  392. } else if (section.type() == Section::Type::Downloads) {
  393. auto &manager = Core::App().downloadManager();
  394. rpl::merge(
  395. rpl::single(false),
  396. manager.loadingListChanges() | rpl::map_to(false),
  397. manager.loadedAdded() | rpl::map_to(true),
  398. manager.loadedRemoved() | rpl::map_to(false)
  399. ) | rpl::start_with_next([=, &manager](bool definitelyHas) {
  400. const auto has = [&] {
  401. for ([[maybe_unused]] const auto id : manager.loadingList()) {
  402. return true;
  403. }
  404. for ([[maybe_unused]] const auto id : manager.loadedList()) {
  405. return true;
  406. }
  407. return false;
  408. };
  409. if (!definitelyHas && !has()) {
  410. _topBarMenuToggle = nullptr;
  411. } else if (!_topBarMenuToggle) {
  412. addTopBarMenuButton();
  413. }
  414. }, _topBar->lifetime());
  415. } else if (section.type() == Section::Type::PeerGifts && key.peer()) {
  416. addTopBarMenuButton();
  417. }
  418. }
  419. void WrapWidget::checkBeforeClose(Fn<void()> close) {
  420. _content->checkBeforeClose(crl::guard(this, [=] {
  421. _controller->parentController()->hideLayer();
  422. close();
  423. }));
  424. }
  425. void WrapWidget::checkBeforeCloseByEscape(Fn<void()> close) {
  426. if (_topBar) {
  427. _topBar->checkBeforeCloseByEscape([&] {
  428. _content->checkBeforeCloseByEscape(crl::guard(this, [=] {
  429. WrapWidget::checkBeforeClose(close);
  430. }));
  431. });
  432. } else {
  433. _content->checkBeforeCloseByEscape(crl::guard(this, [=] {
  434. WrapWidget::checkBeforeClose(close);
  435. }));
  436. }
  437. }
  438. void WrapWidget::addTopBarMenuButton() {
  439. Expects(_topBar != nullptr);
  440. Expects(_content != nullptr);
  441. {
  442. const auto guard = gsl::finally([&] { _topBarMenu = nullptr; });
  443. showTopBarMenu(true);
  444. if (!_topBarMenu) {
  445. return;
  446. }
  447. }
  448. _topBarMenuToggle.reset(_topBar->addButton(
  449. base::make_unique_q<Ui::IconButton>(
  450. _topBar,
  451. (wrap() == Wrap::Layer
  452. ? st::infoLayerTopBarMenu
  453. : st::infoTopBarMenu))));
  454. _topBarMenuToggle->addClickHandler([this] {
  455. showTopBarMenu(false);
  456. });
  457. Shortcuts::Requests(
  458. ) | rpl::filter([=] {
  459. return (_controller->section().type() == Section::Type::Profile);
  460. }) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
  461. using Command = Shortcuts::Command;
  462. request->check(Command::ShowChatMenu, 1) && request->handle([=] {
  463. Window::ActivateWindow(_controller->parentController());
  464. showTopBarMenu(false);
  465. return true;
  466. });
  467. }, _topBarMenuToggle->lifetime());
  468. }
  469. bool WrapWidget::closeByOutsideClick() const {
  470. return _content->closeByOutsideClick();
  471. }
  472. void WrapWidget::addProfileCallsButton() {
  473. Expects(_topBar != nullptr);
  474. const auto peer = key().peer();
  475. const auto user = peer ? peer->asUser() : nullptr;
  476. if (!user || user->sharedMediaInfo() || user->isInaccessible()) {
  477. return;
  478. }
  479. user->session().changes().peerFlagsValue(
  480. user,
  481. Data::PeerUpdate::Flag::HasCalls
  482. ) | rpl::filter([=] {
  483. return user->hasCalls();
  484. }) | rpl::take(
  485. 1
  486. ) | rpl::start_with_next([=] {
  487. _topBar->addButton(
  488. base::make_unique_q<Ui::IconButton>(
  489. _topBar,
  490. (wrap() == Wrap::Layer
  491. ? st::infoLayerTopBarCall
  492. : st::infoTopBarCall))
  493. )->addClickHandler([=] {
  494. Core::App().calls().startOutgoingCall(user, false);
  495. });
  496. }, _topBar->lifetime());
  497. if (user && user->callsStatus() == UserData::CallsStatus::Unknown) {
  498. user->updateFull();
  499. }
  500. }
  501. void WrapWidget::showTopBarMenu(bool check) {
  502. if (_topBarMenu) {
  503. _topBarMenu->hideMenu(true);
  504. return;
  505. }
  506. _topBarMenu = base::make_unique_q<Ui::PopupMenu>(
  507. QWidget::window(),
  508. st::popupMenuExpandedSeparator);
  509. _topBarMenu->setDestroyedCallback([this] {
  510. InvokeQueued(this, [this] { _topBarMenu = nullptr; });
  511. if (auto toggle = _topBarMenuToggle.get()) {
  512. toggle->setForceRippled(false);
  513. }
  514. });
  515. _content->fillTopBarMenu(Ui::Menu::CreateAddActionCallback(_topBarMenu));
  516. if (_topBarMenu->empty()) {
  517. _topBarMenu = nullptr;
  518. return;
  519. } else if (check) {
  520. return;
  521. }
  522. _topBarMenu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
  523. _topBarMenuToggle->setForceRippled(true);
  524. _topBarMenu->popup(_topBarMenuToggle->mapToGlobal(
  525. st::infoLayerTopBarMenuPosition));
  526. }
  527. bool WrapWidget::requireTopBarSearch() const {
  528. if (!_topBar
  529. || !_controller->searchFieldController()
  530. || (_controller->wrap() == Wrap::Layer)
  531. || (_controller->section().type() == Section::Type::Profile)
  532. || key().isDownloads()) {
  533. return false;
  534. } else if (hasStackHistory()
  535. || _controller->section().type() == Section::Type::RequestsList) {
  536. return true;
  537. }
  538. return false;
  539. }
  540. bool WrapWidget::showBackFromStackInternal(
  541. const Window::SectionShow &params) {
  542. if (hasStackHistory()) {
  543. auto last = std::move(_historyStack.back());
  544. _historyStack.pop_back();
  545. showNewContent(
  546. last.section.get(),
  547. params.withWay(Window::SectionShow::Way::Backward));
  548. return true;
  549. }
  550. return (wrap() == Wrap::Layer);
  551. }
  552. void WrapWidget::removeFromStack(const std::vector<Section> &sections) {
  553. for (const auto &section : sections) {
  554. const auto it = ranges::find_if(_historyStack, [&](
  555. const StackItem &item) {
  556. const auto &s = item.section->section();
  557. if (s.type() != section.type()) {
  558. return false;
  559. } else if (s.type() == Section::Type::SavedSublists) {
  560. return true;
  561. } else if (s.type() == Section::Type::Media) {
  562. return (s.mediaType() == section.mediaType());
  563. } else if (s.type() == Section::Type::Settings) {
  564. return (s.settingsType() == section.settingsType());
  565. }
  566. return false;
  567. });
  568. if (it != end(_historyStack)) {
  569. _historyStack.erase(it);
  570. }
  571. }
  572. }
  573. not_null<Ui::RpWidget*> WrapWidget::topWidget() const {
  574. return _topBar;
  575. }
  576. void WrapWidget::showContent(object_ptr<ContentWidget> content) {
  577. if (auto old = std::exchange(_content, std::move(content))) {
  578. if (Ui::InFocusChain(old)) {
  579. // Prevent activating dialogs filter field while animating.
  580. setFocus();
  581. }
  582. old->hide();
  583. // Content destructor may invoke closeBox() that will try to
  584. // start layer animation. If we won't detach old content from
  585. // its parent WrapWidget layer animation will be started with a
  586. // partially destructed grand-child widget and result in a crash.
  587. old->setParent(nullptr);
  588. old.destroy();
  589. }
  590. _additionalScroll = 0;
  591. _content->show();
  592. finishShowContent();
  593. }
  594. void WrapWidget::finishShowContent() {
  595. setupTopBarMenuToggle();
  596. updateContentGeometry();
  597. _content->setIsStackBottom(!hasStackHistory());
  598. if (_topBar) {
  599. _topBar->setTitle({
  600. .title = _content->title(),
  601. .subtitle = _content->subtitle(),
  602. });
  603. _topBar->setStories(_content->titleStories());
  604. _topBar->setStoriesArchive(
  605. _controller->key().storiesTab() == Stories::Tab::Archive);
  606. }
  607. _desiredHeights.fire(desiredHeightForContent());
  608. _desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
  609. _desiredBottomShadowVisibilities.fire(
  610. _content->desiredBottomShadowVisibility());
  611. if (auto selection = _content->selectedListValue()) {
  612. _selectedLists.fire(std::move(selection));
  613. } else {
  614. _selectedLists.fire(rpl::single(
  615. SelectedItems(Storage::SharedMediaType::Photo)));
  616. }
  617. _scrollTillBottomChanges.fire(_content->scrollTillBottomChanges());
  618. _topShadow->raise();
  619. _topShadow->finishAnimating();
  620. _bottomShadow->raise();
  621. _bottomShadow->finishAnimating();
  622. _contentChanges.fire({});
  623. _content->scrollBottomSkipValue(
  624. ) | rpl::start_with_next([=] {
  625. updateContentGeometry();
  626. }, _content->lifetime());
  627. }
  628. rpl::producer<bool> WrapWidget::topShadowToggledValue() const {
  629. return _desiredShadowVisibilities.events()
  630. | rpl::flatten_latest(
  631. ) | rpl::map([=](bool v) { return v && (_topBar != nullptr); });
  632. }
  633. rpl::producer<int> WrapWidget::desiredHeightForContent() const {
  634. using namespace rpl::mappers;
  635. return rpl::single(0) | rpl::then(rpl::combine(
  636. _content->desiredHeightValue(),
  637. (_topBar ? _topBar->heightValue() : rpl::single(0)),
  638. _1 + _2));
  639. }
  640. rpl::producer<SelectedItems> WrapWidget::selectedListValue() const {
  641. return _selectedLists.events() | rpl::flatten_latest();
  642. }
  643. object_ptr<ContentWidget> WrapWidget::createContent(
  644. not_null<ContentMemento*> memento,
  645. not_null<Controller*> controller) {
  646. return memento->createWidget(
  647. this,
  648. controller,
  649. contentGeometry());
  650. }
  651. rpl::producer<Wrap> WrapWidget::wrapValue() const {
  652. return _wrap.value();
  653. }
  654. void WrapWidget::setWrap(Wrap wrap) {
  655. _wrap = wrap;
  656. }
  657. rpl::producer<> WrapWidget::contentChanged() const {
  658. return _contentChanges.events();
  659. }
  660. bool WrapWidget::hasTopBarShadow() const {
  661. return _topShadow->toggled();
  662. }
  663. QPixmap WrapWidget::grabForShowAnimation(
  664. const Window::SectionSlideParams &params) {
  665. if (params.withTopBarShadow) {
  666. _topShadow->setVisible(false);
  667. } else {
  668. _topShadow->setVisible(_topShadow->toggled());
  669. }
  670. const auto expanding = _expanding;
  671. if (expanding) {
  672. _grabbingForExpanding = true;
  673. }
  674. auto result = Ui::GrabWidget(this);
  675. if (expanding) {
  676. _grabbingForExpanding = false;
  677. }
  678. if (params.withTopBarShadow) {
  679. _topShadow->setVisible(true);
  680. }
  681. return result;
  682. }
  683. void WrapWidget::showAnimatedHook(
  684. const Window::SectionSlideParams &params) {
  685. if (params.withTopBarShadow) {
  686. _topShadow->setVisible(true);
  687. }
  688. _topBarSurrogate = createTopBarSurrogate(this);
  689. }
  690. void WrapWidget::doSetInnerFocus() {
  691. if (!_topBar || !_topBar->focusSearchField()) {
  692. _content->setInnerFocus();
  693. }
  694. }
  695. void WrapWidget::showFinishedHook() {
  696. // Restore shadow visibility after showChildren() call.
  697. _topShadow->toggle(_topShadow->toggled(), anim::type::instant);
  698. _bottomShadow->toggle(_bottomShadow->toggled(), anim::type::instant);
  699. _topBarSurrogate.destroy();
  700. _content->showFinished();
  701. }
  702. bool WrapWidget::showInternal(
  703. not_null<Window::SectionMemento*> memento,
  704. const Window::SectionShow &params) {
  705. if (auto infoMemento = dynamic_cast<Memento*>(memento.get())) {
  706. if (!_controller || infoMemento->stackSize() > 1) {
  707. return false;
  708. }
  709. auto content = infoMemento->content();
  710. auto skipInternal = hasStackHistory()
  711. && (params.way == Window::SectionShow::Way::ClearStack);
  712. if (_controller->validateMementoPeer(content)) {
  713. if (!skipInternal && _content->showInternal(content)) {
  714. highlightTopBar();
  715. return true;
  716. }
  717. }
  718. // If we're in a nested section and we're asked to show
  719. // a chat profile that is at the bottom of the stack we'll
  720. // just go back in the stack all the way instead of pushing.
  721. if (returnToFirstStackFrame(content, params)) {
  722. return true;
  723. }
  724. showNewContent(content, params);
  725. return true;
  726. }
  727. return false;
  728. }
  729. void WrapWidget::highlightTopBar() {
  730. if (_topBar) {
  731. _topBar->highlight();
  732. }
  733. }
  734. std::shared_ptr<Window::SectionMemento> WrapWidget::createMemento() {
  735. auto stack = std::vector<std::shared_ptr<ContentMemento>>();
  736. stack.reserve(_historyStack.size() + 1);
  737. for (auto &stackItem : base::take(_historyStack)) {
  738. stack.push_back(std::move(stackItem.section));
  739. }
  740. stack.push_back(_content->createMemento());
  741. // We're not in valid state anymore and supposed to be destroyed.
  742. _controller = nullptr;
  743. return std::make_shared<Memento>(std::move(stack));
  744. }
  745. rpl::producer<int> WrapWidget::desiredHeightValue() const {
  746. return _desiredHeights.events_starting_with(desiredHeightForContent())
  747. | rpl::flatten_latest();
  748. }
  749. QRect WrapWidget::contentGeometry() const {
  750. const auto top = _topBar ? _topBar->height() : 0;
  751. return rect().marginsRemoved({ 0, top, 0, 0 });
  752. }
  753. bool WrapWidget::returnToFirstStackFrame(
  754. not_null<ContentMemento*> memento,
  755. const Window::SectionShow &params) {
  756. if (!hasStackHistory()) {
  757. return false;
  758. }
  759. auto firstPeer = _historyStack.front().section->peer();
  760. auto firstSection = _historyStack.front().section->section();
  761. if (firstPeer == memento->peer()
  762. && firstSection.type() == memento->section().type()
  763. && firstSection.type() == Section::Type::Profile) {
  764. _historyStack.resize(1);
  765. _controller->showBackFromStack();
  766. return true;
  767. }
  768. return false;
  769. }
  770. void WrapWidget::showNewContent(
  771. not_null<ContentMemento*> memento,
  772. const Window::SectionShow &params) {
  773. const auto saveToStack = (_content != nullptr)
  774. && (params.way == Window::SectionShow::Way::Forward);
  775. const auto needAnimation = (_content != nullptr)
  776. && (params.animated != anim::type::instant);
  777. auto animationParams = SectionSlideParams();
  778. auto newController = createController(
  779. _controller->parentController(),
  780. memento);
  781. if (_controller && newController) {
  782. newController->takeStepData(_controller.get());
  783. }
  784. auto newContent = object_ptr<ContentWidget>(nullptr);
  785. const auto withBackButton = willHaveBackButton(params);
  786. const auto createInAdvance = needAnimation || withBackButton;
  787. if (createInAdvance) {
  788. newContent = createContent(memento, newController.get());
  789. }
  790. if (needAnimation) {
  791. animationParams.withTopBarShadow = hasTopBarShadow()
  792. && newContent->hasTopBarShadow();
  793. animationParams.oldContentCache = grabForShowAnimation(
  794. animationParams);
  795. const auto layer = (wrap() == Wrap::Layer);
  796. animationParams.withFade = layer;
  797. animationParams.topSkip = layer ? st::boxRadius : 0;
  798. if (HasCustomTopBar(_controller.get())
  799. || HasCustomTopBar(newController.get())) {
  800. const auto s = QSize(
  801. newContent->width(),
  802. animationParams.topSkip);
  803. auto image = Ui::RippleAnimation::MaskByDrawer(s, false, [&](
  804. QPainter &p) {
  805. const auto r = QRect(0, 0, s.width(), s.height() * 2);
  806. p.drawRoundedRect(r, st::boxRadius, st::boxRadius);
  807. });
  808. animationParams.topMask = Ui::PixmapFromImage(std::move(image));
  809. }
  810. }
  811. if (saveToStack) {
  812. auto item = StackItem();
  813. item.section = _content->createMemento();
  814. _historyStack.push_back(std::move(item));
  815. } else if (params.way == Window::SectionShow::Way::ClearStack) {
  816. _historyStack.clear();
  817. }
  818. if (withBackButton) {
  819. newContent->enableBackButton();
  820. }
  821. {
  822. // Let old controller outlive old content widget.
  823. const auto oldController = std::exchange(
  824. _controller,
  825. std::move(newController));
  826. if (newContent) {
  827. setupTop();
  828. showContent(std::move(newContent));
  829. } else {
  830. showNewContent(memento);
  831. }
  832. }
  833. if (animationParams) {
  834. if (Ui::InFocusChain(this)) {
  835. setFocus();
  836. }
  837. showAnimated(
  838. saveToStack
  839. ? SlideDirection::FromRight
  840. : SlideDirection::FromLeft,
  841. animationParams);
  842. }
  843. }
  844. void WrapWidget::showNewContent(not_null<ContentMemento*> memento) {
  845. // Validates contentGeometry().
  846. setupTop();
  847. showContent(createContent(memento, _controller.get()));
  848. }
  849. void WrapWidget::resizeEvent(QResizeEvent *e) {
  850. if (_topBar) {
  851. _topBar->resizeToWidth(width());
  852. }
  853. updateContentGeometry();
  854. }
  855. void WrapWidget::keyPressEvent(QKeyEvent *e) {
  856. if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) {
  857. checkBeforeCloseByEscape((hasStackHistory() || wrap() != Wrap::Layer)
  858. ? Fn<void()>([=] { _controller->showBackFromStack(); })
  859. : Fn<void()>([=] {
  860. _controller->parentController()->hideSpecialLayer();
  861. }));
  862. return;
  863. }
  864. SectionWidget::keyPressEvent(e);
  865. }
  866. void WrapWidget::updateContentGeometry() {
  867. if (_content) {
  868. if (_topBar) {
  869. _topShadow->resizeToWidth(width());
  870. _topShadow->moveToLeft(0, _topBar->height());
  871. }
  872. _content->setGeometry(contentGeometry());
  873. _bottomShadow->resizeToWidth(width());
  874. _bottomShadow->moveToLeft(
  875. 0,
  876. _content->y()
  877. + _content->height()
  878. - _content->scrollBottomSkip());
  879. }
  880. }
  881. bool WrapWidget::floatPlayerHandleWheelEvent(QEvent *e) {
  882. return _content->floatPlayerHandleWheelEvent(e);
  883. }
  884. QRect WrapWidget::floatPlayerAvailableRect() {
  885. return _content->floatPlayerAvailableRect();
  886. }
  887. object_ptr<Ui::RpWidget> WrapWidget::createTopBarSurrogate(
  888. QWidget *parent) {
  889. if (_topBar && hasBackButton()) {
  890. Assert(_topBar != nullptr);
  891. auto result = object_ptr<Ui::AbstractButton>(parent);
  892. result->addClickHandler([weak = Ui::MakeWeak(this)]{
  893. if (weak) {
  894. weak->_controller->showBackFromStack();
  895. }
  896. });
  897. result->setGeometry(_topBar->geometry());
  898. result->show();
  899. return result;
  900. }
  901. return nullptr;
  902. }
  903. void WrapWidget::updateGeometry(
  904. QRect newGeometry,
  905. bool expanding,
  906. int additionalScroll,
  907. int maxVisibleHeight) {
  908. auto scrollChanged = (_additionalScroll != additionalScroll);
  909. auto geometryChanged = (geometry() != newGeometry);
  910. auto shrinkingContent = (additionalScroll < _additionalScroll);
  911. _additionalScroll = additionalScroll;
  912. _maxVisibleHeight = maxVisibleHeight;
  913. _expanding = expanding;
  914. _content->applyMaxVisibleHeight(maxVisibleHeight);
  915. if (geometryChanged) {
  916. if (shrinkingContent) {
  917. setGeometry(newGeometry);
  918. }
  919. if (scrollChanged) {
  920. _content->applyAdditionalScroll(additionalScroll);
  921. }
  922. if (!shrinkingContent) {
  923. setGeometry(newGeometry);
  924. }
  925. } else if (scrollChanged) {
  926. _content->applyAdditionalScroll(additionalScroll);
  927. }
  928. }
  929. int WrapWidget::scrollTillBottom(int forHeight) const {
  930. return _content->scrollTillBottom(forHeight
  931. - (_topBar ? _topBar->height() : 0));
  932. }
  933. int WrapWidget::scrollBottomSkip() const {
  934. return _content->scrollBottomSkip();
  935. }
  936. rpl::producer<int> WrapWidget::scrollTillBottomChanges() const {
  937. return _scrollTillBottomChanges.events_starting_with(
  938. _content->scrollTillBottomChanges()
  939. ) | rpl::flatten_latest();
  940. }
  941. rpl::producer<bool> WrapWidget::grabbingForExpanding() const {
  942. return _grabbingForExpanding.value();
  943. }
  944. const Ui::RoundRect *WrapWidget::bottomSkipRounding() const {
  945. return _content->bottomSkipRounding();
  946. }
  947. bool WrapWidget::hasBackButton() const {
  948. return !_isSeparatedWindow
  949. && (wrap() == Wrap::Narrow || hasStackHistory());
  950. }
  951. bool WrapWidget::willHaveBackButton(
  952. const Window::SectionShow &params) const {
  953. using Way = Window::SectionShow::Way;
  954. const auto willSaveToStack = (_content != nullptr)
  955. && (params.way == Way::Forward);
  956. const auto willClearStack = (params.way == Way::ClearStack);
  957. const auto willHaveStack = !willClearStack
  958. && (hasStackHistory() || willSaveToStack);
  959. return (wrap() == Wrap::Narrow) || willHaveStack;
  960. }
  961. WrapWidget::~WrapWidget() = default;
  962. } // namespace Info