neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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;