info_top_bar.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  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_top_bar.h"
  8. #include "dialogs/ui/dialogs_stories_list.h"
  9. #include "lang/lang_keys.h"
  10. #include "info/info_wrap_widget.h"
  11. #include "info/info_controller.h"
  12. #include "info/profile/info_profile_values.h"
  13. #include "storage/storage_shared_media.h"
  14. #include "boxes/delete_messages_box.h"
  15. #include "boxes/peer_list_controllers.h"
  16. #include "main/main_session.h"
  17. #include "ui/widgets/buttons.h"
  18. #include "ui/widgets/labels.h"
  19. #include "ui/widgets/fields/input_field.h"
  20. #include "ui/widgets/shadow.h"
  21. #include "ui/wrap/fade_wrap.h"
  22. #include "ui/wrap/padding_wrap.h"
  23. #include "ui/search_field_controller.h"
  24. #include "data/data_session.h"
  25. #include "data/data_channel.h"
  26. #include "data/data_user.h"
  27. #include "styles/style_dialogs.h"
  28. #include "styles/style_info.h"
  29. namespace Info {
  30. TopBar::TopBar(
  31. QWidget *parent,
  32. not_null<Window::SessionNavigation*> navigation,
  33. const style::InfoTopBar &st,
  34. SelectedItems &&selectedItems)
  35. : RpWidget(parent)
  36. , _navigation(navigation)
  37. , _st(st)
  38. , _selectedItems(Section::MediaType::kCount) {
  39. if (_st.radius) {
  40. _roundRect.emplace(_st.radius, _st.bg);
  41. }
  42. setAttribute(Qt::WA_OpaquePaintEvent, !_roundRect);
  43. setSelectedItems(std::move(selectedItems));
  44. updateControlsVisibility(anim::type::instant);
  45. }
  46. template <typename Callback>
  47. void TopBar::registerUpdateControlCallback(
  48. QObject *guard,
  49. Callback &&callback) {
  50. _updateControlCallbacks[guard] =[
  51. weak = Ui::MakeWeak(guard),
  52. callback = std::forward<Callback>(callback)
  53. ](anim::type animated) {
  54. if (!weak) {
  55. return false;
  56. }
  57. callback(animated);
  58. return true;
  59. };
  60. }
  61. template <typename Widget, typename IsVisible>
  62. void TopBar::registerToggleControlCallback(
  63. Widget *widget,
  64. IsVisible &&callback) {
  65. registerUpdateControlCallback(widget, [
  66. widget,
  67. isVisible = std::forward<IsVisible>(callback)
  68. ](anim::type animated) {
  69. widget->toggle(isVisible(), animated);
  70. });
  71. }
  72. void TopBar::setTitle(TitleDescriptor descriptor) {
  73. if (_title) {
  74. delete _title;
  75. }
  76. if (_subtitle) {
  77. delete _subtitle;
  78. }
  79. const auto withSubtitle = !!descriptor.subtitle;
  80. if (withSubtitle) {
  81. _subtitle = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>(
  82. this,
  83. object_ptr<Ui::FlatLabel>(
  84. this,
  85. std::move(descriptor.subtitle),
  86. _st.subtitle),
  87. st::infoTopBarScale);
  88. _subtitle->setDuration(st::infoTopBarDuration);
  89. _subtitle->toggle(
  90. !selectionMode() && !storiesTitle(),
  91. anim::type::instant);
  92. registerToggleControlCallback(_subtitle.data(), [=] {
  93. return !selectionMode() && !storiesTitle() && !searchMode();
  94. });
  95. }
  96. _title = Ui::CreateChild<Ui::FadeWrap<Ui::FlatLabel>>(
  97. this,
  98. object_ptr<Ui::FlatLabel>(
  99. this,
  100. std::move(descriptor.title),
  101. withSubtitle ? _st.titleWithSubtitle : _st.title),
  102. st::infoTopBarScale);
  103. _title->setDuration(st::infoTopBarDuration);
  104. _title->toggle(
  105. !selectionMode() && !storiesTitle(),
  106. anim::type::instant);
  107. registerToggleControlCallback(_title.data(), [=] {
  108. return !selectionMode() && !storiesTitle() && !searchMode();
  109. });
  110. if (_back) {
  111. _title->setAttribute(Qt::WA_TransparentForMouseEvents);
  112. if (_subtitle) {
  113. _subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
  114. }
  115. }
  116. updateControlsGeometry(width());
  117. }
  118. void TopBar::enableBackButton() {
  119. if (_back) {
  120. return;
  121. }
  122. _back = Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
  123. this,
  124. object_ptr<Ui::IconButton>(this, _st.back),
  125. st::infoTopBarScale);
  126. _back->setDuration(st::infoTopBarDuration);
  127. _back->toggle(!selectionMode(), anim::type::instant);
  128. _back->entity()->clicks(
  129. ) | rpl::to_empty
  130. | rpl::start_to_stream(_backClicks, _back->lifetime());
  131. registerToggleControlCallback(_back.data(), [=] {
  132. return !selectionMode();
  133. });
  134. if (_title) {
  135. _title->setAttribute(Qt::WA_TransparentForMouseEvents);
  136. }
  137. if (_subtitle) {
  138. _subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
  139. }
  140. if (_storiesWrap) {
  141. _storiesWrap->raise();
  142. }
  143. updateControlsGeometry(width());
  144. }
  145. void TopBar::createSearchView(
  146. not_null<Ui::SearchFieldController*> controller,
  147. rpl::producer<bool> &&shown,
  148. bool startsFocused) {
  149. setSearchField(
  150. controller->createField(this, _st.searchRow.field),
  151. std::move(shown),
  152. startsFocused);
  153. }
  154. bool TopBar::focusSearchField() {
  155. if (_searchField && _searchField->isVisible()) {
  156. _searchField->setFocus();
  157. return true;
  158. }
  159. return false;
  160. }
  161. Ui::FadeWrap<Ui::RpWidget> *TopBar::pushButton(
  162. base::unique_qptr<Ui::RpWidget> button) {
  163. auto wrapped = base::make_unique_q<Ui::FadeWrap<Ui::RpWidget>>(
  164. this,
  165. object_ptr<Ui::RpWidget>::fromRaw(button.release()),
  166. st::infoTopBarScale);
  167. auto weak = wrapped.get();
  168. _buttons.push_back(std::move(wrapped));
  169. weak->setDuration(st::infoTopBarDuration);
  170. registerToggleControlCallback(weak, [=] {
  171. return !selectionMode()
  172. && !_searchModeEnabled;
  173. });
  174. weak->toggle(
  175. !selectionMode() && !_searchModeEnabled,
  176. anim::type::instant);
  177. weak->widthValue(
  178. ) | rpl::start_with_next([this] {
  179. updateControlsGeometry(width());
  180. }, lifetime());
  181. return weak;
  182. }
  183. void TopBar::forceButtonVisibility(
  184. Ui::FadeWrap<Ui::RpWidget> *button,
  185. rpl::producer<bool> shown) {
  186. _updateControlCallbacks.erase(button);
  187. button->toggleOn(std::move(shown));
  188. }
  189. void TopBar::setSearchField(
  190. base::unique_qptr<Ui::InputField> field,
  191. rpl::producer<bool> &&shown,
  192. bool startsFocused) {
  193. Expects(field != nullptr);
  194. createSearchView(field.release(), std::move(shown), startsFocused);
  195. }
  196. void TopBar::clearSearchField() {
  197. _searchView = nullptr;
  198. }
  199. void TopBar::checkBeforeCloseByEscape(Fn<void()> close) {
  200. if (_searchModeEnabled) {
  201. if (_searchField && !_searchField->empty()) {
  202. _searchField->setText({});
  203. } else {
  204. _searchModeEnabled = false;
  205. updateControlsVisibility(anim::type::normal);
  206. }
  207. } else {
  208. close();
  209. }
  210. }
  211. void TopBar::createSearchView(
  212. not_null<Ui::InputField*> field,
  213. rpl::producer<bool> &&shown,
  214. bool startsFocused) {
  215. _searchView = base::make_unique_q<Ui::FixedHeightWidget>(
  216. this,
  217. _st.searchRow.height);
  218. auto wrap = _searchView.get();
  219. registerUpdateControlCallback(wrap, [=](anim::type) {
  220. wrap->setVisible(!selectionMode() && _searchModeAvailable);
  221. });
  222. _searchField = field;
  223. auto fieldWrap = Ui::CreateChild<Ui::FadeWrap<Ui::InputField>>(
  224. wrap,
  225. object_ptr<Ui::InputField>::fromRaw(field),
  226. st::infoTopBarScale);
  227. fieldWrap->setDuration(st::infoTopBarDuration);
  228. auto focusLifetime = field->lifetime().make_state<rpl::lifetime>();
  229. registerUpdateControlCallback(fieldWrap, [=](anim::type animated) {
  230. auto fieldShown = !selectionMode() && searchMode();
  231. if (!fieldShown && field->hasFocus()) {
  232. setFocus();
  233. }
  234. fieldWrap->toggle(fieldShown, animated);
  235. if (fieldShown) {
  236. *focusLifetime = field->shownValue()
  237. | rpl::filter([](bool shown) { return shown; })
  238. | rpl::take(1)
  239. | rpl::start_with_next([=] { field->setFocus(); });
  240. } else {
  241. focusLifetime->destroy();
  242. }
  243. });
  244. auto button = base::make_unique_q<Ui::IconButton>(this, _st.search);
  245. auto search = button.get();
  246. search->addClickHandler([=] { showSearch(); });
  247. auto searchWrap = pushButton(std::move(button));
  248. registerToggleControlCallback(searchWrap, [=] {
  249. return !selectionMode()
  250. && _searchModeAvailable
  251. && !_searchModeEnabled;
  252. });
  253. auto cancel = Ui::CreateChild<Ui::CrossButton>(
  254. wrap,
  255. _st.searchRow.fieldCancel);
  256. registerToggleControlCallback(cancel, [=] {
  257. return !selectionMode() && searchMode();
  258. });
  259. cancel->addClickHandler([=] {
  260. if (!field->getLastText().isEmpty()) {
  261. field->setText(QString());
  262. } else {
  263. _searchModeEnabled = false;
  264. updateControlsVisibility(anim::type::normal);
  265. }
  266. });
  267. wrap->widthValue(
  268. ) | rpl::start_with_next([=](int newWidth) {
  269. auto availableWidth = newWidth
  270. - _st.searchRow.fieldCancelSkip;
  271. fieldWrap->resizeToWidth(availableWidth);
  272. fieldWrap->moveToLeft(
  273. _st.searchRow.padding.left(),
  274. _st.searchRow.padding.top());
  275. cancel->moveToRight(0, 0);
  276. }, wrap->lifetime());
  277. widthValue(
  278. ) | rpl::start_with_next([=](int newWidth) {
  279. auto left = _back
  280. ? _st.back.width
  281. : _st.titlePosition.x();
  282. wrap->setGeometryToLeft(
  283. left,
  284. 0,
  285. newWidth - left,
  286. wrap->height(),
  287. newWidth);
  288. }, wrap->lifetime());
  289. field->alive(
  290. ) | rpl::start_with_done([=] {
  291. field->setParent(nullptr);
  292. removeButton(search);
  293. clearSearchField();
  294. }, _searchView->lifetime());
  295. _searchModeEnabled = !field->getLastText().isEmpty() || startsFocused;
  296. updateControlsVisibility(anim::type::instant);
  297. std::move(
  298. shown
  299. ) | rpl::start_with_next([=](bool visible) {
  300. auto alreadyInSearch = !field->getLastText().isEmpty();
  301. _searchModeAvailable = visible || alreadyInSearch;
  302. updateControlsVisibility(anim::type::instant);
  303. }, wrap->lifetime());
  304. }
  305. void TopBar::showSearch() {
  306. _searchModeEnabled = true;
  307. updateControlsVisibility(anim::type::normal);
  308. }
  309. void TopBar::removeButton(not_null<Ui::RpWidget*> button) {
  310. _buttons.erase(
  311. std::remove(_buttons.begin(), _buttons.end(), button),
  312. _buttons.end());
  313. }
  314. int TopBar::resizeGetHeight(int newWidth) {
  315. updateControlsGeometry(newWidth);
  316. return _st.height;
  317. }
  318. void TopBar::updateControlsGeometry(int newWidth) {
  319. updateDefaultControlsGeometry(newWidth);
  320. updateSelectionControlsGeometry(newWidth);
  321. updateStoriesGeometry(newWidth);
  322. }
  323. void TopBar::updateDefaultControlsGeometry(int newWidth) {
  324. auto right = 0;
  325. for (auto &button : _buttons) {
  326. if (!button) {
  327. continue;
  328. }
  329. button->moveToRight(right, 0, newWidth);
  330. right += button->width();
  331. }
  332. if (_back) {
  333. _back->setGeometryToLeft(
  334. 0,
  335. 0,
  336. newWidth - right,
  337. _back->height(),
  338. newWidth);
  339. }
  340. if (_title) {
  341. const auto x = _back
  342. ? _st.back.width
  343. : _subtitle
  344. ? _st.titleWithSubtitlePosition.x()
  345. : _st.titlePosition.x();
  346. const auto y = _subtitle
  347. ? _st.titleWithSubtitlePosition.y()
  348. : _st.titlePosition.y();
  349. _title->moveToLeft(x, y, newWidth);
  350. if (_subtitle) {
  351. _subtitle->moveToLeft(
  352. _back ? _st.back.width : _st.subtitlePosition.x(),
  353. _st.subtitlePosition.y(),
  354. newWidth);
  355. }
  356. }
  357. }
  358. void TopBar::updateSelectionControlsGeometry(int newWidth) {
  359. if (!_selectionText) {
  360. return;
  361. }
  362. auto right = _st.mediaActionsSkip;
  363. if (_canDelete) {
  364. _delete->moveToRight(right, 0, newWidth);
  365. right += _delete->width();
  366. }
  367. if (_canToggleStoryPin) {
  368. _toggleStoryInProfile->moveToRight(right, 0, newWidth);
  369. right += _toggleStoryInProfile->width();
  370. _toggleStoryPin->moveToRight(right, 0, newWidth);
  371. right += _toggleStoryPin->width();
  372. }
  373. if (_canForward) {
  374. _forward->moveToRight(right, 0, newWidth);
  375. right += _forward->width();
  376. }
  377. auto left = 0;
  378. _cancelSelection->moveToLeft(left, 0);
  379. left += _cancelSelection->width();
  380. const auto top = 0;
  381. const auto availableWidth = newWidth - left - right;
  382. _selectionText->resizeToNaturalWidth(availableWidth);
  383. _selectionText->moveToLeft(
  384. left,
  385. top,
  386. newWidth);
  387. }
  388. void TopBar::updateStoriesGeometry(int newWidth) {
  389. if (!_stories) {
  390. return;
  391. }
  392. auto right = 0;
  393. for (auto &button : _buttons) {
  394. if (!button) {
  395. continue;
  396. }
  397. button->moveToRight(right, 0, newWidth);
  398. right += button->width();
  399. }
  400. const auto &small = st::dialogsStories;
  401. const auto wrapLeft = (_back ? _st.back.width : 0);
  402. const auto left = _back
  403. ? 0
  404. : (_st.titlePosition.x() - small.left - small.photoLeft);
  405. const auto height = small.photo + 2 * small.photoTop;
  406. const auto top = _st.titlePosition.y()
  407. + (_st.title.style.font->height - height) / 2;
  408. _stories->setLayoutConstraints({ left, top }, style::al_left);
  409. _storiesWrap->move(wrapLeft, 0);
  410. }
  411. void TopBar::paintEvent(QPaintEvent *e) {
  412. auto p = QPainter(this);
  413. auto highlight = _a_highlight.value(_highlight ? 1. : 0.);
  414. if (_highlight && !_a_highlight.animating()) {
  415. _highlight = false;
  416. startHighlightAnimation();
  417. }
  418. if (!_roundRect) {
  419. const auto brush = anim::brush(_st.bg, _st.highlightBg, highlight);
  420. p.fillRect(e->rect(), brush);
  421. } else if (highlight > 0.) {
  422. p.setPen(Qt::NoPen);
  423. p.setBrush(anim::brush(_st.bg, _st.highlightBg, highlight));
  424. p.drawRoundedRect(
  425. rect() + style::margins(0, 0, 0, _st.radius * 2),
  426. _st.radius,
  427. _st.radius);
  428. } else {
  429. _roundRect->paintSomeRounded(
  430. p,
  431. rect(),
  432. RectPart::TopLeft | RectPart::TopRight);
  433. }
  434. }
  435. void TopBar::highlight() {
  436. _highlight = true;
  437. startHighlightAnimation();
  438. }
  439. void TopBar::startHighlightAnimation() {
  440. _a_highlight.start(
  441. [this] { update(); },
  442. _highlight ? 0. : 1.,
  443. _highlight ? 1. : 0.,
  444. _st.highlightDuration);
  445. }
  446. void TopBar::updateControlsVisibility(anim::type animated) {
  447. for (auto i = _updateControlCallbacks.begin(); i != _updateControlCallbacks.end();) {
  448. auto &&[widget, callback] = *i;
  449. if (!callback(animated)) {
  450. i = _updateControlCallbacks.erase(i);
  451. } else {
  452. ++i;
  453. }
  454. }
  455. }
  456. void TopBar::setStories(rpl::producer<Dialogs::Stories::Content> content) {
  457. _storiesLifetime.destroy();
  458. delete _storiesWrap.data();
  459. if (content) {
  460. using namespace Dialogs::Stories;
  461. auto last = std::move(
  462. content
  463. ) | rpl::start_spawning(_storiesLifetime);
  464. _storiesWrap = _storiesLifetime.make_state<
  465. Ui::FadeWrap<Ui::AbstractButton>
  466. >(this, object_ptr<Ui::AbstractButton>(this), st::infoTopBarScale);
  467. registerToggleControlCallback(
  468. _storiesWrap.data(),
  469. [this] { return _storiesCount > 0; });
  470. _storiesWrap->toggle(false, anim::type::instant);
  471. _storiesWrap->setDuration(st::infoTopBarDuration);
  472. const auto button = _storiesWrap->entity();
  473. const auto stories = Ui::CreateChild<List>(
  474. button,
  475. st::dialogsStoriesListInfo,
  476. rpl::duplicate(
  477. last
  478. ) | rpl::filter([](const Content &content) {
  479. return !content.elements.empty();
  480. }));
  481. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  482. button,
  483. QString(),
  484. _st.title);
  485. stories->setAttribute(Qt::WA_TransparentForMouseEvents);
  486. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  487. stories->geometryValue(
  488. ) | rpl::start_with_next([=](QRect geometry) {
  489. const auto skip = _st.title.style.font->spacew;
  490. label->move(
  491. geometry.x() + geometry.width() + skip,
  492. _st.titlePosition.y());
  493. }, label->lifetime());
  494. rpl::combine(
  495. _storiesWrap->positionValue(),
  496. label->geometryValue()
  497. ) | rpl::start_with_next([=] {
  498. button->resize(
  499. label->x() + label->width() + _st.titlePosition.x(),
  500. _st.height);
  501. }, button->lifetime());
  502. _stories = stories;
  503. _stories->clicks(
  504. ) | rpl::start_to_stream(_storyClicks, _stories->lifetime());
  505. button->setClickedCallback([=] {
  506. _storyClicks.fire({});
  507. });
  508. rpl::duplicate(
  509. last
  510. ) | rpl::start_with_next([=](const Content &content) {
  511. const auto count = content.total;
  512. if (_storiesCount != count) {
  513. const auto was = (_storiesCount > 0);
  514. _storiesCount = count;
  515. const auto now = (_storiesCount > 0);
  516. if (was != now) {
  517. updateControlsVisibility(anim::type::normal);
  518. }
  519. if (now) {
  520. label->setText(
  521. tr::lng_contacts_stories_status(
  522. tr::now,
  523. lt_count,
  524. _storiesCount));
  525. }
  526. updateControlsGeometry(width());
  527. }
  528. }, _storiesLifetime);
  529. _storiesLifetime.add([weak = QPointer<QWidget>(label)] {
  530. delete weak.data();
  531. });
  532. } else {
  533. _storiesCount = 0;
  534. }
  535. updateControlsVisibility(anim::type::instant);
  536. }
  537. void TopBar::setStoriesArchive(bool archive) {
  538. _storiesArchive = archive;
  539. }
  540. void TopBar::setSelectedItems(SelectedItems &&items) {
  541. auto wasSelectionMode = selectionMode();
  542. _selectedItems = std::move(items);
  543. if (selectionMode()) {
  544. if (_selectionText) {
  545. updateSelectionState();
  546. if (!wasSelectionMode) {
  547. _selectionText->entity()->finishAnimating();
  548. }
  549. } else {
  550. createSelectionControls();
  551. }
  552. }
  553. updateControlsVisibility(anim::type::normal);
  554. }
  555. SelectedItems TopBar::takeSelectedItems() {
  556. _canDelete = false;
  557. _canForward = false;
  558. return std::move(_selectedItems);
  559. }
  560. rpl::producer<SelectionAction> TopBar::selectionActionRequests() const {
  561. return _selectionActionRequests.events();
  562. }
  563. void TopBar::updateSelectionState() {
  564. Expects(_selectionText
  565. && _delete
  566. && _forward
  567. && _toggleStoryInProfile
  568. && _toggleStoryPin);
  569. _canDelete = computeCanDelete();
  570. _canForward = computeCanForward();
  571. _canUnpinStories = computeCanUnpinStories();
  572. _selectionText->entity()->setValue(generateSelectedText());
  573. _delete->toggle(_canDelete, anim::type::instant);
  574. _forward->toggle(_canForward, anim::type::instant);
  575. _toggleStoryInProfile->toggle(_canToggleStoryPin, anim::type::instant);
  576. _toggleStoryPin->toggle(_canToggleStoryPin, anim::type::instant);
  577. _toggleStoryPin->entity()->setIconOverride(
  578. _canUnpinStories ? &_st.storiesUnpin.icon : nullptr,
  579. _canUnpinStories ? &_st.storiesUnpin.iconOver : nullptr);
  580. updateSelectionControlsGeometry(width());
  581. }
  582. void TopBar::createSelectionControls() {
  583. auto wrap = [&](auto created) {
  584. registerToggleControlCallback(
  585. created,
  586. [this] { return selectionMode(); });
  587. created->toggle(false, anim::type::instant);
  588. return created;
  589. };
  590. _canDelete = computeCanDelete();
  591. _canForward = computeCanForward();
  592. _canUnpinStories = computeCanUnpinStories();
  593. _canToggleStoryPin = computeCanToggleStoryPin();
  594. _cancelSelection = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
  595. this,
  596. object_ptr<Ui::IconButton>(this, _st.mediaCancel),
  597. st::infoTopBarScale));
  598. _cancelSelection->setDuration(st::infoTopBarDuration);
  599. _cancelSelection->entity()->clicks(
  600. ) | rpl::map_to(
  601. SelectionAction::Clear
  602. ) | rpl::start_to_stream(
  603. _selectionActionRequests,
  604. _cancelSelection->lifetime());
  605. _selectionText = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::LabelWithNumbers>>(
  606. this,
  607. object_ptr<Ui::LabelWithNumbers>(
  608. this,
  609. _st.title,
  610. _st.titlePosition.y(),
  611. generateSelectedText()),
  612. st::infoTopBarScale));
  613. _selectionText->setDuration(st::infoTopBarDuration);
  614. _selectionText->entity()->resize(0, _st.height);
  615. _forward = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
  616. this,
  617. object_ptr<Ui::IconButton>(this, _st.mediaForward),
  618. st::infoTopBarScale));
  619. registerToggleControlCallback(
  620. _forward.data(),
  621. [this] { return selectionMode() && _canForward; });
  622. _forward->setDuration(st::infoTopBarDuration);
  623. _forward->entity()->clicks(
  624. ) | rpl::map_to(
  625. SelectionAction::Forward
  626. ) | rpl::start_to_stream(
  627. _selectionActionRequests,
  628. _cancelSelection->lifetime());
  629. _forward->entity()->setVisible(_canForward);
  630. _delete = wrap(Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
  631. this,
  632. object_ptr<Ui::IconButton>(this, _st.mediaDelete),
  633. st::infoTopBarScale));
  634. registerToggleControlCallback(
  635. _delete.data(),
  636. [this] { return selectionMode() && _canDelete; });
  637. _delete->setDuration(st::infoTopBarDuration);
  638. _delete->entity()->clicks(
  639. ) | rpl::map_to(
  640. SelectionAction::Delete
  641. ) | rpl::start_to_stream(
  642. _selectionActionRequests,
  643. _cancelSelection->lifetime());
  644. _delete->entity()->setVisible(_canDelete);
  645. _toggleStoryInProfile = wrap(
  646. Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
  647. this,
  648. object_ptr<Ui::IconButton>(
  649. this,
  650. _storiesArchive ? _st.storiesSave : _st.storiesArchive),
  651. st::infoTopBarScale));
  652. registerToggleControlCallback(
  653. _toggleStoryInProfile.data(),
  654. [this] { return selectionMode() && _canToggleStoryPin; });
  655. _toggleStoryInProfile->setDuration(st::infoTopBarDuration);
  656. _toggleStoryInProfile->entity()->clicks(
  657. ) | rpl::map_to(
  658. SelectionAction::ToggleStoryInProfile
  659. ) | rpl::start_to_stream(
  660. _selectionActionRequests,
  661. _cancelSelection->lifetime());
  662. _toggleStoryInProfile->entity()->setVisible(_canToggleStoryPin);
  663. _toggleStoryPin = wrap(
  664. Ui::CreateChild<Ui::FadeWrap<Ui::IconButton>>(
  665. this,
  666. object_ptr<Ui::IconButton>(
  667. this,
  668. _st.storiesPin),
  669. st::infoTopBarScale));
  670. if (_canUnpinStories) {
  671. _toggleStoryPin->entity()->setIconOverride(
  672. _canUnpinStories ? &_st.storiesUnpin.icon : nullptr,
  673. _canUnpinStories ? &_st.storiesUnpin.iconOver : nullptr);
  674. }
  675. registerToggleControlCallback(
  676. _toggleStoryPin.data(),
  677. [this] { return selectionMode() && _canToggleStoryPin; });
  678. _toggleStoryPin->setDuration(st::infoTopBarDuration);
  679. _toggleStoryPin->entity()->clicks(
  680. ) | rpl::map_to(
  681. SelectionAction::ToggleStoryPin
  682. ) | rpl::start_to_stream(
  683. _selectionActionRequests,
  684. _cancelSelection->lifetime());
  685. _toggleStoryPin->entity()->setVisible(_canToggleStoryPin);
  686. updateControlsGeometry(width());
  687. }
  688. bool TopBar::computeCanDelete() const {
  689. return ranges::all_of(_selectedItems.list, &SelectedItem::canDelete);
  690. }
  691. bool TopBar::computeCanForward() const {
  692. return ranges::all_of(_selectedItems.list, &SelectedItem::canForward);
  693. }
  694. bool TopBar::computeCanUnpinStories() const {
  695. return ranges::any_of(_selectedItems.list, &SelectedItem::canUnpinStory);
  696. }
  697. bool TopBar::computeCanToggleStoryPin() const {
  698. return ranges::all_of(
  699. _selectedItems.list,
  700. &SelectedItem::canToggleStoryPin);
  701. }
  702. Ui::StringWithNumbers TopBar::generateSelectedText() const {
  703. return _selectedItems.title(_selectedItems.list.size());
  704. }
  705. bool TopBar::selectionMode() const {
  706. return !_selectedItems.list.empty();
  707. }
  708. bool TopBar::storiesTitle() const {
  709. return _storiesCount > 0;
  710. }
  711. bool TopBar::searchMode() const {
  712. return _searchModeAvailable && _searchModeEnabled;
  713. }
  714. void TopBar::performForward() {
  715. _selectionActionRequests.fire(SelectionAction::Forward);
  716. }
  717. void TopBar::performDelete() {
  718. _selectionActionRequests.fire(SelectionAction::Delete);
  719. }
  720. } // namespace Info