choose_theme_controller.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  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 "ui/chat/choose_theme_controller.h"
  8. #include "boxes/background_box.h"
  9. #include "ui/rp_widget.h"
  10. #include "ui/widgets/shadow.h"
  11. #include "ui/widgets/labels.h"
  12. #include "ui/widgets/buttons.h"
  13. #include "ui/chat/chat_theme.h"
  14. #include "ui/chat/message_bubble.h"
  15. #include "ui/wrap/vertical_layout.h"
  16. #include "ui/painter.h"
  17. #include "main/main_session.h"
  18. #include "window/window_session_controller.h"
  19. #include "window/themes/window_theme.h"
  20. #include "data/data_session.h"
  21. #include "data/data_peer.h"
  22. #include "data/data_cloud_themes.h"
  23. #include "data/data_document.h"
  24. #include "data/data_document_media.h"
  25. #include "lang/lang_keys.h"
  26. #include "apiwrap.h"
  27. #include "styles/style_widgets.h"
  28. #include "styles/style_layers.h" // boxTitle.
  29. #include "styles/style_settings.h"
  30. #include "styles/style_window.h"
  31. #include <QtWidgets/QApplication>
  32. namespace Ui {
  33. namespace {
  34. const auto kDisableElement = [] { return u"disable"_q; };
  35. [[nodiscard]] QImage GeneratePreview(not_null<Ui::ChatTheme*> theme) {
  36. const auto &background = theme->background();
  37. const auto &colors = background.colors;
  38. const auto size = st::chatThemePreviewSize;
  39. auto prepared = background.prepared;
  40. const auto paintPattern = [&](QPainter &p, bool inverted) {
  41. if (prepared.isNull()) {
  42. return;
  43. }
  44. const auto w = prepared.width();
  45. const auto h = prepared.height();
  46. const auto scaled = size.scaled(
  47. st::windowMinWidth / 2,
  48. st::windowMinHeight / 2,
  49. Qt::KeepAspectRatio);
  50. const auto use = (scaled.width() > w || scaled.height() > h)
  51. ? scaled.scaled({ w, h }, Qt::KeepAspectRatio)
  52. : scaled;
  53. const auto good = QSize(
  54. std::max(use.width(), 1),
  55. std::max(use.height(), 1));
  56. auto small = prepared.copy(QRect(
  57. QPoint(
  58. (w - good.width()) / 2,
  59. (h - good.height()) / 2),
  60. good));
  61. if (inverted) {
  62. small = Ui::InvertPatternImage(std::move(small));
  63. }
  64. p.drawImage(
  65. QRect(QPoint(), size * style::DevicePixelRatio()),
  66. small);
  67. };
  68. const auto fullsize = size * style::DevicePixelRatio();
  69. auto result = background.waitingForNegativePattern()
  70. ? QImage(
  71. fullsize,
  72. QImage::Format_ARGB32_Premultiplied)
  73. : Ui::GenerateBackgroundImage(
  74. fullsize,
  75. colors.empty() ? std::vector{ 1, QColor(0, 0, 0) } : colors,
  76. background.gradientRotation,
  77. background.patternOpacity,
  78. paintPattern);
  79. if (background.waitingForNegativePattern()) {
  80. result.fill(Qt::black);
  81. }
  82. result.setDevicePixelRatio(style::DevicePixelRatio());
  83. {
  84. auto p = QPainter(&result);
  85. const auto sent = QRect(
  86. QPoint(
  87. (size.width()
  88. - st::chatThemeBubbleSize.width()
  89. - st::chatThemeBubblePosition.x()),
  90. st::chatThemeBubblePosition.y()),
  91. st::chatThemeBubbleSize);
  92. const auto received = QRect(
  93. st::chatThemeBubblePosition.x(),
  94. sent.y() + sent.height() + st::chatThemeBubbleSkip,
  95. sent.width(),
  96. sent.height());
  97. const auto radius = st::chatThemeBubbleRadius;
  98. PainterHighQualityEnabler hq(p);
  99. p.setPen(Qt::NoPen);
  100. if (const auto pattern = theme->bubblesBackgroundPattern()) {
  101. auto bubble = pattern->pixmap.toImage().scaled(
  102. sent.size() * style::DevicePixelRatio(),
  103. Qt::IgnoreAspectRatio,
  104. Qt::SmoothTransformation
  105. ).convertToFormat(QImage::Format_ARGB32_Premultiplied);
  106. const auto corners = Images::CornersMask(radius);
  107. p.drawImage(sent, Images::Round(std::move(bubble), corners));
  108. } else {
  109. p.setBrush(theme->palette()->msgOutBg()->c);
  110. p.drawRoundedRect(sent, radius, radius);
  111. }
  112. p.setBrush(theme->palette()->msgInBg()->c);
  113. p.drawRoundedRect(received, radius, radius);
  114. }
  115. return Images::Round(std::move(result), ImageRoundRadius::Large);
  116. }
  117. [[nodiscard]] QImage GenerateEmptyPreview() {
  118. auto result = QImage(
  119. st::chatThemePreviewSize * style::DevicePixelRatio(),
  120. QImage::Format_ARGB32_Premultiplied);
  121. result.fill(st::settingsThemeNotSupportedBg->c);
  122. result.setDevicePixelRatio(style::DevicePixelRatio());
  123. {
  124. auto p = QPainter(&result);
  125. p.setPen(st::menuIconFg);
  126. p.setFont(st::semiboldFont);
  127. const auto top = st::chatThemeEmptyPreviewTop;
  128. const auto width = st::chatThemePreviewSize.width();
  129. const auto height = st::chatThemePreviewSize.height() - top;
  130. p.drawText(
  131. QRect(0, top, width, height),
  132. tr::lng_chat_theme_none(tr::now),
  133. style::al_top);
  134. }
  135. return Images::Round(std::move(result), ImageRoundRadius::Large);
  136. }
  137. } // namespace
  138. struct ChooseThemeController::Entry {
  139. Ui::ChatThemeKey key;
  140. std::shared_ptr<Ui::ChatTheme> theme;
  141. std::shared_ptr<Data::DocumentMedia> media;
  142. QImage preview;
  143. EmojiPtr emoji = nullptr;
  144. QRect geometry;
  145. bool chosen = false;
  146. };
  147. ChooseThemeController::ChooseThemeController(
  148. not_null<RpWidget*> parent,
  149. not_null<Window::SessionController*> window,
  150. not_null<PeerData*> peer)
  151. : _controller(window)
  152. , _peer(peer)
  153. , _wrap(std::make_unique<VerticalLayout>(parent))
  154. , _topShadow(std::make_unique<PlainShadow>(parent))
  155. , _content(_wrap->add(object_ptr<RpWidget>(_wrap.get())))
  156. , _inner(CreateChild<RpWidget>(_content.get()))
  157. , _disabledEmoji(Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\x8c")))
  158. , _dark(Window::Theme::IsThemeDarkValue()) {
  159. init(parent->sizeValue());
  160. }
  161. ChooseThemeController::~ChooseThemeController() {
  162. _controller->clearPeerThemeOverride(_peer);
  163. }
  164. void ChooseThemeController::init(rpl::producer<QSize> outer) {
  165. using namespace rpl::mappers;
  166. const auto themes = &_controller->session().data().cloudThemes();
  167. const auto &list = themes->chatThemes();
  168. if (!list.empty()) {
  169. fill(list);
  170. } else {
  171. themes->refreshChatThemes();
  172. themes->chatThemesUpdated(
  173. ) | rpl::take(1) | rpl::start_with_next([=] {
  174. fill(themes->chatThemes());
  175. }, lifetime());
  176. }
  177. const auto titleWrap = _wrap->insert(
  178. 0,
  179. object_ptr<FixedHeightWidget>(
  180. _wrap.get(),
  181. st::boxTitle.style.font->height),
  182. st::chatThemeTitlePadding);
  183. auto title = CreateChild<FlatLabel>(
  184. titleWrap,
  185. tr::lng_chat_theme_title(),
  186. st::boxTitle);
  187. _wrap->paintRequest(
  188. ) | rpl::start_with_next([=](QRect clip) {
  189. QPainter(_wrap.get()).fillRect(clip, st::windowBg);
  190. }, lifetime());
  191. const auto close = Ui::CreateChild<Ui::IconButton>(
  192. _wrap.get(),
  193. st::boxTitleClose);
  194. close->setClickedCallback([=] { this->close(); });
  195. rpl::combine(
  196. _wrap->widthValue(),
  197. titleWrap->positionValue()
  198. ) | rpl::start_with_next([=](int width, QPoint position) {
  199. close->moveToRight(0, 0, width);
  200. }, close->lifetime());
  201. initButtons();
  202. initList();
  203. _inner->positionValue(
  204. ) | rpl::start_with_next([=](QPoint position) {
  205. title->move(std::max(position.x(), 0), 0);
  206. }, title->lifetime());
  207. std::move(
  208. outer
  209. ) | rpl::start_with_next([=](QSize outer) {
  210. _wrap->resizeToWidth(outer.width());
  211. _wrap->move(0, outer.height() - _wrap->height());
  212. const auto line = st::lineWidth;
  213. _topShadow->setGeometry(0, _wrap->y() - line, outer.width(), line);
  214. }, lifetime());
  215. rpl::combine(
  216. _shouldBeShown.value(),
  217. _forceHidden.value(),
  218. _1 && !_2
  219. ) | rpl::start_with_next([=](bool shown) {
  220. _wrap->setVisible(shown);
  221. _topShadow->setVisible(shown);
  222. }, lifetime());
  223. }
  224. void ChooseThemeController::initButtons() {
  225. const auto controls = _wrap->add(object_ptr<RpWidget>(_wrap.get()));
  226. const auto apply = CreateChild<RoundButton>(
  227. controls,
  228. tr::lng_chat_theme_apply(),
  229. st::defaultLightButton);
  230. apply->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  231. const auto choose = CreateChild<RoundButton>(
  232. controls,
  233. tr::lng_chat_theme_change_wallpaper(),
  234. st::defaultLightButton);
  235. choose->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  236. const auto &margin = st::chatThemeButtonMargin;
  237. controls->resize(
  238. margin.left() + choose->width() + margin.right(),
  239. margin.top() + choose->height() + margin.bottom());
  240. rpl::combine(
  241. controls->widthValue(),
  242. apply->widthValue(),
  243. choose->widthValue(),
  244. _chosen.value()
  245. ) | rpl::start_with_next([=](
  246. int outer,
  247. int applyWidth,
  248. int chooseWidth,
  249. QString chosen) {
  250. const auto was = _peer->themeEmoji();
  251. const auto now = (chosen == kDisableElement()) ? QString() : chosen;
  252. const auto changed = (now != was);
  253. apply->setVisible(changed);
  254. choose->setVisible(!changed);
  255. const auto shown = changed ? apply : choose;
  256. const auto shownWidth = changed ? applyWidth : chooseWidth;
  257. const auto inner = margin.left() + shownWidth + margin.right();
  258. const auto left = (outer - inner) / 2;
  259. shown->moveToLeft(left, margin.top());
  260. }, controls->lifetime());
  261. apply->setClickedCallback([=] {
  262. if (const auto chosen = findChosen()) {
  263. const auto was = _peer->themeEmoji();
  264. const auto now = chosen->key ? _chosen.current() : QString();
  265. if (was != now) {
  266. _peer->setThemeEmoji(now);
  267. const auto dropWallPaper = (_peer->wallPaper() != nullptr);
  268. if (dropWallPaper) {
  269. _peer->setWallPaper({});
  270. }
  271. if (chosen->theme) {
  272. // Remember while changes propagate through event loop.
  273. _controller->pushLastUsedChatTheme(chosen->theme);
  274. }
  275. const auto api = &_peer->session().api();
  276. api->request(MTPmessages_SetChatWallPaper(
  277. MTP_flags(0),
  278. _peer->input,
  279. MTPInputWallPaper(),
  280. MTPWallPaperSettings(),
  281. MTPint()
  282. )).afterDelay(10).done([=](const MTPUpdates &result) {
  283. api->applyUpdates(result);
  284. }).send();
  285. api->request(MTPmessages_SetChatTheme(
  286. _peer->input,
  287. MTP_string(now)
  288. )).done([=](const MTPUpdates &result) {
  289. api->applyUpdates(result);
  290. }).send();
  291. }
  292. }
  293. _controller->toggleChooseChatTheme(_peer);
  294. });
  295. choose->setClickedCallback([=] {
  296. _controller->show(Box<BackgroundBox>(_controller, _peer));
  297. });
  298. }
  299. void ChooseThemeController::paintEntry(QPainter &p, const Entry &entry) {
  300. const auto geometry = entry.geometry;
  301. p.drawImage(geometry, entry.preview);
  302. const auto size = Ui::Emoji::GetSizeLarge();
  303. const auto factor = style::DevicePixelRatio();
  304. const auto emojiLeft = geometry.x()
  305. + (geometry.width() - (size / factor)) / 2;
  306. const auto emojiTop = geometry.y()
  307. + geometry.height()
  308. - (size / factor)
  309. - st::chatThemeEmojiBottom;
  310. Ui::Emoji::Draw(p, entry.emoji, size, emojiLeft, emojiTop);
  311. if (entry.chosen) {
  312. auto hq = PainterHighQualityEnabler(p);
  313. auto pen = st::activeLineFg->p;
  314. const auto width = st::defaultInputField.borderActive;
  315. pen.setWidth(width);
  316. p.setPen(pen);
  317. const auto add = st::lineWidth + width;
  318. p.drawRoundedRect(
  319. entry.geometry.marginsAdded({ add, add, add, add }),
  320. st::roundRadiusLarge + add,
  321. st::roundRadiusLarge + add);
  322. }
  323. }
  324. void ChooseThemeController::initList() {
  325. _content->resize(
  326. _content->width(),
  327. (st::chatThemeEntryMargin.top()
  328. + st::chatThemePreviewSize.height()
  329. + st::chatThemeEntryMargin.bottom()));
  330. _inner->setMouseTracking(true);
  331. _inner->paintRequest(
  332. ) | rpl::start_with_next([=](QRect clip) {
  333. auto p = QPainter(_inner.get());
  334. for (const auto &entry : _entries) {
  335. if (entry.preview.isNull() || !clip.intersects(entry.geometry)) {
  336. continue;
  337. }
  338. paintEntry(p, entry);
  339. }
  340. }, lifetime());
  341. const auto byPoint = [=](QPoint position) -> Entry* {
  342. for (auto &entry : _entries) {
  343. if (entry.geometry.contains(position)) {
  344. return &entry;
  345. }
  346. }
  347. return nullptr;
  348. };
  349. const auto chosenText = [=](const Entry *entry) {
  350. if (!entry) {
  351. return QString();
  352. } else if (entry->key) {
  353. return entry->emoji->text();
  354. } else {
  355. return kDisableElement();
  356. }
  357. };
  358. _inner->events(
  359. ) | rpl::start_with_next([=](not_null<QEvent*> event) {
  360. const auto type = event->type();
  361. if (type == QEvent::MouseMove) {
  362. const auto mouse = static_cast<QMouseEvent*>(event.get());
  363. const auto skip = _inner->width() - _content->width();
  364. if (skip <= 0) {
  365. _dragStartPosition = _pressPosition = std::nullopt;
  366. } else if (_pressPosition.has_value()
  367. && ((mouse->globalPos() - *_pressPosition).manhattanLength()
  368. >= QApplication::startDragDistance())) {
  369. _dragStartPosition = base::take(_pressPosition);
  370. _dragStartInnerLeft = _inner->x();
  371. }
  372. if (_dragStartPosition.has_value()) {
  373. const auto shift = mouse->globalPos().x()
  374. - _dragStartPosition->x();
  375. updateInnerLeft(_dragStartInnerLeft + shift);
  376. } else {
  377. _inner->setCursor(byPoint(mouse->pos())
  378. ? style::cur_pointer
  379. : style::cur_default);
  380. }
  381. } else if (type == QEvent::MouseButtonPress) {
  382. const auto mouse = static_cast<QMouseEvent*>(event.get());
  383. if (mouse->button() == Qt::LeftButton) {
  384. _pressPosition = mouse->globalPos();
  385. }
  386. _pressed = chosenText(byPoint(mouse->pos()));
  387. } else if (type == QEvent::MouseButtonRelease) {
  388. _pressPosition = _dragStartPosition = std::nullopt;
  389. const auto mouse = static_cast<QMouseEvent*>(event.get());
  390. const auto entry = byPoint(mouse->pos());
  391. const auto chosen = chosenText(entry);
  392. if (entry && chosen == _pressed && chosen != _chosen.current()) {
  393. clearCurrentBackgroundState();
  394. if (const auto was = findChosen()) {
  395. was->chosen = false;
  396. }
  397. _chosen = chosen;
  398. entry->chosen = true;
  399. if (entry->theme || !entry->key) {
  400. _controller->overridePeerTheme(
  401. _peer,
  402. entry->theme,
  403. entry->emoji);
  404. }
  405. _inner->update();
  406. }
  407. _pressed = QString();
  408. } else if (type == QEvent::Wheel) {
  409. const auto wheel = static_cast<QWheelEvent*>(event.get());
  410. const auto was = _inner->x();
  411. updateInnerLeft((wheel->angleDelta().x() != 0)
  412. ? (was + (wheel->pixelDelta().x()
  413. ? wheel->pixelDelta().x()
  414. : wheel->angleDelta().x()))
  415. : (wheel->angleDelta().y() != 0)
  416. ? (was + (wheel->pixelDelta().y()
  417. ? wheel->pixelDelta().y()
  418. : wheel->angleDelta().y()))
  419. : was);
  420. }
  421. }, lifetime());
  422. _content->events(
  423. ) | rpl::start_with_next([=](not_null<QEvent*> event) {
  424. const auto type = event->type();
  425. if (type == QEvent::KeyPress) {
  426. const auto key = static_cast<QKeyEvent*>(event.get());
  427. if (key->key() == Qt::Key_Escape) {
  428. close();
  429. }
  430. }
  431. }, lifetime());
  432. rpl::combine(
  433. _content->widthValue(),
  434. _inner->widthValue()
  435. ) | rpl::start_with_next([=](int content, int inner) {
  436. if (!content || !inner) {
  437. return;
  438. } else if (!_entries.empty() && !_initialInnerLeftApplied) {
  439. applyInitialInnerLeft();
  440. } else {
  441. updateInnerLeft(_inner->x());
  442. }
  443. }, lifetime());
  444. }
  445. void ChooseThemeController::applyInitialInnerLeft() {
  446. if (const auto chosen = findChosen()) {
  447. updateInnerLeft(
  448. _content->width() / 2 - chosen->geometry.center().x());
  449. }
  450. _initialInnerLeftApplied = true;
  451. }
  452. void ChooseThemeController::updateInnerLeft(int now) {
  453. const auto skip = _content->width() - _inner->width();
  454. const auto clamped = (skip >= 0)
  455. ? (skip / 2)
  456. : std::clamp(now, skip, 0);
  457. _inner->move(clamped, 0);
  458. }
  459. void ChooseThemeController::close() {
  460. if (const auto chosen = findChosen()) {
  461. if (Ui::Emoji::Find(_peer->themeEmoji()) != chosen->emoji) {
  462. clearCurrentBackgroundState();
  463. }
  464. }
  465. _controller->toggleChooseChatTheme(_peer);
  466. }
  467. void ChooseThemeController::clearCurrentBackgroundState() {
  468. if (const auto entry = findChosen()) {
  469. if (entry->theme) {
  470. entry->theme->clearBackgroundState();
  471. }
  472. }
  473. }
  474. auto ChooseThemeController::findChosen() -> Entry* {
  475. const auto chosen = _chosen.current();
  476. if (chosen.isEmpty()) {
  477. return nullptr;
  478. }
  479. for (auto &entry : _entries) {
  480. if (!entry.key && chosen == kDisableElement()) {
  481. return &entry;
  482. } else if (chosen == entry.emoji->text()) {
  483. return &entry;
  484. }
  485. }
  486. return nullptr;
  487. }
  488. auto ChooseThemeController::findChosen() const -> const Entry* {
  489. return const_cast<ChooseThemeController*>(this)->findChosen();
  490. }
  491. void ChooseThemeController::fill(
  492. const std::vector<Data::CloudTheme> &themes) {
  493. if (themes.empty()) {
  494. return;
  495. }
  496. const auto count = int(themes.size()) + 1;
  497. const auto single = st::chatThemePreviewSize;
  498. const auto skip = st::chatThemeEntrySkip;
  499. const auto &margin = st::chatThemeEntryMargin;
  500. const auto full = margin.left()
  501. + single.width() * count
  502. + skip * (count - 1)
  503. + margin.right();
  504. _inner->resize(
  505. full,
  506. margin.top() + single.height() + margin.bottom());
  507. const auto initial = Ui::Emoji::Find(_peer->themeEmoji());
  508. if (!initial) {
  509. _chosen = kDisableElement();
  510. }
  511. _dark.value(
  512. ) | rpl::start_with_next([=](bool dark) {
  513. clearCurrentBackgroundState();
  514. if (_chosen.current().isEmpty() && initial) {
  515. _chosen = initial->text();
  516. }
  517. _cachingLifetime.destroy();
  518. const auto old = base::take(_entries);
  519. auto x = margin.left();
  520. _entries.push_back({
  521. .preview = GenerateEmptyPreview(),
  522. .emoji = _disabledEmoji,
  523. .geometry = QRect(QPoint(x, margin.top()), single),
  524. .chosen = (_chosen.current() == kDisableElement()),
  525. });
  526. Assert(_entries.front().emoji != nullptr);
  527. style::PaletteChanged(
  528. ) | rpl::start_with_next([=] {
  529. _entries.front().preview = GenerateEmptyPreview();
  530. }, _cachingLifetime);
  531. const auto type = dark
  532. ? Data::CloudThemeType::Dark
  533. : Data::CloudThemeType::Light;
  534. x += single.width() + skip;
  535. for (const auto &theme : themes) {
  536. const auto emoji = Ui::Emoji::Find(theme.emoticon);
  537. if (!emoji || !theme.settings.contains(type)) {
  538. continue;
  539. }
  540. const auto key = ChatThemeKey{ theme.id, dark };
  541. const auto isChosen = (_chosen.current() == emoji->text());
  542. _entries.push_back({
  543. .key = key,
  544. .emoji = emoji,
  545. .geometry = QRect(QPoint(x, skip), single),
  546. .chosen = isChosen,
  547. });
  548. _controller->cachedChatThemeValue(
  549. theme,
  550. Data::WallPaper(0),
  551. type
  552. ) | rpl::filter([=](const std::shared_ptr<ChatTheme> &data) {
  553. return data && (data->key() == key);
  554. }) | rpl::take(
  555. 1
  556. ) | rpl::start_with_next([=](std::shared_ptr<ChatTheme> &&data) {
  557. const auto key = data->key();
  558. const auto i = ranges::find(_entries, key, &Entry::key);
  559. if (i == end(_entries)) {
  560. return;
  561. }
  562. const auto theme = data.get();
  563. i->theme = std::move(data);
  564. i->preview = GeneratePreview(theme);
  565. if (_chosen.current() == i->emoji->text()) {
  566. _controller->overridePeerTheme(
  567. _peer,
  568. i->theme,
  569. i->emoji);
  570. }
  571. _inner->update();
  572. if (!theme->background().isPattern
  573. || !theme->background().prepared.isNull()) {
  574. return;
  575. }
  576. // Subscribe to pattern loading if needed.
  577. theme->repaintBackgroundRequests(
  578. ) | rpl::filter([=] {
  579. const auto i = ranges::find(
  580. _entries,
  581. key,
  582. &Entry::key);
  583. return (i == end(_entries))
  584. || !i->theme->background().prepared.isNull();
  585. }) | rpl::take(1) | rpl::start_with_next([=] {
  586. const auto i = ranges::find(
  587. _entries,
  588. key,
  589. &Entry::key);
  590. if (i == end(_entries)) {
  591. return;
  592. }
  593. i->preview = GeneratePreview(theme);
  594. _inner->update();
  595. }, _cachingLifetime);
  596. }, _cachingLifetime);
  597. x += single.width() + skip;
  598. }
  599. if (!_initialInnerLeftApplied && _content->width() > 0) {
  600. applyInitialInnerLeft();
  601. }
  602. }, lifetime());
  603. _shouldBeShown = true;
  604. }
  605. bool ChooseThemeController::shouldBeShown() const {
  606. return _shouldBeShown.current();
  607. }
  608. rpl::producer<bool> ChooseThemeController::shouldBeShownValue() const {
  609. return _shouldBeShown.value();
  610. }
  611. int ChooseThemeController::height() const {
  612. return shouldBeShown() ? _wrap->height() : 0;
  613. }
  614. void ChooseThemeController::hide() {
  615. _forceHidden = true;
  616. }
  617. void ChooseThemeController::show() {
  618. _forceHidden = false;
  619. }
  620. void ChooseThemeController::raise() {
  621. _wrap->raise();
  622. _topShadow->raise();
  623. }
  624. void ChooseThemeController::setFocus() {
  625. _content->setFocus();
  626. }
  627. rpl::lifetime &ChooseThemeController::lifetime() {
  628. return _wrap->lifetime();
  629. }
  630. } // namespace Ui