background_box.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  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 "boxes/background_box.h"
  8. #include "lang/lang_keys.h"
  9. #include "ui/effects/round_checkbox.h"
  10. #include "ui/image/image.h"
  11. #include "ui/chat/attach/attach_extensions.h"
  12. #include "ui/chat/chat_theme.h"
  13. #include "ui/ui_utility.h"
  14. #include "ui/vertical_list.h"
  15. #include "main/main_session.h"
  16. #include "apiwrap.h"
  17. #include "mtproto/sender.h"
  18. #include "core/file_utilities.h"
  19. #include "data/data_peer.h"
  20. #include "data/data_session.h"
  21. #include "data/data_file_origin.h"
  22. #include "data/data_document.h"
  23. #include "data/data_document_media.h"
  24. #include "boxes/background_preview_box.h"
  25. #include "info/profile/info_profile_icon.h"
  26. #include "ui/boxes/confirm_box.h"
  27. #include "ui/widgets/buttons.h"
  28. #include "window/window_session_controller.h"
  29. #include "window/themes/window_theme.h"
  30. #include "styles/style_overview.h"
  31. #include "styles/style_layers.h"
  32. #include "styles/style_boxes.h"
  33. #include "styles/style_chat_helpers.h"
  34. #include "styles/style_info.h"
  35. namespace {
  36. constexpr auto kBackgroundsInRow = 3;
  37. QImage TakeMiddleSample(QImage original, QSize size) {
  38. size *= style::DevicePixelRatio();
  39. const auto from = original.size();
  40. if (from.isEmpty()) {
  41. auto result = original.scaled(size);
  42. result.setDevicePixelRatio(style::DevicePixelRatio());
  43. return result;
  44. }
  45. const auto take = (from.width() * size.height()
  46. > from.height() * size.width())
  47. ? QSize(size.width() * from.height() / size.height(), from.height())
  48. : QSize(from.width(), size.height() * from.width() / size.width());
  49. auto result = original.copy(
  50. (from.width() - take.width()) / 2,
  51. (from.height() - take.height()) / 2,
  52. take.width(),
  53. take.height()
  54. ).scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
  55. result.setDevicePixelRatio(style::DevicePixelRatio());
  56. return result;
  57. }
  58. } // namespace
  59. class BackgroundBox::Inner final : public Ui::RpWidget {
  60. public:
  61. Inner(
  62. QWidget *parent,
  63. not_null<Main::Session*> session,
  64. PeerData *forPeer);
  65. ~Inner();
  66. [[nodiscard]] rpl::producer<Data::WallPaper> chooseEvents() const;
  67. [[nodiscard]] rpl::producer<Data::WallPaper> removeRequests() const;
  68. [[nodiscard]] auto resolveResetCustomPaper() const
  69. ->std::optional<Data::WallPaper>;
  70. void removePaper(const Data::WallPaper &data);
  71. private:
  72. void paintEvent(QPaintEvent *e) override;
  73. void mouseMoveEvent(QMouseEvent *e) override;
  74. void mousePressEvent(QMouseEvent *e) override;
  75. void mouseReleaseEvent(QMouseEvent *e) override;
  76. void visibleTopBottomUpdated(
  77. int visibleTop,
  78. int visibleBottom) override;
  79. struct Paper {
  80. Data::WallPaper data;
  81. mutable std::shared_ptr<Data::DocumentMedia> dataMedia;
  82. mutable QPixmap thumbnail;
  83. };
  84. struct Selected {
  85. int index = 0;
  86. inline bool operator==(const Selected &other) const {
  87. return index == other.index;
  88. }
  89. inline bool operator!=(const Selected &other) const {
  90. return !(*this == other);
  91. }
  92. };
  93. struct DeleteSelected {
  94. int index = 0;
  95. inline bool operator==(const DeleteSelected &other) const {
  96. return index == other.index;
  97. }
  98. inline bool operator!=(const DeleteSelected &other) const {
  99. return !(*this == other);
  100. }
  101. };
  102. using Selection = std::variant<v::null_t, Selected, DeleteSelected>;
  103. int getSelectionIndex(const Selection &selection) const;
  104. void repaintPaper(int index);
  105. void resizeToContentAndPreload();
  106. void updatePapers();
  107. void requestPapers();
  108. void pushCustomPapers();
  109. void sortPapers();
  110. void paintPaper(
  111. QPainter &p,
  112. const Paper &paper,
  113. int column,
  114. int row) const;
  115. void validatePaperThumbnail(const Paper &paper) const;
  116. [[nodiscard]] bool forChannel() const;
  117. const not_null<Main::Session*> _session;
  118. PeerData * const _forPeer = nullptr;
  119. MTP::Sender _api;
  120. std::vector<Paper> _papers;
  121. uint64 _currentId = 0;
  122. uint64 _insertedResetId = 0;
  123. Selection _over;
  124. Selection _overDown;
  125. std::unique_ptr<Ui::RoundCheckbox> _check; // this is not a widget
  126. rpl::event_stream<Data::WallPaper> _backgroundChosen;
  127. rpl::event_stream<Data::WallPaper> _backgroundRemove;
  128. };
  129. BackgroundBox::BackgroundBox(
  130. QWidget*,
  131. not_null<Window::SessionController*> controller,
  132. PeerData *forPeer)
  133. : _controller(controller)
  134. , _forPeer(forPeer) {
  135. }
  136. void BackgroundBox::prepare() {
  137. setTitle(tr::lng_backgrounds_header());
  138. addButton(tr::lng_close(), [=] { closeBox(); });
  139. setDimensions(st::boxWideWidth, st::boxMaxListHeight);
  140. auto wrap = object_ptr<Ui::VerticalLayout>(this);
  141. const auto container = wrap.data();
  142. Ui::AddSkip(container);
  143. const auto button = container->add(object_ptr<Ui::SettingsButton>(
  144. container,
  145. tr::lng_settings_bg_from_file(),
  146. st::infoProfileButton));
  147. object_ptr<Info::Profile::FloatingIcon>(
  148. button,
  149. st::infoIconMediaPhoto,
  150. st::infoSharedMediaButtonIconPosition);
  151. if (forChannel() && _forPeer->wallPaper()) {
  152. const auto remove = container->add(object_ptr<Ui::SettingsButton>(
  153. container,
  154. tr::lng_settings_bg_remove(),
  155. st::infoBlockButton));
  156. object_ptr<Info::Profile::FloatingIcon>(
  157. remove,
  158. st::infoIconDeleteRed,
  159. st::infoSharedMediaButtonIconPosition);
  160. remove->setClickedCallback([=] {
  161. if (const auto resolved = _inner->resolveResetCustomPaper()) {
  162. chosen(*resolved);
  163. }
  164. });
  165. }
  166. button->setClickedCallback([=] {
  167. chooseFromFile();
  168. });
  169. Ui::AddSkip(container);
  170. Ui::AddDivider(container);
  171. _inner = container->add(
  172. object_ptr<Inner>(this, &_controller->session(), _forPeer));
  173. container->resizeToWidth(st::boxWideWidth);
  174. setInnerWidget(std::move(wrap), st::backgroundScroll);
  175. setInnerTopSkip(st::lineWidth);
  176. _inner->chooseEvents(
  177. ) | rpl::start_with_next([=](const Data::WallPaper &paper) {
  178. chosen(paper);
  179. }, _inner->lifetime());
  180. _inner->removeRequests(
  181. ) | rpl::start_with_next([=](const Data::WallPaper &paper) {
  182. removePaper(paper);
  183. }, _inner->lifetime());
  184. }
  185. void BackgroundBox::chooseFromFile() {
  186. const auto filterStart = _forPeer
  187. ? u"Image files (*"_q
  188. : u"Theme files (*.tdesktop-theme *.tdesktop-palette *"_q;
  189. auto filters = QStringList(
  190. filterStart
  191. + Ui::ImageExtensions().join(u" *"_q)
  192. + u")"_q);
  193. filters.push_back(FileDialog::AllFilesFilter());
  194. const auto callback = [=](const FileDialog::OpenResult &result) {
  195. if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
  196. return;
  197. }
  198. if (!_forPeer && !result.paths.isEmpty()) {
  199. const auto filePath = result.paths.front();
  200. const auto hasExtension = [&](QLatin1String extension) {
  201. return filePath.endsWith(extension, Qt::CaseInsensitive);
  202. };
  203. if (hasExtension(qstr(".tdesktop-theme"))
  204. || hasExtension(qstr(".tdesktop-palette"))) {
  205. Window::Theme::Apply(filePath);
  206. return;
  207. }
  208. }
  209. auto image = Images::Read({
  210. .path = result.paths.isEmpty() ? QString() : result.paths.front(),
  211. .content = result.remoteContent,
  212. .forceOpaque = true,
  213. }).image;
  214. if (image.isNull() || image.width() <= 0 || image.height() <= 0) {
  215. return;
  216. }
  217. auto local = Data::CustomWallPaper();
  218. local.setLocalImageAsThumbnail(std::make_shared<Image>(
  219. std::move(image)));
  220. _controller->show(Box<BackgroundPreviewBox>(
  221. _controller,
  222. local,
  223. BackgroundPreviewArgs{ _forPeer }));
  224. };
  225. FileDialog::GetOpenPath(
  226. this,
  227. tr::lng_choose_image(tr::now),
  228. filters.join(u";;"_q),
  229. crl::guard(this, callback));
  230. }
  231. bool BackgroundBox::hasDefaultForPeer() const {
  232. Expects(_forPeer != nullptr);
  233. const auto paper = _forPeer->wallPaper();
  234. if (!paper) {
  235. return true;
  236. }
  237. const auto reset = _inner->resolveResetCustomPaper();
  238. Assert(reset.has_value());
  239. return (paper->id() == reset->id());
  240. }
  241. bool BackgroundBox::chosenDefaultForPeer(
  242. const Data::WallPaper &paper) const {
  243. if (!_forPeer) {
  244. return false;
  245. }
  246. const auto reset = _inner->resolveResetCustomPaper();
  247. Assert(reset.has_value());
  248. return (paper.id() == reset->id());
  249. }
  250. void BackgroundBox::chosen(const Data::WallPaper &paper) {
  251. if (chosenDefaultForPeer(paper)) {
  252. if (!hasDefaultForPeer()) {
  253. const auto reset = crl::guard(this, [=](Fn<void()> close) {
  254. resetForPeer();
  255. close();
  256. });
  257. _controller->show(Ui::MakeConfirmBox({
  258. .text = tr::lng_background_sure_reset_default(),
  259. .confirmed = reset,
  260. .confirmText = tr::lng_background_reset_default(),
  261. }));
  262. } else {
  263. closeBox();
  264. }
  265. return;
  266. } else if (forChannel()) {
  267. if (_forPeer->wallPaper() && _forPeer->wallPaper()->equals(paper)) {
  268. closeBox();
  269. return;
  270. }
  271. const auto &themes = _forPeer->owner().cloudThemes();
  272. for (const auto &theme : themes.chatThemes()) {
  273. for (const auto &[type, themed] : theme.settings) {
  274. if (themed.paper && themed.paper->equals(paper)) {
  275. _controller->show(Box<BackgroundPreviewBox>(
  276. _controller,
  277. Data::WallPaper::FromEmojiId(theme.emoticon),
  278. BackgroundPreviewArgs{ _forPeer }));
  279. return;
  280. }
  281. }
  282. }
  283. }
  284. _controller->show(Box<BackgroundPreviewBox>(
  285. _controller,
  286. paper,
  287. BackgroundPreviewArgs{ _forPeer }));
  288. }
  289. void BackgroundBox::resetForPeer() {
  290. const auto api = &_controller->session().api();
  291. api->request(MTPmessages_SetChatWallPaper(
  292. MTP_flags(0),
  293. _forPeer->input,
  294. MTPInputWallPaper(),
  295. MTPWallPaperSettings(),
  296. MTPint()
  297. )).done([=](const MTPUpdates &result) {
  298. api->applyUpdates(result);
  299. }).send();
  300. const auto weak = Ui::MakeWeak(this);
  301. _forPeer->setWallPaper({});
  302. if (weak) {
  303. _controller->finishChatThemeEdit(_forPeer);
  304. }
  305. }
  306. bool BackgroundBox::forChannel() const {
  307. return _forPeer && _forPeer->isChannel();
  308. }
  309. void BackgroundBox::removePaper(const Data::WallPaper &paper) {
  310. const auto session = &_controller->session();
  311. const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) {
  312. close();
  313. if (weak) {
  314. weak->_inner->removePaper(paper);
  315. }
  316. session->data().removeWallpaper(paper);
  317. session->api().request(MTPaccount_SaveWallPaper(
  318. paper.mtpInput(session),
  319. MTP_bool(true),
  320. paper.mtpSettings()
  321. )).send();
  322. };
  323. _controller->show(Ui::MakeConfirmBox({
  324. .text = tr::lng_background_sure_delete(),
  325. .confirmed = remove,
  326. .confirmText = tr::lng_selected_delete(),
  327. }));
  328. }
  329. BackgroundBox::Inner::Inner(
  330. QWidget *parent,
  331. not_null<Main::Session*> session,
  332. PeerData *forPeer)
  333. : RpWidget(parent)
  334. , _session(session)
  335. , _forPeer(forPeer)
  336. , _api(&_session->mtp())
  337. , _check(
  338. std::make_unique<Ui::RoundCheckbox>(
  339. st::overviewCheck,
  340. [=] { update(); })) {
  341. _check->setChecked(true, anim::type::instant);
  342. resize(
  343. st::boxWideWidth,
  344. (2 * (st::backgroundSize.height() + st::backgroundPadding)
  345. + st::backgroundPadding));
  346. Window::Theme::IsNightModeValue(
  347. ) | rpl::start_with_next([=] {
  348. updatePapers();
  349. }, lifetime());
  350. requestPapers();
  351. _session->downloaderTaskFinished(
  352. ) | rpl::start_with_next([=] {
  353. update();
  354. }, lifetime());
  355. style::PaletteChanged(
  356. ) | rpl::start_with_next([=] {
  357. _check->invalidateCache();
  358. }, lifetime());
  359. if (forChannel()) {
  360. _session->data().cloudThemes().chatThemesUpdated(
  361. ) | rpl::start_with_next([=] {
  362. updatePapers();
  363. }, lifetime());
  364. } else {
  365. using Update = Window::Theme::BackgroundUpdate;
  366. Window::Theme::Background()->updates(
  367. ) | rpl::start_with_next([=](const Update &update) {
  368. if (update.type == Update::Type::New) {
  369. sortPapers();
  370. requestPapers();
  371. this->update();
  372. }
  373. }, lifetime());
  374. }
  375. setMouseTracking(true);
  376. }
  377. void BackgroundBox::Inner::requestPapers() {
  378. if (forChannel()) {
  379. _session->data().cloudThemes().refreshChatThemes();
  380. return;
  381. }
  382. _api.request(MTPaccount_GetWallPapers(
  383. MTP_long(_session->data().wallpapersHash())
  384. )).done([=](const MTPaccount_WallPapers &result) {
  385. if (_session->data().updateWallpapers(result)) {
  386. updatePapers();
  387. }
  388. }).send();
  389. }
  390. auto BackgroundBox::Inner::resolveResetCustomPaper() const
  391. -> std::optional<Data::WallPaper> {
  392. if (!_forPeer) {
  393. return {};
  394. }
  395. const auto nonCustom = Window::Theme::Background()->paper();
  396. const auto themeEmoji = _forPeer->themeEmoji();
  397. if (forChannel() || themeEmoji.isEmpty()) {
  398. return nonCustom;
  399. }
  400. const auto &themes = _forPeer->owner().cloudThemes();
  401. const auto theme = themes.themeForEmoji(themeEmoji);
  402. if (!theme) {
  403. return nonCustom;
  404. }
  405. using Type = Data::CloudTheme::Type;
  406. const auto dark = Window::Theme::IsNightMode();
  407. const auto i = theme->settings.find(dark ? Type::Dark : Type::Light);
  408. if (i != end(theme->settings) && i->second.paper) {
  409. return *i->second.paper;
  410. }
  411. return nonCustom;
  412. }
  413. void BackgroundBox::Inner::pushCustomPapers() {
  414. auto customId = uint64();
  415. if (const auto custom = _forPeer ? _forPeer->wallPaper() : nullptr) {
  416. customId = custom->id();
  417. const auto j = ranges::find(
  418. _papers,
  419. custom->id(),
  420. [](const Paper &paper) { return paper.data.id(); });
  421. if (j != end(_papers)) {
  422. j->data = j->data.withParamsFrom(*custom);
  423. } else {
  424. _papers.insert(begin(_papers), Paper{ *custom });
  425. }
  426. }
  427. if (const auto reset = resolveResetCustomPaper()) {
  428. _insertedResetId = reset->id();
  429. const auto j = ranges::find(
  430. _papers,
  431. _insertedResetId,
  432. [](const Paper &paper) { return paper.data.id(); });
  433. if (j != end(_papers)) {
  434. if (_insertedResetId != customId) {
  435. j->data = j->data.withParamsFrom(*reset);
  436. }
  437. } else {
  438. _papers.insert(begin(_papers), Paper{ *reset });
  439. }
  440. }
  441. }
  442. void BackgroundBox::Inner::sortPapers() {
  443. Expects(!forChannel());
  444. const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr;
  445. _currentId = currentCustom
  446. ? currentCustom->id()
  447. : _insertedResetId
  448. ? _insertedResetId
  449. : Window::Theme::Background()->id();
  450. const auto dark = Window::Theme::IsNightMode();
  451. ranges::stable_sort(_papers, std::greater<>(), [&](const Paper &paper) {
  452. const auto &data = paper.data;
  453. return std::make_tuple(
  454. _insertedResetId && (_insertedResetId == data.id()),
  455. data.id() == _currentId,
  456. dark ? data.isDark() : !data.isDark(),
  457. Data::IsDefaultWallPaper(data),
  458. !data.isDefault() && !Data::IsLegacy1DefaultWallPaper(data),
  459. Data::IsLegacy3DefaultWallPaper(data),
  460. Data::IsLegacy2DefaultWallPaper(data),
  461. Data::IsLegacy1DefaultWallPaper(data));
  462. });
  463. if (!_papers.empty()
  464. && _papers.front().data.id() == _currentId
  465. && !currentCustom
  466. && !_insertedResetId) {
  467. _papers.front().data = _papers.front().data.withParamsFrom(
  468. Window::Theme::Background()->paper());
  469. }
  470. }
  471. void BackgroundBox::Inner::updatePapers() {
  472. if (forChannel()) {
  473. if (_session->data().cloudThemes().chatThemes().empty()) {
  474. return;
  475. }
  476. } else {
  477. if (_session->data().wallpapers().empty()) {
  478. return;
  479. }
  480. }
  481. _over = _overDown = Selection();
  482. const auto was = base::take(_papers);
  483. if (forChannel()) {
  484. const auto now = _forPeer->wallPaper();
  485. const auto &list = _session->data().cloudThemes().chatThemes();
  486. if (list.empty()) {
  487. return;
  488. }
  489. using Type = Data::CloudThemeType;
  490. const auto type = Window::Theme::IsNightMode()
  491. ? Type::Dark
  492. : Type::Light;
  493. _papers.reserve(list.size() + 1);
  494. const auto nowEmojiId = now ? now->emojiId() : QString();
  495. if (!now || !now->emojiId().isEmpty()) {
  496. _papers.push_back({ Window::Theme::Background()->paper() });
  497. _currentId = _papers.back().data.id();
  498. } else {
  499. _papers.push_back({ *now });
  500. _currentId = now->id();
  501. }
  502. for (const auto &theme : list) {
  503. const auto i = theme.settings.find(type);
  504. if (i != end(theme.settings) && i->second.paper) {
  505. _papers.push_back({ *i->second.paper });
  506. if (nowEmojiId == theme.emoticon) {
  507. _currentId = _papers.back().data.id();
  508. }
  509. }
  510. }
  511. } else {
  512. _papers = _session->data().wallpapers(
  513. ) | ranges::views::filter([&](const Data::WallPaper &paper) {
  514. return (!paper.isPattern() || !paper.backgroundColors().empty())
  515. && (!_forPeer
  516. || (!Data::IsDefaultWallPaper(paper)
  517. && (Data::IsCloudWallPaper(paper)
  518. || Data::IsCustomWallPaper(paper))));
  519. }) | ranges::views::transform([](const Data::WallPaper &paper) {
  520. return Paper{ paper };
  521. }) | ranges::to_vector;
  522. pushCustomPapers();
  523. sortPapers();
  524. }
  525. resizeToContentAndPreload();
  526. }
  527. void BackgroundBox::Inner::resizeToContentAndPreload() {
  528. const auto count = _papers.size();
  529. const auto rows = (count / kBackgroundsInRow)
  530. + (count % kBackgroundsInRow ? 1 : 0);
  531. resize(
  532. st::boxWideWidth,
  533. (rows * (st::backgroundSize.height() + st::backgroundPadding)
  534. + st::backgroundPadding));
  535. const auto preload = kBackgroundsInRow * 3;
  536. for (const auto &paper : _papers | ranges::views::take(preload)) {
  537. if (!paper.data.localThumbnail() && !paper.dataMedia) {
  538. if (const auto document = paper.data.document()) {
  539. paper.dataMedia = document->createMediaView();
  540. paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
  541. }
  542. }
  543. }
  544. update();
  545. }
  546. void BackgroundBox::Inner::paintEvent(QPaintEvent *e) {
  547. QRect r(e->rect());
  548. auto p = QPainter(this);
  549. if (_papers.empty()) {
  550. p.setFont(st::noContactsFont);
  551. p.setPen(st::noContactsColor);
  552. p.drawText(QRect(0, 0, width(), st::noContactsHeight), tr::lng_contacts_loading(tr::now), style::al_center);
  553. return;
  554. }
  555. auto row = 0;
  556. auto column = 0;
  557. for (const auto &paper : _papers) {
  558. const auto increment = gsl::finally([&] {
  559. ++column;
  560. if (column == kBackgroundsInRow) {
  561. column = 0;
  562. ++row;
  563. }
  564. });
  565. if ((st::backgroundSize.height() + st::backgroundPadding) * (row + 1) <= r.top()) {
  566. continue;
  567. } else if ((st::backgroundSize.height() + st::backgroundPadding) * row >= r.top() + r.height()) {
  568. break;
  569. }
  570. paintPaper(p, paper, column, row);
  571. }
  572. }
  573. void BackgroundBox::Inner::validatePaperThumbnail(
  574. const Paper &paper) const {
  575. if (!paper.thumbnail.isNull()) {
  576. return;
  577. }
  578. const auto localThumbnail = paper.data.localThumbnail();
  579. if (!localThumbnail) {
  580. if (const auto document = paper.data.document()) {
  581. if (!paper.dataMedia) {
  582. paper.dataMedia = document->createMediaView();
  583. paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
  584. }
  585. if (!paper.dataMedia->thumbnail()) {
  586. return;
  587. }
  588. } else if (!paper.data.backgroundColors().empty()) {
  589. paper.thumbnail = Ui::PixmapFromImage(
  590. Ui::GenerateBackgroundImage(
  591. st::backgroundSize * style::DevicePixelRatio(),
  592. paper.data.backgroundColors(),
  593. paper.data.gradientRotation()));
  594. paper.thumbnail.setDevicePixelRatio(style::DevicePixelRatio());
  595. return;
  596. } else {
  597. return;
  598. }
  599. }
  600. const auto thumbnail = localThumbnail
  601. ? localThumbnail
  602. : paper.dataMedia->thumbnail();
  603. auto original = thumbnail->original();
  604. if (paper.data.isPattern()) {
  605. original = Ui::PreparePatternImage(
  606. std::move(original),
  607. paper.data.backgroundColors(),
  608. paper.data.gradientRotation(),
  609. paper.data.patternOpacity());
  610. }
  611. paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample(
  612. original,
  613. st::backgroundSize));
  614. paper.thumbnail.setDevicePixelRatio(style::DevicePixelRatio());
  615. }
  616. bool BackgroundBox::Inner::forChannel() const {
  617. return _forPeer && _forPeer->isChannel();
  618. }
  619. void BackgroundBox::Inner::paintPaper(
  620. QPainter &p,
  621. const Paper &paper,
  622. int column,
  623. int row) const {
  624. const auto x = st::backgroundPadding + column * (st::backgroundSize.width() + st::backgroundPadding);
  625. const auto y = st::backgroundPadding + row * (st::backgroundSize.height() + st::backgroundPadding);
  626. validatePaperThumbnail(paper);
  627. if (!paper.thumbnail.isNull()) {
  628. p.drawPixmap(x, y, paper.thumbnail);
  629. }
  630. const auto over = !v::is_null(_overDown) ? _overDown : _over;
  631. if (paper.data.id() == _currentId) {
  632. const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size;
  633. const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size;
  634. _check->paint(p, checkLeft, checkTop, width());
  635. } else if (!forChannel()
  636. && Data::IsCloudWallPaper(paper.data)
  637. && !Data::IsDefaultWallPaper(paper.data)
  638. && !Data::IsLegacy2DefaultWallPaper(paper.data)
  639. && !Data::IsLegacy3DefaultWallPaper(paper.data)
  640. && !v::is_null(over)
  641. && (&paper == &_papers[getSelectionIndex(over)])) {
  642. const auto deleteSelected = v::is<DeleteSelected>(over);
  643. const auto deletePos = QPoint(x + st::backgroundSize.width() - st::stickerPanDeleteIconBg.width(), y);
  644. p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg);
  645. st::stickerPanDeleteIconBg.paint(p, deletePos, width());
  646. p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg);
  647. st::stickerPanDeleteIconFg.paint(p, deletePos, width());
  648. p.setOpacity(1.);
  649. }
  650. }
  651. void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
  652. const auto newOver = [&] {
  653. const auto x = e->pos().x();
  654. const auto y = e->pos().y();
  655. const auto width = st::backgroundSize.width();
  656. const auto height = st::backgroundSize.height();
  657. const auto skip = st::backgroundPadding;
  658. const auto row = int((y - skip) / (height + skip));
  659. const auto column = int((x - skip) / (width + skip));
  660. const auto result = row * kBackgroundsInRow + column;
  661. if (y - row * (height + skip) > skip + height) {
  662. return Selection();
  663. } else if (x - column * (width + skip) > skip + width) {
  664. return Selection();
  665. } else if (result >= _papers.size()) {
  666. return Selection();
  667. }
  668. auto &data = _papers[result].data;
  669. const auto deleteLeft = (column + 1) * (width + skip)
  670. - st::stickerPanDeleteIconBg.width();
  671. const auto deleteBottom = row * (height + skip) + skip
  672. + st::stickerPanDeleteIconBg.height();
  673. const auto inDelete = !forChannel()
  674. && (x >= deleteLeft)
  675. && (y < deleteBottom)
  676. && Data::IsCloudWallPaper(data)
  677. && !Data::IsDefaultWallPaper(data)
  678. && !Data::IsLegacy2DefaultWallPaper(data)
  679. && !Data::IsLegacy3DefaultWallPaper(data)
  680. && (_currentId != data.id());
  681. return (result >= _papers.size())
  682. ? Selection()
  683. : inDelete
  684. ? Selection(DeleteSelected{ result })
  685. : Selection(Selected{ result });
  686. }();
  687. if (_over != newOver) {
  688. repaintPaper(getSelectionIndex(_over));
  689. _over = newOver;
  690. repaintPaper(getSelectionIndex(_over));
  691. setCursor((!v::is_null(_over) || !v::is_null(_overDown))
  692. ? style::cur_pointer
  693. : style::cur_default);
  694. }
  695. }
  696. void BackgroundBox::Inner::repaintPaper(int index) {
  697. if (index < 0 || index >= _papers.size()) {
  698. return;
  699. }
  700. const auto row = (index / kBackgroundsInRow);
  701. const auto column = (index % kBackgroundsInRow);
  702. const auto width = st::backgroundSize.width();
  703. const auto height = st::backgroundSize.height();
  704. const auto skip = st::backgroundPadding;
  705. update(
  706. (width + skip) * column + skip,
  707. (height + skip) * row + skip,
  708. width,
  709. height);
  710. }
  711. void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) {
  712. _overDown = _over;
  713. }
  714. int BackgroundBox::Inner::getSelectionIndex(
  715. const Selection &selection) const {
  716. return v::match(selection, [](const Selected &data) {
  717. return data.index;
  718. }, [](const DeleteSelected &data) {
  719. return data.index;
  720. }, [](v::null_t) {
  721. return -1;
  722. });
  723. }
  724. void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
  725. if (base::take(_overDown) == _over && !v::is_null(_over)) {
  726. const auto index = getSelectionIndex(_over);
  727. if (index >= 0 && index < _papers.size()) {
  728. if (std::get_if<DeleteSelected>(&_over)) {
  729. _backgroundRemove.fire_copy(_papers[index].data);
  730. } else if (std::get_if<Selected>(&_over)) {
  731. auto &paper = _papers[index];
  732. if (!paper.dataMedia) {
  733. if (const auto document = paper.data.document()) {
  734. // Keep it alive while it is on the screen.
  735. paper.dataMedia = document->createMediaView();
  736. }
  737. }
  738. _backgroundChosen.fire_copy(paper.data);
  739. }
  740. }
  741. } else if (v::is_null(_over)) {
  742. setCursor(style::cur_default);
  743. }
  744. }
  745. void BackgroundBox::Inner::visibleTopBottomUpdated(
  746. int visibleTop,
  747. int visibleBottom) {
  748. for (auto i = 0, count = int(_papers.size()); i != count; ++i) {
  749. const auto row = (i / kBackgroundsInRow);
  750. const auto height = st::backgroundSize.height();
  751. const auto skip = st::backgroundPadding;
  752. const auto top = skip + row * (height + skip);
  753. const auto bottom = top + height;
  754. if ((bottom <= visibleTop || top >= visibleBottom)
  755. && !_papers[i].thumbnail.isNull()) {
  756. _papers[i].dataMedia = nullptr;
  757. }
  758. }
  759. }
  760. rpl::producer<Data::WallPaper> BackgroundBox::Inner::chooseEvents() const {
  761. return _backgroundChosen.events();
  762. }
  763. auto BackgroundBox::Inner::removeRequests() const
  764. -> rpl::producer<Data::WallPaper> {
  765. return _backgroundRemove.events();
  766. }
  767. void BackgroundBox::Inner::removePaper(const Data::WallPaper &data) {
  768. const auto i = ranges::find(
  769. _papers,
  770. data.id(),
  771. [](const Paper &paper) { return paper.data.id(); });
  772. if (i != end(_papers)) {
  773. _papers.erase(i);
  774. _over = _overDown = Selection();
  775. resizeToContentAndPreload();
  776. }
  777. }
  778. BackgroundBox::Inner::~Inner() = default;