box.txt (17733B)
1 void BackgroundBox::prepare() { 2 setTitle(tr::lng_backgrounds_header()); 3 4 addButton(tr::lng_close(), [=] { closeBox(); }); 5 6 setDimensions(st::boxWideWidth, st::boxMaxListHeight); 7 8 auto wrap = object_ptr<Ui::VerticalLayout>(this); 9 const auto container = wrap.data(); 10 11 Settings::AddSkip(container); 12 13 const auto button = container->add(Settings::CreateButton( 14 container, 15 tr::lng_settings_bg_from_file(), 16 st::infoProfileButton)); 17 object_ptr<Info::Profile::FloatingIcon>( 18 button, 19 st::infoIconMediaPhoto, 20 st::infoSharedMediaButtonIconPosition); 21 22 button->setClickedCallback([=] { 23 chooseFromFile(); 24 }); 25 26 Settings::AddSkip(container); 27 Settings::AddDivider(container); 28 29 _inner = container->add( 30 object_ptr<Inner>(this, &_controller->session(), _forPeer)); 31 32 container->resizeToWidth(st::boxWideWidth); 33 34 setInnerWidget(std::move(wrap), st::backgroundScroll); 35 setInnerTopSkip(st::lineWidth); 36 37 _inner->chooseEvents( 38 ) | rpl::start_with_next([=](const Data::WallPaper &paper) { 39 chosen(paper); 40 }, _inner->lifetime()); 41 42 _inner->removeRequests( 43 ) | rpl::start_with_next([=](const Data::WallPaper &paper) { 44 removePaper(paper); 45 }, _inner->lifetime()); 46 } 47 48 void BackgroundBox::chooseFromFile() { 49 const auto filterStart = _forPeer 50 ? u"Image files (*"_q 51 : u"Theme files (*.tdesktop-theme *.tdesktop-palette *"_q; 52 auto filters = QStringList( 53 filterStart 54 + Ui::ImageExtensions().join(u" *"_q) 55 + u")"_q); 56 filters.push_back(FileDialog::AllFilesFilter()); 57 const auto callback = [=](const FileDialog::OpenResult &result) { 58 if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { 59 return; 60 } 61 62 if (!_forPeer && !result.paths.isEmpty()) { 63 const auto filePath = result.paths.front(); 64 const auto hasExtension = [&](QLatin1String extension) { 65 return filePath.endsWith(extension, Qt::CaseInsensitive); 66 }; 67 if (hasExtension(qstr(".tdesktop-theme")) 68 || hasExtension(qstr(".tdesktop-palette"))) { 69 Window::Theme::Apply(filePath); 70 return; 71 } 72 } 73 74 auto image = Images::Read({ 75 .path = result.paths.isEmpty() ? QString() : result.paths.front(), 76 .content = result.remoteContent, 77 .forceOpaque = true, 78 }).image; 79 if (image.isNull() || image.width() <= 0 || image.height() <= 0) { 80 return; 81 } 82 auto local = Data::CustomWallPaper(); 83 local.setLocalImageAsThumbnail(std::make_shared<Image>( 84 std::move(image))); 85 _controller->show(Box<BackgroundPreviewBox>( 86 _controller, 87 local, 88 BackgroundPreviewArgs{ _forPeer })); 89 }; 90 FileDialog::GetOpenPath( 91 this, 92 tr::lng_choose_image(tr::now), 93 filters.join(u";;"_q), 94 crl::guard(this, callback)); 95 } 96 97 bool BackgroundBox::hasDefaultForPeer() const { 98 Expects(_forPeer != nullptr); 99 100 const auto paper = _forPeer->wallPaper(); 101 if (!paper) { 102 return true; 103 } 104 const auto reset = _inner->resolveResetCustomPaper(); 105 Assert(reset.has_value()); 106 return (paper->id() == reset->id()); 107 } 108 109 bool BackgroundBox::chosenDefaultForPeer( 110 const Data::WallPaper &paper) const { 111 if (!_forPeer) { 112 return false; 113 } 114 115 const auto reset = _inner->resolveResetCustomPaper(); 116 Assert(reset.has_value()); 117 return (paper.id() == reset->id()); 118 } 119 120 void BackgroundBox::chosen(const Data::WallPaper &paper) { 121 if (chosenDefaultForPeer(paper)) { 122 if (!hasDefaultForPeer()) { 123 const auto reset = crl::guard(this, [=](Fn<void()> close) { 124 resetForPeer(); 125 close(); 126 }); 127 _controller->show(Ui::MakeConfirmBox({ 128 .text = tr::lng_background_sure_reset_default(), 129 .confirmed = reset, 130 .confirmText = tr::lng_background_reset_default(), 131 })); 132 } else { 133 closeBox(); 134 } 135 return; 136 } 137 _controller->show(Box<BackgroundPreviewBox>( 138 _controller, 139 paper, 140 BackgroundPreviewArgs{ _forPeer })); 141 } 142 143 void BackgroundBox::resetForPeer() { 144 const auto api = &_controller->session().api(); 145 api->request(MTPmessages_SetChatWallPaper( 146 MTP_flags(0), 147 _forPeer->input, 148 MTPInputWallPaper(), 149 MTPWallPaperSettings(), 150 MTPint() 151 )).done([=](const MTPUpdates &result) { 152 api->applyUpdates(result); 153 }).send(); 154 155 const auto weak = Ui::MakeWeak(this); 156 _forPeer->setWallPaper(std::nullopt); 157 if (weak) { 158 _controller->finishChatThemeEdit(_forPeer); 159 } 160 } 161 162 void BackgroundBox::removePaper(const Data::WallPaper &paper) { 163 const auto session = &_controller->session(); 164 const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) { 165 close(); 166 if (weak) { 167 weak->_inner->removePaper(paper); 168 } 169 session->data().removeWallpaper(paper); 170 session->api().request(MTPaccount_SaveWallPaper( 171 paper.mtpInput(session), 172 MTP_bool(true), 173 paper.mtpSettings() 174 )).send(); 175 }; 176 _controller->show(Ui::MakeConfirmBox({ 177 .text = tr::lng_background_sure_delete(), 178 .confirmed = remove, 179 .confirmText = tr::lng_selected_delete(), 180 })); 181 } 182 183 BackgroundBox::Inner::Inner( 184 QWidget *parent, 185 not_null<Main::Session*> session, 186 PeerData *forPeer) 187 : RpWidget(parent) 188 , _session(session) 189 , _forPeer(forPeer) 190 , _api(&_session->mtp()) 191 , _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) { 192 _check->setChecked(true, anim::type::instant); 193 resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); 194 Window::Theme::IsNightModeValue( 195 ) | rpl::start_with_next([=] { 196 updatePapers(); 197 }, lifetime()); 198 requestPapers(); 199 200 _session->downloaderTaskFinished( 201 ) | rpl::start_with_next([=] { 202 update(); 203 }, lifetime()); 204 205 style::PaletteChanged( 206 ) | rpl::start_with_next([=] { 207 _check->invalidateCache(); 208 }, lifetime()); 209 210 using Update = Window::Theme::BackgroundUpdate; 211 Window::Theme::Background()->updates( 212 ) | rpl::start_with_next([=](const Update &update) { 213 if (update.type == Update::Type::New) { 214 sortPapers(); 215 requestPapers(); 216 this->update(); 217 } 218 }, lifetime()); 219 220 221 setMouseTracking(true); 222 } 223 224 void BackgroundBox::Inner::requestPapers() { 225 _api.request(MTPaccount_GetWallPapers( 226 MTP_long(_session->data().wallpapersHash()) 227 )).done([=](const MTPaccount_WallPapers &result) { 228 if (_session->data().updateWallpapers(result)) { 229 updatePapers(); 230 } 231 }).send(); 232 } 233 234 auto BackgroundBox::Inner::resolveResetCustomPaper() const 235 -> std::optional<Data::WallPaper> { 236 if (!_forPeer) { 237 return {}; 238 } 239 const auto nonCustom = Window::Theme::Background()->paper(); 240 const auto themeEmoji = _forPeer->themeEmoji(); 241 if (themeEmoji.isEmpty()) { 242 return nonCustom; 243 } 244 const auto &themes = _forPeer->owner().cloudThemes(); 245 const auto theme = themes.themeForEmoji(themeEmoji); 246 if (!theme) { 247 return nonCustom; 248 } 249 using Type = Data::CloudTheme::Type; XXXXX 250 const auto dark = Window::Theme::IsNightMode(); 251 const auto i = theme->settings.find(dark ? Type::Dark : Type::Light); 252 if (i != end(theme->settings) && i->second.paper) { 253 return *i->second.paper; 254 } 255 return nonCustom; 256 } 257 258 void BackgroundBox::Inner::pushCustomPapers() { 259 auto customId = uint64(); 260 if (const auto custom = _forPeer ? _forPeer->wallPaper() : nullptr) { 261 customId = custom->id(); 262 const auto j = ranges::find( 263 _papers, 264 custom->id(), 265 [](const Paper &paper) { return paper.data.id(); }); 266 if (j != end(_papers)) { 267 j->data = j->data.withParamsFrom(*custom); 268 } else { 269 _papers.insert(begin(_papers), Paper{ *custom }); 270 } 271 } 272 if (const auto reset = resolveResetCustomPaper()) { 273 _insertedResetId = reset->id(); 274 const auto j = ranges::find( 275 _papers, 276 _insertedResetId, 277 [](const Paper &paper) { return paper.data.id(); }); 278 if (j != end(_papers)) { 279 if (_insertedResetId != customId) { 280 j->data = j->data.withParamsFrom(*reset); 281 } 282 } else { 283 _papers.insert(begin(_papers), Paper{ *reset }); 284 } 285 } 286 } 287 288 void BackgroundBox::Inner::sortPapers() { 289 const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr; 290 _currentId = currentCustom 291 ? currentCustom->id() 292 : _insertedResetId 293 ? _insertedResetId 294 : Window::Theme::Background()->id(); 295 const auto dark = Window::Theme::IsNightMode(); 296 ranges::stable_sort(_papers, std::greater<>(), [&](const Paper &paper) { 297 const auto &data = paper.data; 298 return std::make_tuple( 299 _insertedResetId && (_insertedResetId == data.id()), 300 data.id() == _currentId, 301 dark ? data.isDark() : !data.isDark(), 302 Data::IsDefaultWallPaper(data), 303 !data.isDefault() && !Data::IsLegacy1DefaultWallPaper(data), 304 Data::IsLegacy3DefaultWallPaper(data), 305 Data::IsLegacy2DefaultWallPaper(data), 306 Data::IsLegacy1DefaultWallPaper(data)); 307 }); 308 if (!_papers.empty() 309 && _papers.front().data.id() == _currentId 310 && !currentCustom 311 && !_insertedResetId) { 312 _papers.front().data = _papers.front().data.withParamsFrom( 313 Window::Theme::Background()->paper()); 314 } 315 } 316 317 void BackgroundBox::Inner::updatePapers() { 318 if (_session->data().wallpapers().empty()) { 319 return; 320 } 321 _over = _overDown = Selection(); 322 323 _papers = _session->data().wallpapers( 324 ) | ranges::views::filter([&](const Data::WallPaper &paper) { 325 return (!paper.isPattern() || !paper.backgroundColors().empty()) 326 && (!_forPeer 327 || (!Data::IsDefaultWallPaper(paper) 328 && (Data::IsCloudWallPaper(paper) 329 || Data::IsCustomWallPaper(paper)))); 330 }) | ranges::views::transform([](const Data::WallPaper &paper) { 331 return Paper{ paper }; 332 }) | ranges::to_vector; 333 pushCustomPapers(); 334 sortPapers(); 335 resizeToContentAndPreload(); 336 } 337 338 void BackgroundBox::Inner::resizeToContentAndPreload() { 339 const auto count = _papers.size(); 340 const auto rows = (count / kBackgroundsInRow) 341 + (count % kBackgroundsInRow ? 1 : 0); 342 343 resize( 344 st::boxWideWidth, 345 (rows * (st::backgroundSize.height() + st::backgroundPadding) 346 + st::backgroundPadding)); 347 348 const auto preload = kBackgroundsInRow * 3; 349 for (const auto &paper : _papers | ranges::views::take(preload)) { 350 if (!paper.data.localThumbnail() && !paper.dataMedia) { 351 if (const auto document = paper.data.document()) { 352 paper.dataMedia = document->createMediaView(); 353 paper.dataMedia->thumbnailWanted(paper.data.fileOrigin()); 354 } 355 } 356 } 357 update(); 358 } 359 360 void BackgroundBox::Inner::paintEvent(QPaintEvent *e) { 361 QRect r(e->rect()); 362 auto p = QPainter(this); 363 364 if (_papers.empty()) { 365 p.setFont(st::noContactsFont); 366 p.setPen(st::noContactsColor); 367 p.drawText(QRect(0, 0, width(), st::noContactsHeight), tr::lng_contacts_loading(tr::now), style::al_center); 368 return; 369 } 370 auto row = 0; 371 auto column = 0; 372 for (const auto &paper : _papers) { 373 const auto increment = gsl::finally([&] { 374 ++column; 375 if (column == kBackgroundsInRow) { 376 column = 0; 377 ++row; 378 } 379 }); 380 if ((st::backgroundSize.height() + st::backgroundPadding) * (row + 1) <= r.top()) { 381 continue; 382 } else if ((st::backgroundSize.height() + st::backgroundPadding) * row >= r.top() + r.height()) { 383 break; 384 } 385 paintPaper(p, paper, column, row); 386 } 387 } 388 389 void BackgroundBox::Inner::validatePaperThumbnail( 390 const Paper &paper) const { 391 if (!paper.thumbnail.isNull()) { 392 return; 393 } 394 const auto localThumbnail = paper.data.localThumbnail(); 395 if (!localThumbnail) { 396 if (const auto document = paper.data.document()) { 397 if (!paper.dataMedia) { 398 paper.dataMedia = document->createMediaView(); 399 paper.dataMedia->thumbnailWanted(paper.data.fileOrigin()); 400 } 401 if (!paper.dataMedia->thumbnail()) { 402 return; 403 } 404 } else if (!paper.data.backgroundColors().empty()) { 405 paper.thumbnail = Ui::PixmapFromImage( 406 Ui::GenerateBackgroundImage( 407 st::backgroundSize * cIntRetinaFactor(), 408 paper.data.backgroundColors(), 409 paper.data.gradientRotation())); 410 paper.thumbnail.setDevicePixelRatio(cRetinaFactor()); 411 return; 412 } else { 413 return; 414 } 415 } 416 const auto thumbnail = localThumbnail 417 ? localThumbnail 418 : paper.dataMedia->thumbnail(); 419 auto original = thumbnail->original(); 420 if (paper.data.isPattern()) { 421 original = Ui::PreparePatternImage( 422 std::move(original), 423 paper.data.backgroundColors(), 424 paper.data.gradientRotation(), 425 paper.data.patternOpacity()); 426 } 427 paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample( 428 original, 429 st::backgroundSize)); 430 paper.thumbnail.setDevicePixelRatio(cRetinaFactor()); 431 } 432 433 void BackgroundBox::Inner::paintPaper( 434 QPainter &p, 435 const Paper &paper, 436 int column, 437 int row) const { 438 const auto x = st::backgroundPadding + column * (st::backgroundSize.width() + st::backgroundPadding); 439 const auto y = st::backgroundPadding + row * (st::backgroundSize.height() + st::backgroundPadding); 440 validatePaperThumbnail(paper); 441 if (!paper.thumbnail.isNull()) { 442 p.drawPixmap(x, y, paper.thumbnail); 443 } 444 445 const auto over = !v::is_null(_overDown) ? _overDown : _over; 446 if (paper.data.id() == _currentId) { 447 const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; 448 const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; 449 _check->paint(p, checkLeft, checkTop, width()); 450 } else if (Data::IsCloudWallPaper(paper.data) 451 && !Data::IsDefaultWallPaper(paper.data) 452 && !Data::IsLegacy2DefaultWallPaper(paper.data) 453 && !Data::IsLegacy3DefaultWallPaper(paper.data) 454 && !v::is_null(over) 455 && (&paper == &_papers[getSelectionIndex(over)])) { 456 const auto deleteSelected = v::is<DeleteSelected>(over); 457 const auto deletePos = QPoint(x + st::backgroundSize.width() - st::stickerPanDeleteIconBg.width(), y); 458 p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg); 459 st::stickerPanDeleteIconBg.paint(p, deletePos, width()); 460 p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg); 461 st::stickerPanDeleteIconFg.paint(p, deletePos, width()); 462 p.setOpacity(1.); 463 } 464 } 465 466 void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { 467 const auto newOver = [&] { 468 const auto x = e->pos().x(); 469 const auto y = e->pos().y(); 470 const auto width = st::backgroundSize.width(); 471 const auto height = st::backgroundSize.height(); 472 const auto skip = st::backgroundPadding; 473 const auto row = int((y - skip) / (height + skip)); 474 const auto column = int((x - skip) / (width + skip)); 475 const auto result = row * kBackgroundsInRow + column; 476 if (y - row * (height + skip) > skip + height) { 477 return Selection(); 478 } else if (x - column * (width + skip) > skip + width) { 479 return Selection(); 480 } else if (result >= _papers.size()) { 481 return Selection(); 482 } 483 auto &data = _papers[result].data; 484 const auto deleteLeft = (column + 1) * (width + skip) 485 - st::stickerPanDeleteIconBg.width(); 486 const auto deleteBottom = row * (height + skip) + skip 487 + st::stickerPanDeleteIconBg.height(); 488 const auto inDelete = (x >= deleteLeft) 489 && (y < deleteBottom) 490 && Data::IsCloudWallPaper(data) 491 && !Data::IsDefaultWallPaper(data) 492 && !Data::IsLegacy2DefaultWallPaper(data) 493 && !Data::IsLegacy3DefaultWallPaper(data) 494 && (_currentId != data.id()); 495 return (result >= _papers.size()) 496 ? Selection() 497 : inDelete 498 ? Selection(DeleteSelected{ result }) 499 : Selection(Selected{ result }); 500 }(); 501 if (_over != newOver) { 502 repaintPaper(getSelectionIndex(_over)); 503 _over = newOver; 504 repaintPaper(getSelectionIndex(_over)); 505 setCursor((!v::is_null(_over) || !v::is_null(_overDown)) 506 ? style::cur_pointer 507 : style::cur_default); 508 } 509 } 510 511 void BackgroundBox::Inner::repaintPaper(int index) { 512 if (index < 0 || index >= _papers.size()) { 513 return; 514 } 515 const auto row = (index / kBackgroundsInRow); 516 const auto column = (index % kBackgroundsInRow); 517 const auto width = st::backgroundSize.width(); 518 const auto height = st::backgroundSize.height(); 519 const auto skip = st::backgroundPadding; 520 update( 521 (width + skip) * column + skip, 522 (height + skip) * row + skip, 523 width, 524 height); 525 } 526 527 void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) { 528 _overDown = _over; 529 } 530 531 int BackgroundBox::Inner::getSelectionIndex( 532 const Selection &selection) const { 533 return v::match(selection, [](const Selected &data) { 534 return data.index; 535 }, [](const DeleteSelected &data) { 536 return data.index; 537 }, [](v::null_t) { 538 return -1; 539 }); 540 } 541 542 void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) { 543 if (base::take(_overDown) == _over && !v::is_null(_over)) { 544 const auto index = getSelectionIndex(_over); 545 if (index >= 0 && index < _papers.size()) { 546 if (std::get_if<DeleteSelected>(&_over)) { 547 _backgroundRemove.fire_copy(_papers[index].data); 548 } else if (std::get_if<Selected>(&_over)) { 549 auto &paper = _papers[index]; 550 if (!paper.dataMedia) { 551 if (const auto document = paper.data.document()) { 552 // Keep it alive while it is on the screen. 553 paper.dataMedia = document->createMediaView(); 554 } 555 } 556 _backgroundChosen.fire_copy(paper.data); 557 } 558 } 559 } else if (v::is_null(_over)) { 560 setCursor(style::cur_default); 561 } 562 } 563 564 void BackgroundBox::Inner::visibleTopBottomUpdated( 565 int visibleTop, 566 int visibleBottom) { 567 for (auto i = 0, count = int(_papers.size()); i != count; ++i) { 568 const auto row = (i / kBackgroundsInRow); 569 const auto height = st::backgroundSize.height(); 570 const auto skip = st::backgroundPadding; 571 const auto top = skip + row * (height + skip); 572 const auto bottom = top + height; 573 if ((bottom <= visibleTop || top >= visibleBottom) 574 && !_papers[i].thumbnail.isNull()) { 575 _papers[i].dataMedia = nullptr; 576 } 577 } 578 } 579 580 rpl::producer<Data::WallPaper> BackgroundBox::Inner::chooseEvents() const { 581 return _backgroundChosen.events(); 582 } 583 584 auto BackgroundBox::Inner::removeRequests() const 585 -> rpl::producer<Data::WallPaper> { 586 return _backgroundRemove.events(); 587 } 588 589 void BackgroundBox::Inner::removePaper(const Data::WallPaper &data) { 590 const auto i = ranges::find( 591 _papers, 592 data.id(), 593 [](const Paper &paper) { return paper.data.id(); }); 594 if (i != end(_papers)) { 595 _papers.erase(i); 596 _over = _overDown = Selection(); 597 resizeToContentAndPreload(); 598 } 599 } 600 601 BackgroundBox::Inner::~Inner() = default;