From 1894b8fcf7a576de8b4e21b5fa5b7a0cd0a8b408 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 17 Jan 2019 12:18:23 +0400 Subject: [PATCH] Handle t.me/bg links with wallpapers / colors. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/apiwrap.cpp | 54 ++- Telegram/SourceFiles/apiwrap.h | 11 + Telegram/SourceFiles/base/binary_guard.h | 13 +- .../SourceFiles/base/concurrent_timer.cpp | 4 +- Telegram/SourceFiles/boxes/background_box.cpp | 327 ++++++++++++++++++ Telegram/SourceFiles/boxes/background_box.h | 51 +++ .../SourceFiles/core/click_handler_types.cpp | 9 +- .../SourceFiles/core/local_url_handlers.cpp | 19 +- Telegram/SourceFiles/data/data_document.cpp | 43 ++- Telegram/SourceFiles/data/data_document.h | 3 + .../data/data_document_good_thumbnail.cpp | 6 +- .../SourceFiles/data/data_file_origin.cpp | 8 +- Telegram/SourceFiles/data/data_file_origin.h | 20 +- Telegram/SourceFiles/data/data_web_page.cpp | 4 +- Telegram/SourceFiles/mainwidget.cpp | 129 ++++--- Telegram/SourceFiles/mainwidget.h | 11 +- Telegram/SourceFiles/mediaview.cpp | 2 +- .../SourceFiles/settings/settings_chat.cpp | 49 +-- .../storage/cache/storage_cache_cleaner.cpp | 2 +- .../storage/cache/storage_cache_compactor.cpp | 2 +- .../SourceFiles/storage/file_download.cpp | 6 +- Telegram/SourceFiles/storage/localstorage.cpp | 2 +- .../SourceFiles/storage/storage_databases.cpp | 4 +- .../support/support_autocomplete.cpp | 4 +- .../SourceFiles/support/support_templates.cpp | 4 +- Telegram/SourceFiles/ui/emoji_config.cpp | 4 +- Telegram/SourceFiles/ui/image/image.cpp | 5 + Telegram/SourceFiles/ui/image/image.h | 2 + .../window/notifications_manager_default.cpp | 2 +- .../SourceFiles/window/section_widget.cpp | 4 + .../window/themes/window_theme.cpp | 150 +++++--- .../SourceFiles/window/themes/window_theme.h | 36 +- .../window/themes/window_theme_editor.cpp | 4 +- 34 files changed, 805 insertions(+), 194 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 67ad7d7f5..47d1a37ea 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -392,6 +392,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_theme_reverting#other" = "Reverting to the old theme in {count} seconds."; "lng_theme_keep_changes" = "Keep changes"; "lng_theme_revert" = "Revert"; +"lng_background_header" = "Background preview"; +"lng_background_text1" = "You can't swipe left or right to preview anything - this is tdesktop, sorry."; +"lng_background_text2" = "Sounds awful."; +"lng_background_bad_link" = "This background link appears to be invalid."; +"lng_background_apply" = "Apply"; "lng_download_path_ask" = "Ask download path for each file"; "lng_download_path" = "Download path"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 92612f260..4d24f817b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/notifications_manager.h" #include "window/window_lock_widgets.h" #include "window/window_controller.h" +#include "window/themes/window_theme.h" #include "inline_bots/inline_bot_result.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" @@ -864,6 +865,52 @@ void ApiWrap::requestFakeChatListMessage( }).send(); } +void ApiWrap::requestWallPaper( + const QString &slug, + Fn done, + Fn fail) { + if (_wallPaperSlug != slug) { + _wallPaperSlug = slug; + if (_wallPaperRequestId) { + request(base::take(_wallPaperRequestId)).cancel(); + } + } + _wallPaperDone = std::move(done); + _wallPaperFail = std::move(fail); + if (_wallPaperRequestId) { + return; + } + _wallPaperRequestId = request(MTPaccount_GetWallPaper( + MTP_inputWallPaperSlug(MTP_string(slug)) + )).done([=](const MTPWallPaper &result) { + _wallPaperRequestId = 0; + _wallPaperSlug = QString(); + result.match([&](const MTPDwallPaper &data) { + const auto document = _session->data().document(data.vdocument); + if (document->checkWallPaperProperties()) { + if (const auto done = base::take(_wallPaperDone)) { + done({ + data.vid.v, + data.vaccess_hash.v, + data.vflags.v, + qs(data.vslug), + document->thumb, + document + }); + } + } else if (const auto fail = base::take(_wallPaperFail)) { + fail(RPCError::Local("BAD_DOCUMENT", "In a wallpaper.")); + } + }); + }).fail([=](const RPCError &error) { + _wallPaperRequestId = 0; + _wallPaperSlug = QString(); + if (const auto fail = base::take(_wallPaperFail)) { + fail(error); + } + }).send(); +} + void ApiWrap::requestFullPeer(not_null peer) { if (_fullPeerRequests.contains(peer)) { return; @@ -2829,8 +2876,11 @@ void ApiWrap::refreshFileReference( request( MTPmessages_GetSavedGifs(MTP_int(0)), [] { crl::on_main([] { Local::writeSavedGifs(); }); }); - }, [&](Data::FileOriginWallpapers data) { - request(MTPaccount_GetWallPapers(MTP_int(0))); + }, [&](Data::FileOriginWallpaper data) { + request(MTPaccount_GetWallPaper( + MTP_inputWallPaper( + MTP_long(data.paperId), + MTP_long(data.accessHash)))); }, [&](std::nullopt_t) { fail(); }); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 690bc46ad..edfd31490 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -26,6 +26,7 @@ class mtpFileLoader; namespace Data { struct UpdatedFileReferences; +struct WallPaper; } // namespace Data namespace InlineBots { @@ -92,6 +93,11 @@ public: //void changeDialogUnreadMark(not_null feed, bool unread); // #feed void requestFakeChatListMessage(not_null history); + void requestWallPaper( + const QString &slug, + Fn done, + Fn fail); + void requestFullPeer(not_null peer); void requestPeer(not_null peer); void requestPeers(const QList &peers); @@ -775,4 +781,9 @@ private: base::flat_map _pollCloseRequestIds; base::flat_map _pollReloadRequestIds; + mtpRequestId _wallPaperRequestId = 0; + QString _wallPaperSlug; + Fn _wallPaperDone; + Fn _wallPaperFail; + }; diff --git a/Telegram/SourceFiles/base/binary_guard.h b/Telegram/SourceFiles/base/binary_guard.h index 1e5a0ee58..1c6455e1a 100644 --- a/Telegram/SourceFiles/base/binary_guard.h +++ b/Telegram/SourceFiles/base/binary_guard.h @@ -21,7 +21,9 @@ public: ~binary_guard(); bool alive() const; - void kill(); + + binary_guard &operator=(std::nullptr_t); + explicit operator bool() const; private: void destroy(); @@ -44,15 +46,20 @@ inline binary_guard &binary_guard::operator=(binary_guard &&other) { return *this; } -inline binary_guard::~binary_guard() { +inline binary_guard &binary_guard::operator=(std::nullptr_t) { destroy(); + return *this; +} + +inline binary_guard::operator bool() const { + return alive(); } inline bool binary_guard::alive() const { return _bothAlive && _bothAlive->load(); } -inline void binary_guard::kill() { +inline binary_guard::~binary_guard() { destroy(); } diff --git a/Telegram/SourceFiles/base/concurrent_timer.cpp b/Telegram/SourceFiles/base/concurrent_timer.cpp index 4f9fb82f8..03ce351a0 100644 --- a/Telegram/SourceFiles/base/concurrent_timer.cpp +++ b/Telegram/SourceFiles/base/concurrent_timer.cpp @@ -301,11 +301,11 @@ void ConcurrentTimer::cancelAndSchedule(int timeout) { runner = _runner, guard = std::move(guards.second) ]() mutable { - if (!guard.alive()) { + if (!guard) { return; } runner([=, guard = std::move(guard)] { - if (!guard.alive()) { + if (!guard) { return; } timerEvent(); diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 0752658d0..65f65bcd6 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -13,14 +13,93 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "ui/effects/round_checkbox.h" #include "ui/image/image.h" +#include "history/history.h" +#include "history/history_message.h" +#include "history/view/history_view_message.h" #include "auth_session.h" +#include "apiwrap.h" #include "data/data_session.h" +#include "data/data_user.h" +#include "data/data_document.h" +#include "boxes/confirm_box.h" #include "styles/style_overview.h" +#include "styles/style_history.h" #include "styles/style_boxes.h" namespace { constexpr auto kBackgroundsInRow = 3; +constexpr auto kMaxWallPaperSlugLength = 255; + +[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { + if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) { + return false; + } + return ranges::find_if(slug, [](QChar ch) { + return (ch != '.') + && (ch != '_') + && (ch != '-') + && (ch < '0' || ch > '9') + && (ch < 'a' || ch > 'z') + && (ch < 'A' || ch > 'Z'); + }) == slug.end(); +} + +AdminLog::OwnedItem GenerateTextItem( + not_null delegate, + not_null history, + const QString &text, + bool out) { + Expects(history->peer->isUser()); + + using Flag = MTPDmessage::Flag; + const auto id = ServerMaxMsgId + (ServerMaxMsgId / 3) + (out ? 1 : 0); + const auto flags = Flag::f_entities + | Flag::f_from_id + | (out ? Flag::f_out : Flag(0)); + const auto replyTo = 0; + const auto viaBotId = 0; + const auto item = new HistoryMessage( + history, + id, + flags, + replyTo, + viaBotId, + unixtime(), + out ? history->session().userId() : peerToUser(history->peer->id), + QString(), + TextWithEntities{ TextUtilities::Clean(text) }); + return AdminLog::OwnedItem(delegate, item); +} + +QImage PrepareScaledFromFull( + const QImage &image, + Images::Option blur = Images::Option(0)) { + const auto size = st::boxWideWidth; + const auto width = std::max(image.width(), 1); + const auto height = std::max(image.height(), 1); + const auto takeWidth = (width > height) + ? (width * size / height) + : size; + const auto takeHeight = (width > height) + ? size + : (height * size / width); + return Images::prepare( + image, + takeWidth, + takeHeight, + Images::Option::Smooth | blur, + size, + size); +} + +QPixmap PrepareScaledFromThumb(ImagePtr thumb) { + return thumb->loaded() + ? App::pixmapFromImageInPlace(PrepareScaledFromFull( + thumb->original(), + Images::Option::Blurred)) + : QPixmap(); +} } // namespace @@ -202,3 +281,251 @@ void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) { } BackgroundBox::Inner::~Inner() = default; + +BackgroundPreviewBox::BackgroundPreviewBox( + QWidget*, + const Data::WallPaper &paper) +: _text1(GenerateTextItem( + this, + App::history(App::user(ServiceUserId)), + lang(lng_background_text1), + false)) +, _text2(GenerateTextItem( + this, + App::history(App::user(ServiceUserId)), + lang(lng_background_text2), + true)) +, _paper(paper) +, _radial(animation(this, &BackgroundPreviewBox::step_radial)) { + subscribe(Auth().downloaderTaskFinished(), [=] { update(); }); +} + +void BackgroundPreviewBox::prepare() { + setTitle(langFactory(lng_background_header)); + + addButton(langFactory(lng_background_apply), [=] { apply(); }); + addButton(langFactory(lng_cancel), [=] { closeBox(); }); + + _scaled = PrepareScaledFromThumb(_paper.thumb); + checkLoadedDocument(); + + if (_paper.thumb && !_paper.thumb->loaded()) { + _paper.thumb->loadEvenCancelled(Data::FileOriginWallpaper( + _paper.id, + _paper.accessHash)); + } + if (_paper.document) { + _paper.document->save(Data::FileOriginWallpaper( + _paper.id, + _paper.accessHash), QString()); + if (_paper.document->loading()) { + _radial.start(_paper.document->progress()); + } + } + + _text1->setDisplayDate(true); + _text1->initDimensions(); + _text1->resizeGetHeight(st::boxWideWidth); + _text2->initDimensions(); + _text2->resizeGetHeight(st::boxWideWidth); + + setDimensions(st::boxWideWidth, st::boxWideWidth); +} + +void BackgroundPreviewBox::apply() { + App::main()->setChatBackground(_paper, std::move(_full)); + closeBox(); +} + +void BackgroundPreviewBox::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto ms = getms(); + + if (const auto color = Window::Theme::GetWallPaperColor(_paper.slug)) { + p.fillRect(e->rect(), *color); + } else { + if (_scaled.isNull()) { + _scaled = PrepareScaledFromThumb(_paper.thumb); + if (_scaled.isNull()) { + p.fillRect(e->rect(), st::boxBg); + return; + } + } + paintImage(p); + paintRadial(p, ms); + } + paintTexts(p, ms); +} + +void BackgroundPreviewBox::paintImage(Painter &p) { + Expects(!_scaled.isNull()); + + const auto factor = cIntRetinaFactor(); + const auto size = st::boxWideWidth; + const auto from = QRect( + 0, + (size - height()) / 2 * factor, + size * factor, + height() * factor); + p.drawPixmap(rect(), _scaled, from); +} + +void BackgroundPreviewBox::paintRadial(Painter &p, TimeMs ms) { + bool radial = false; + float64 radialOpacity = 0; + if (_radial.animating()) { + _radial.step(ms); + radial = _radial.animating(); + radialOpacity = _radial.opacity(); + } + if (!radial) { + return; + } + auto inner = radialRect(); + + p.setPen(Qt::NoPen); + p.setOpacity(radialOpacity); + p.setBrush(st::radialBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + p.setOpacity(1); + QRect arc(inner.marginsRemoved(QMargins(st::radialLine, st::radialLine, st::radialLine, st::radialLine))); + _radial.draw(p, arc, st::radialLine, st::radialFg); +} + +QRect BackgroundPreviewBox::radialRect() const { + const auto available = height() + - st::historyPaddingBottom + - _text1->height() + - _text2->height() + - st::historyPaddingBottom; + return QRect( + QPoint( + (width() - st::radialSize.width()) / 2, + (available - st::radialSize.height()) / 2), + st::radialSize); +} + +void BackgroundPreviewBox::paintTexts(Painter &p, TimeMs ms) { + const auto height1 = _text1->height(); + const auto height2 = _text2->height(); + const auto top = height() + - height1 + - height2 + - st::historyPaddingBottom; + p.translate(0, top); + _text1->draw(p, rect(), TextSelection(), ms); + p.translate(0, height1); + _text2->draw(p, rect(), TextSelection(), ms); + p.translate(0, height2); +} + +void BackgroundPreviewBox::step_radial(TimeMs ms, bool timer) { + Expects(_paper.document != nullptr); + + const auto document = _paper.document; + const auto wasAnimating = _radial.animating(); + const auto updated = _radial.update( + document->progress(), + !document->loading(), + ms); + if (timer + && (wasAnimating || _radial.animating()) + && (!anim::Disabled() || updated)) { + update(radialRect()); + } + checkLoadedDocument(); +} + +void BackgroundPreviewBox::checkLoadedDocument() { + const auto document = _paper.document; + if (!document + || !document->loaded(DocumentData::FilePathResolveChecked) + || _generating) { + return; + } + _generating = Data::ReadImageAsync(document, [=]( + QImage &&image) mutable { + auto [left, right] = base::make_binary_guard(); + _generating = std::move(left); + crl::async([ + this, + image = std::move(image), + guard = std::move(right) + ]() mutable { + auto scaled = PrepareScaledFromFull(image); + crl::on_main([ + this, + image = std::move(image), + scaled = std::move(scaled), + guard = std::move(guard) + ]() mutable { + if (!guard) { + return; + } + _scaled = App::pixmapFromImageInPlace(std::move(scaled)); + _full = std::move(image); + update(); + }); + }); + }); +} + +bool BackgroundPreviewBox::Start(const QString &slug, const QString &mode) { + if (Window::Theme::GetWallPaperColor(slug)) { + Ui::show(Box(Data::WallPaper{ + Window::Theme::kCustomBackground, + 0ULL, // accessHash + MTPDwallPaper::Flags(0), + slug, + })); + return true; + } + if (!IsValidWallPaperSlug(slug)) { + Ui::show(Box(lang(lng_background_bad_link))); + return false; + } + Auth().api().requestWallPaper(slug, [](const Data::WallPaper &result) { + Ui::show(Box(result)); + }, [](const RPCError &error) { + Ui::show(Box(lang(lng_background_bad_link))); + }); + return true; +} + +HistoryView::Context BackgroundPreviewBox::elementContext() { + return HistoryView::Context::ContactPreview; +} + +std::unique_ptr BackgroundPreviewBox::elementCreate( + not_null message) { + return std::make_unique(this, message); +} + +std::unique_ptr BackgroundPreviewBox::elementCreate( + not_null message) { + Unexpected("Service message in BackgroundPreviewBox."); +} + +bool BackgroundPreviewBox::elementUnderCursor( + not_null view) { + return false; +} + +void BackgroundPreviewBox::elementAnimationAutoplayAsync( + not_null element) { +} + +TimeMs BackgroundPreviewBox::elementHighlightTime( + not_null element) { + return TimeMs(); +} + +bool BackgroundPreviewBox::elementInSelectionMode() { + return false; +} diff --git a/Telegram/SourceFiles/boxes/background_box.h b/Telegram/SourceFiles/boxes/background_box.h index 3b217b65c..eb84e0314 100644 --- a/Telegram/SourceFiles/boxes/background_box.h +++ b/Telegram/SourceFiles/boxes/background_box.h @@ -7,7 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/binary_guard.h" #include "boxes/abstract_box.h" +#include "window/themes/window_theme.h" +#include "history/admin_log/history_admin_log_item.h" +#include "history/view/history_view_element.h" +#include "ui/effects/radial_animation.h" namespace Ui { class RoundCheckbox; @@ -27,3 +32,49 @@ private: QPointer _inner; }; + +class BackgroundPreviewBox + : public BoxContent + , public HistoryView::ElementDelegate { +public: + BackgroundPreviewBox(QWidget*, const Data::WallPaper &paper); + + static bool Start(const QString &slug, const QString &mode); + + using Element = HistoryView::Element; + HistoryView::Context elementContext() override; + std::unique_ptr elementCreate( + not_null message) override; + std::unique_ptr elementCreate( + not_null message) override; + bool elementUnderCursor(not_null view) override; + void elementAnimationAutoplayAsync( + not_null element) override; + TimeMs elementHighlightTime( + not_null element) override; + bool elementInSelectionMode() override; + +protected: + void prepare() override; + + void paintEvent(QPaintEvent *e) override; + +private: + void apply(); + void step_radial(TimeMs ms, bool timer); + QRect radialRect() const; + + void checkLoadedDocument(); + void paintImage(Painter &p); + void paintRadial(Painter &p, TimeMs ms); + void paintTexts(Painter &p, TimeMs ms); + + AdminLog::OwnedItem _text1; + AdminLog::OwnedItem _text2; + Data::WallPaper _paper; + QImage _full; + QPixmap _scaled; + Ui::RadialAnimation _radial; + base::binary_guard _generating; + +}; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 86c0b6ada..edb8e2322 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -44,7 +44,7 @@ QString tryConvertUrlToLocal(QString url) { return qsl("tg://msg_url?") + shareUrlMatch->captured(1); } else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) { return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1); - } else if (auto ivMatch = regex_match(qsl("iv/?\\?(.+)(#|$)"), query, matchOptions)) { + } else if (auto ivMatch = regex_match(qsl("^iv/?\\?(.+)(#|$)"), query, matchOptions)) { // // We need to show our t.me page, not the url directly. // @@ -55,10 +55,13 @@ QString tryConvertUrlToLocal(QString url) { // return previewedUrl; //} return url; - } else if (auto socksMatch = regex_match(qsl("socks/?\\?(.+)(#|$)"), query, matchOptions)) { + } else if (auto socksMatch = regex_match(qsl("^socks/?\\?(.+)(#|$)"), query, matchOptions)) { return qsl("tg://socks?") + socksMatch->captured(1); - } else if (auto proxyMatch = regex_match(qsl("proxy/?\\?(.+)(#|$)"), query, matchOptions)) { + } else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), query, matchOptions)) { return qsl("tg://proxy?") + proxyMatch->captured(1); + } else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-]+)(\\?(.+)?)?$"), query, matchOptions)) { + const auto params = bgMatch->captured(3); + return qsl("tg://bg?slug=") + bgMatch->captured(1) + (params.isEmpty() ? QString() : '&' + params); } else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) { auto params = query.mid(usernameMatch->captured(0).size()).toString(); auto postParam = QString(); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index b4bfe00a4..8d76edbc0 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "core/update_checker.h" #include "boxes/confirm_phone_box.h" +#include "boxes/background_box.h" #include "boxes/confirm_box.h" #include "boxes/share_box.h" #include "boxes/connection_box.h" @@ -162,11 +163,23 @@ bool ShowPassport(const Match &match, const QVariant &context) { qthelp::UrlParamNameTransform::ToLower)); } +bool ShowWallPaper(const Match &match, const QVariant &context) { + if (!AuthSession::Exists()) { + return false; + } + const auto params = url_parse_params( + match->captured(1), + qthelp::UrlParamNameTransform::ToLower); + return BackgroundPreviewBox::Start( + params.value(qsl("slug")), + params.value(qsl("mode"))); +} + bool ResolveUsername(const Match &match, const QVariant &context) { if (!AuthSession::Exists()) { return false; } - auto params = url_parse_params( + const auto params = url_parse_params( match->captured(1), qthelp::UrlParamNameTransform::ToLower); const auto domain = params.value(qsl("domain")); @@ -280,6 +293,10 @@ const std::vector &LocalUrlHandlers() { qsl("^passport/?\\?(.+)(#|$)"), ShowPassport }, + { + qsl("^bg/?\\?(.+)(#|$)"), + ShowWallPaper + }, { qsl("^resolve/?\\?(.+)(#|$)"), ResolveUsername diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 0cd774e72..39e53d404 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -382,7 +382,8 @@ void DocumentOpenClickHandler::Open( if (data->status != FileReady) return; QString filename; - if (!data->saveToCache()) { + if (!data->saveToCache() + || (location.isEmpty() || (!data->data().isEmpty()))) { filename = documentSaveFilename(data); if (filename.isEmpty()) return; } @@ -575,6 +576,9 @@ void DocumentData::setattributes(const QVector &attributes } bool DocumentData::checkWallPaperProperties() { + if (type == WallPaperDocument) { + return true; + } if (type != FileDocument || !thumb || !dimensions.width() @@ -1301,6 +1305,8 @@ uint8 DocumentData::cacheTag() const { return Data::kVideoMessageCacheTag; } else if (isAnimation()) { return Data::kAnimationCacheTag; + } else if (type == WallPaperDocument) { + return Data::kImageCacheTag; } return 0; } @@ -1526,4 +1532,39 @@ paf pif ps1 reg rgs scr sct shb shs u3p vb vbe vbs vbscript ws wsf"); FileExtension(filepath).toLower()); } +base::binary_guard ReadImageAsync( + not_null document, + FnMut done) { + auto [left, right] = base::make_binary_guard(); + crl::async([ + bytes = document->data(), + path = document->filepath(), + guard = std::move(left), + callback = std::move(done) + ]() mutable { + auto format = QByteArray(); + if (bytes.isEmpty()) { + QFile f(path); + if (f.size() <= App::kImageSizeLimit + && f.open(QIODevice::ReadOnly)) { + bytes = f.readAll(); + } + } + auto image = bytes.isEmpty() + ? QImage() + : App::readImage(bytes, &format, false, nullptr); + crl::on_main([ + guard = std::move(guard), + image = std::move(image), + callback = std::move(callback) + ]() mutable { + if (!guard) { + return; + } + callback(std::move(image)); + }); + }); + return std::move(right); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index bb91c7a9d..e5186ba49 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -340,5 +340,8 @@ namespace Data { QString FileExtension(const QString &filepath); bool IsValidMediaFile(const QString &filepath); bool IsExecutableName(const QString &filepath); +base::binary_guard ReadImageAsync( + not_null document, + FnMut done); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_document_good_thumbnail.cpp b/Telegram/SourceFiles/data/data_document_good_thumbnail.cpp index 806e1ea75..d328c2fd1 100644 --- a/Telegram/SourceFiles/data/data_document_good_thumbnail.cpp +++ b/Telegram/SourceFiles/data/data_document_good_thumbnail.cpp @@ -25,7 +25,7 @@ GoodThumbSource::GoodThumbSource(not_null document) } void GoodThumbSource::generate(base::binary_guard &&guard) { - if (!guard.alive()) { + if (!guard) { return; } const auto data = _document->data(); @@ -74,7 +74,7 @@ void GoodThumbSource::ready( image = std::move(image), bytes = std::move(bytesForCache) ]() mutable { - if (!guard.alive()) { + if (!guard) { return; } if (image.isNull()) { @@ -162,7 +162,7 @@ bool GoodThumbSource::displayLoading() { } void GoodThumbSource::cancel() { - _loading.kill(); + _loading = nullptr; } float64 GoodThumbSource::progress() { diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index 3321eb61e..704e3a23f 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -178,12 +178,6 @@ struct FileReferenceAccumulator { }, [](const MTPDmessages_savedGifsNotModified &data) { }); } - void push(const MTPaccount_WallPapers &data) { - data.match([&](const MTPDaccount_wallPapers &data) { - push(data.vwallpapers); - }, [](const MTPDaccount_wallPapersNotModified &) { - }); - } UpdatedFileReferences result; }; @@ -248,7 +242,7 @@ UpdatedFileReferences GetFileReferences(const MTPmessages_SavedGifs &data) { return GetFileReferencesHelper(data); } -UpdatedFileReferences GetFileReferences(const MTPaccount_WallPapers &data) { +UpdatedFileReferences GetFileReferences(const MTPWallPaper &data) { return GetFileReferencesHelper(data); } diff --git a/Telegram/SourceFiles/data/data_file_origin.h b/Telegram/SourceFiles/data/data_file_origin.h index d4226c7c3..e94805559 100644 --- a/Telegram/SourceFiles/data/data_file_origin.h +++ b/Telegram/SourceFiles/data/data_file_origin.h @@ -60,9 +60,17 @@ struct FileOriginSavedGifs { } }; -struct FileOriginWallpapers { - inline bool operator<(const FileOriginWallpapers &) const { - return false; +struct FileOriginWallpaper { + FileOriginWallpaper(uint64 paperId, uint64 accessHash) + : paperId(paperId) + , accessHash(accessHash) { + } + + uint64 paperId = 0; + uint64 accessHash = 0; + + inline bool operator<(const FileOriginWallpaper &other) const { + return paperId < other.paperId; } }; @@ -73,7 +81,7 @@ struct FileOrigin { FileOriginPeerPhoto, FileOriginStickerSet, FileOriginSavedGifs, - FileOriginWallpapers>; + FileOriginWallpaper>; FileOrigin() = default; FileOrigin(FileOriginMessage data) : data(data) { @@ -86,7 +94,7 @@ struct FileOrigin { } FileOrigin(FileOriginSavedGifs data) : data(data) { } - FileOrigin(FileOriginWallpapers data) : data(data) { + FileOrigin(FileOriginWallpaper data) : data(data) { } explicit operator bool() const { @@ -130,6 +138,6 @@ UpdatedFileReferences GetFileReferences( const MTPmessages_FavedStickers &data); UpdatedFileReferences GetFileReferences(const MTPmessages_StickerSet &data); UpdatedFileReferences GetFileReferences(const MTPmessages_SavedGifs &data); -UpdatedFileReferences GetFileReferences(const MTPaccount_WallPapers &data); +UpdatedFileReferences GetFileReferences(const MTPWallPaper &data); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 4a783f9a8..fcbe56215 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -133,7 +133,7 @@ WebPageType ParseWebPageType(const MTPDwebPage &page) { if (type == qstr("photo")) return WebPageType::Photo; if (type == qstr("video")) return WebPageType::Video; if (type == qstr("profile")) return WebPageType::Profile; - if (type == qstr("telegram_wallpaper")) return WebPageType::WallPaper; + if (type == qstr("telegram_background")) return WebPageType::WallPaper; return page.has_cached_page() ? WebPageType::ArticleWithIV : WebPageType::Article; @@ -218,7 +218,7 @@ bool WebPageData::applyChanges( pendingTill = newPendingTill; ++version; - if (type == WebPageType::WallPaper) { + if (type == WebPageType::WallPaper && document) { document->checkWallPaperProperties(); } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 659c158b5..9176352cf 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -926,7 +926,9 @@ bool MainWidget::sendMessageFail(const RPCError &error) { } void MainWidget::onCacheBackground() { - if (Window::Theme::Background()->tile()) { + if (Window::Theme::Background()->color()) { + return; + } else if (Window::Theme::Background()->tile()) { auto &bg = Window::Theme::Background()->pixmapForTiled(); auto result = QImage(_willCacheFor.width() * cIntRetinaFactor(), _willCacheFor.height() * cIntRetinaFactor(), QImage::Format_RGB32); @@ -1418,17 +1420,23 @@ void MainWidget::updateScrollColors() { _history->updateScrollColors(); } -void MainWidget::setChatBackground(const Data::WallPaper &background) { +void MainWidget::setChatBackground( + const Data::WallPaper &background, + QImage &&image) { + using namespace Window::Theme; + + if (isReadyChatBackground(background, image)) { + setReadyChatBackground(background, std::move(image)); + return; + } + _background = std::make_unique(); _background->data = background; - if (_background->data.document) { - _background->data.document->save( - Data::FileOriginWallpapers(), - QString()); - } else if (_background->data.thumb) { - _background->data.thumb->loadEvenCancelled( - Data::FileOriginWallpapers()); - } + _background->data.document->save( + Data::FileOriginWallpaper( + _background->data.id, + _background->data.accessHash), + QString()); checkChatBackground(); const auto tile = (background.id == Window::Theme::kInitialBackground); @@ -1436,13 +1444,47 @@ void MainWidget::setChatBackground(const Data::WallPaper &background) { Window::Theme::Background()->notify(Update(Update::Type::Start, tile)); } +bool MainWidget::isReadyChatBackground( + const Data::WallPaper &background, + const QImage &image) const { + return !image.isNull() + || !background.document + || Window::Theme::GetWallPaperColor(background.slug); +} + +void MainWidget::setReadyChatBackground( + const Data::WallPaper &background, + QImage &&image) { + using namespace Window::Theme; + + if (image.isNull() + && !background.document + && background.thumb + && background.thumb->loaded()) { + image = background.thumb->pixNoCache(Data::FileOrigin()).toImage(); + } + + const auto resetToDefault = image.isNull() + && !background.document + && !GetWallPaperColor(background.slug) + && (background.id != kInitialBackground); + const auto ready = resetToDefault + ? Data::WallPaper{ kDefaultBackground } + : background; + + Background()->setImage(ready, std::move(image)); + const auto tile = (ready.id == kInitialBackground); + Background()->setTile(tile); + Ui::ForceFullRepaint(this); +} + bool MainWidget::chatBackgroundLoading() { return (_background != nullptr); } float64 MainWidget::chatBackgroundProgress() const { if (_background) { - if (_background->generating.alive()) { + if (_background->generating) { return 1.; } else if (_background->data.document) { return _background->data.document->progress(); @@ -1454,7 +1496,7 @@ float64 MainWidget::chatBackgroundProgress() const { } void MainWidget::checkChatBackground() { - if (!_background || _background->generating.alive()) { + if (!_background || _background->generating) { return; } const auto document = _background->data.document; @@ -1464,42 +1506,13 @@ void MainWidget::checkChatBackground() { return; } - if (!document) { - const auto &thumb = _background->data.thumb; - setGeneratedBackground(thumb - ? thumb->pixNoCache(Data::FileOrigin()).toImage() - : QImage()); - return; - } - auto [left, right] = base::make_binary_guard(); - _background->generating = std::move(left); - crl::async([ - this, - bytes = document->data(), - path = document->filepath(), - guard = std::move(right) - ]() mutable { - auto format = QByteArray(); - if (bytes.isEmpty()) { - QFile f(path); - if (f.size() <= App::kImageSizeLimit - && f.open(QIODevice::ReadOnly)) { - bytes = f.readAll(); - } - } - auto image = bytes.isEmpty() - ? QImage() - : App::readImage(bytes, &format, false, nullptr); - crl::on_main([ - this, - guard = std::move(guard), - image = std::move(image) - ]() mutable { - if (!guard.alive()) { - return; - } - setGeneratedBackground(std::move(image)); - }); + _background->generating = Data::ReadImageAsync(document, [=]( + QImage &&image) { + const auto background = base::take(_background); + const auto ready = image.isNull() + ? Data::WallPaper{ Window::Theme::kDefaultBackground } + : background->data; + setChatBackground(ready, std::move(image)); }); } @@ -1507,26 +1520,6 @@ ImagePtr MainWidget::newBackgroundThumb() { return _background ? _background->data.thumb : ImagePtr(); } -void MainWidget::setGeneratedBackground(QImage &&image) { - using namespace Window::Theme; - - if (image.isNull()) { - Background()->setImage({ kDefaultBackground }); - } else if (false - || _background->data.id == kInitialBackground - || _background->data.id == kDefaultBackground) { - Background()->setImage(_background->data); - } else { - Background()->setImage( - _background->data, - std::move(image)); - } - const auto tile = (_background->data.id == kInitialBackground); - Background()->setTile(tile); - _background = nullptr; - crl::on_main(this, [=] { update(); }); -} - void MainWidget::messageDataReceived(ChannelData *channel, MsgId msgId) { _history->messageDataReceived(channel, msgId); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 374b6c14d..dab9e9ec5 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -227,7 +227,9 @@ public: QPixmap cachedBackground(const QRect &forRect, int &x, int &y); void updateScrollColors(); - void setChatBackground(const Data::WallPaper &background); + void setChatBackground( + const Data::WallPaper &background, + QImage &&image = QImage()); bool chatBackgroundLoading(); float64 chatBackgroundProgress() const; void checkChatBackground(); @@ -454,7 +456,12 @@ private: void ensureFirstColumnResizeAreaCreated(); void ensureThirdColumnResizeAreaCreated(); - void setGeneratedBackground(QImage &&image); + bool isReadyChatBackground( + const Data::WallPaper &background, + const QImage &image) const; + void setReadyChatBackground( + const Data::WallPaper &background, + QImage &&image); not_null _controller; bool _started = false; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 74e546fb1..51d7db50e 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -1840,7 +1840,7 @@ void MediaView::initThemePreview() { Window::Theme::CurrentData current; current.backgroundId = Window::Theme::Background()->id(); - current.backgroundImage = Window::Theme::Background()->pixmap().toImage(); + current.backgroundImage = Window::Theme::Background()->createCurrentImage(); current.backgroundTiled = Window::Theme::Background()->tile(); const auto path = _doc->location().name(); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 3813496e3..cefbd2c49 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -265,26 +265,35 @@ void BackgroundRow::updateImage() { Painter p(&back); PainterHighQualityEnabler hq(p); - const auto &pix = Window::Theme::Background()->pixmap(); - const auto sx = (pix.width() > pix.height()) - ? ((pix.width() - pix.height()) / 2) - : 0; - const auto sy = (pix.height() > pix.width()) - ? ((pix.height() - pix.width()) / 2) - : 0; - const auto s = (pix.width() > pix.height()) - ? pix.height() - : pix.width(); - p.drawPixmap( - 0, - 0, - st::settingsBackgroundThumb, - st::settingsBackgroundThumb, - pix, - sx, - sy, - s, - s); + if (const auto color = Window::Theme::Background()->color()) { + p.fillRect( + 0, + 0, + st::settingsBackgroundThumb, + st::settingsBackgroundThumb, + *color); + } else { + const auto &pix = Window::Theme::Background()->pixmap(); + const auto sx = (pix.width() > pix.height()) + ? ((pix.width() - pix.height()) / 2) + : 0; + const auto sy = (pix.height() > pix.width()) + ? ((pix.height() - pix.width()) / 2) + : 0; + const auto s = (pix.width() > pix.height()) + ? pix.height() + : pix.width(); + p.drawPixmap( + 0, + 0, + st::settingsBackgroundThumb, + st::settingsBackgroundThumb, + pix, + sx, + sy, + s, + s); + } } Images::prepareRound(back, ImageRoundRadius::Small); _background = App::pixmapFromImageInPlace(std::move(back)); diff --git a/Telegram/SourceFiles/storage/cache/storage_cache_cleaner.cpp b/Telegram/SourceFiles/storage/cache/storage_cache_cleaner.cpp index 664ca5257..7e6a18d0d 100644 --- a/Telegram/SourceFiles/storage/cache/storage_cache_cleaner.cpp +++ b/Telegram/SourceFiles/storage/cache/storage_cache_cleaner.cpp @@ -74,7 +74,7 @@ void CleanerObject::scheduleNext() { return; } _weak.with([](CleanerObject &that) { - if (that._guard.alive()) { + if (that._guard) { that.cleanNext(); } }); diff --git a/Telegram/SourceFiles/storage/cache/storage_cache_compactor.cpp b/Telegram/SourceFiles/storage/cache/storage_cache_compactor.cpp index d439d9203..10f49fbeb 100644 --- a/Telegram/SourceFiles/storage/cache/storage_cache_compactor.cpp +++ b/Telegram/SourceFiles/storage/cache/storage_cache_compactor.cpp @@ -175,7 +175,7 @@ void CompactorObject::fail() { void CompactorObject::done(int64 till) { const auto path = compactPath(); _database.with([=, good = std::move(_guard)](DatabaseObject &database) { - if (good.alive()) { + if (good) { database.compactorDone(path, till); } }); diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index 22682519b..20f8dece3 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -247,7 +247,7 @@ void FileLoader::localLoaded( const StorageImageSaved &result, const QByteArray &imageFormat, const QImage &imageData) { - _localLoading.kill(); + _localLoading = nullptr; if (result.data.isEmpty()) { _localStatus = LocalStatus::NotFound; start(true); @@ -383,7 +383,7 @@ void FileLoader::loadLocal(const Storage::Cache::Key &key) { format = std::move(format), guard = std::move(guard) ]() mutable { - if (!guard.alive()) { + if (!guard) { return; } localLoaded( @@ -433,7 +433,7 @@ bool FileLoader::tryLoadLocal() { return false; } else if (_localStatus != LocalStatus::NotTried) { return _finished; - } else if (_localLoading.alive()) { + } else if (_localLoading) { _localStatus = LocalStatus::Loading; return true; } diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index d67022981..ce404b169 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -4061,7 +4061,7 @@ bool readBackground() { #ifndef OS_MAC_OLD reader.setAutoTransform(true); #endif // OS_MAC_OLD - if (reader.read(&image)) { + if (reader.read(&image) || Window::Theme::GetWallPaperColor(slug)) { _backgroundCanWrite = false; Window::Theme::Background()->setImage({ id, diff --git a/Telegram/SourceFiles/storage/storage_databases.cpp b/Telegram/SourceFiles/storage/storage_databases.cpp index 2d5353fdc..580ed0c1b 100644 --- a/Telegram/SourceFiles/storage/storage_databases.cpp +++ b/Telegram/SourceFiles/storage/storage_databases.cpp @@ -72,7 +72,7 @@ DatabasePointer Databases::get( if (const auto i = _map.find(path); i != end(_map)) { auto &kept = i->second; Assert(kept.destroying.alive()); - kept.destroying.kill(); + kept.destroying = nullptr; kept.database->reconfigure(settings); return DatabasePointer(this, kept.database); } @@ -93,7 +93,7 @@ void Databases::destroy(Cache::Database *database) { database->close(); database->waitForCleaner([=, guard = std::move(second)]() mutable { crl::on_main([=, guard = std::move(guard)]{ - if (!guard.alive()) { + if (!guard) { return; } _map.erase(path); diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index 7b5d2e591..d53f02856 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -278,7 +278,7 @@ AdminLog::OwnedItem GenerateCommentItem( replyTo, viaBotId, unixtime(), - Auth().userId(), + history->session().userId(), QString(), TextWithEntities{ TextUtilities::Clean(data.comment) }); return AdminLog::OwnedItem(delegate, item); @@ -296,7 +296,7 @@ AdminLog::OwnedItem GenerateContactItem( const auto message = MTP_message( MTP_flags(flags), MTP_int(id), - MTP_int(Auth().userId()), + MTP_int(history->session().userId()), peerToMTP(history->peer->id), MTPMessageFwdHeader(), MTP_int(viaBotId), diff --git a/Telegram/SourceFiles/support/support_templates.cpp b/Telegram/SourceFiles/support/support_templates.cpp index 643e3b83b..bc52d949d 100644 --- a/Telegram/SourceFiles/support/support_templates.cpp +++ b/Telegram/SourceFiles/support/support_templates.cpp @@ -476,7 +476,7 @@ void Templates::reload() { void Templates::load() { if (_reloadAfterRead) { return; - } else if (_reading.alive() || _updates) { + } else if (_reading || _updates) { _reloadAfterRead = true; return; } @@ -491,7 +491,7 @@ void Templates::load() { result = std::move(result), guard = std::move(guard) ]() mutable { - if (!guard.alive()) { + if (!guard) { return; } setData(std::move(result.result)); diff --git a/Telegram/SourceFiles/ui/emoji_config.cpp b/Telegram/SourceFiles/ui/emoji_config.cpp index 6a1d5024e..079ed986a 100644 --- a/Telegram/SourceFiles/ui/emoji_config.cpp +++ b/Telegram/SourceFiles/ui/emoji_config.cpp @@ -927,7 +927,7 @@ void Instance::checkUniversalImages() { if (_id != Universal->id()) { _id = Universal->id(); - _generating.kill(); + _generating = nullptr; _sprites.clear(); } if (!Universal->ensureLoaded() && Universal->id() != 0) { @@ -952,7 +952,7 @@ void Instance::generateCache() { image = universal->generate(size, index), guard = std::move(guard) ]() mutable { - if (!guard.alive() || universal != Universal) { + if (!guard || universal != Universal) { return; } pushSprite(std::move(image)); diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp index ba3eb7d6d..d633c62ff 100644 --- a/Telegram/SourceFiles/ui/image/image.cpp +++ b/Telegram/SourceFiles/ui/image/image.cpp @@ -750,6 +750,11 @@ QPixmap Image::pixBlurredColoredNoCache( return App::pixmapFromImageInPlace(prepareColored(add, img)); } +QImage Image::original() const { + checkSource(); + return _data; +} + void Image::automaticLoad(Data::FileOrigin origin, const HistoryItem *item) { if (!loaded()) { _source->automaticLoad(origin, item); diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h index e954f175f..6f0ba3eac 100644 --- a/Telegram/SourceFiles/ui/image/image.h +++ b/Telegram/SourceFiles/ui/image/image.h @@ -102,6 +102,8 @@ public: static ImagePtr Blank(); + QImage original() const; + const QPixmap &pix( Data::FileOrigin origin, int32 w = 0, diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index b4294cfd6..1912f3a9c 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -411,7 +411,7 @@ void Widget::hideSlow() { auto [left, right] = base::make_binary_guard(); _hidingDelayed = std::move(left); App::CallDelayed(st::notifySlowHide, this, [=, guard = std::move(right)] { - if (guard.alive() && _hiding) { + if (guard && _hiding) { hideFast(); } }); diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 3cfb1b16d..24890b12f 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -78,6 +78,10 @@ void SectionWidget::PaintBackground(QWidget *widget, QPaintEvent *event) { auto clip = event->rect(); auto fill = QRect(0, 0, widget->width(), App::main()->height()); + if (const auto color = Window::Theme::Background()->color()) { + p.fillRect(fill, *color); + return; + } auto fromy = App::main()->backgroundFromY(); auto x = 0, y = 0; auto cached = App::main()->cachedBackground(fill, x, y); diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index aec218e3d..941c0e3db 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -24,7 +24,6 @@ constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; constexpr auto kThemeBackgroundSizeLimit = 4 * 1024 * 1024; constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024; constexpr auto kThemeSchemeSizeLimit = 1024 * 1024; -constexpr auto kMinimumTiledSize = 512; constexpr auto kNightThemeFile = str_const(":/gui/night.tdesktop-theme"); struct Applying { @@ -327,8 +326,9 @@ bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr } QImage prepareBackgroundImage(QImage &&image) { - if (image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_ARGB32_Premultiplied && image.format() != QImage::Format_RGB32) { - image = std::move(image).convertToFormat(QImage::Format_RGB32); + if (image.format() != QImage::Format_ARGB32_Premultiplied) { + image = std::move(image).convertToFormat( + QImage::Format_ARGB32_Premultiplied); } image.setDevicePixelRatio(cRetinaFactor()); return std::move(image); @@ -391,9 +391,9 @@ void ChatBackground::setImage( && !nightMode() && _themeAbsolutePath.isEmpty(); if (paper.id == kThemeBackground && _themeImage.isNull()) { - _paper = { kDefaultBackground }; + setPaper({ kDefaultBackground }); } else { - _paper = paper; + setPaper(paper); if (needResetAdjustable) { // If we had a default color theme with non-default background, // and we switch to default background we must somehow switch from @@ -410,18 +410,21 @@ void ChatBackground::setImage( || id() == details::kTestingEditorBackground) { if (id() == details::kTestingDefaultBackground || image.isNull()) { image.load(qsl(":/gui/art/bg.jpg")); - _paper = { details::kTestingDefaultBackground }; + setPaper({ details::kTestingDefaultBackground }); } - setPreparedImage(std::move(image)); + setPreparedImage(prepareBackgroundImage(std::move(image))); } else { if (id() == kInitialBackground) { image.load(qsl(":/gui/art/bg_initial.jpg")); const auto scale = cScale() * cIntRetinaFactor(); if (scale != 100) { - image = image.scaledToWidth(ConvertScale(image.width(), scale), Qt::SmoothTransformation); + image = image.scaledToWidth( + ConvertScale(image.width(), scale), + Qt::SmoothTransformation); } - } else if (id() == kDefaultBackground || image.isNull()) { - _paper = { kDefaultBackground }; + } else if (id() == kDefaultBackground + || (!color() && image.isNull())) { + setPaper({ kDefaultBackground }); image.load(qsl(":/gui/art/bg.jpg")); } Local::writeBackground( @@ -429,9 +432,16 @@ void ChatBackground::setImage( ((id() == kDefaultBackground || id() == kInitialBackground) ? QImage() : image)); - setPreparedImage(prepareBackgroundImage(std::move(image))); + if (const auto fill = color()) { + if (adjustPaletteRequired()) { + adjustPaletteUsingColor(*fill); + } + } else { + setPreparedImage(prepareBackgroundImage(std::move(image))); + } } - Assert(!_pixmap.isNull() && !_pixmapForTiled.isNull()); + Assert((!_pixmap.isNull() && !_pixmapForTiled.isNull()) || color()); + notify(BackgroundUpdate(BackgroundUpdate::Type::New, tile())); if (needResetAdjustable) { notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, tile()), true); @@ -440,33 +450,11 @@ void ChatBackground::setImage( } void ChatBackground::setPreparedImage(QImage &&image) { - image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied); + Expects(image.format() == QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(cRetinaFactor()); - const auto adjustColors = [&] { - const auto usingThemeBackground = [&] { - return (id() == kThemeBackground) - || (id() == details::kTestingThemeBackground); - }; - const auto usingDefaultBackground = [&] { - return (id() == kDefaultBackground) - || (id() == details::kTestingDefaultBackground); - }; - const auto testingPalette = [&] { - const auto path = AreTestingTheme() - ? GlobalApplying.pathAbsolute - : _themeAbsolutePath; - return IsPaletteTestingPath(path); - }; - - if (testingPalette()) { - return false; - } else if (isNonDefaultThemeOrBackground() || nightMode()) { - return !usingThemeBackground(); - } - return !usingDefaultBackground(); - }(); - if (adjustColors) { + if (adjustPaletteRequired()) { adjustPaletteUsingBackground(image); } @@ -500,6 +488,35 @@ void ChatBackground::setPreparedImage(QImage &&image) { } } +void ChatBackground::setPaper(const Data::WallPaper &paper) { + _paper = paper; + _paperColor = GetWallPaperColor(_paper.slug); +} + +bool ChatBackground::adjustPaletteRequired() { + const auto usingThemeBackground = [&] { + return (id() == kThemeBackground) + || (id() == details::kTestingThemeBackground); + }; + const auto usingDefaultBackground = [&] { + return (id() == kDefaultBackground) + || (id() == details::kTestingDefaultBackground); + }; + const auto testingPalette = [&] { + const auto path = AreTestingTheme() + ? GlobalApplying.pathAbsolute + : _themeAbsolutePath; + return IsPaletteTestingPath(path); + }; + + if (testingPalette()) { + return false; + } else if (isNonDefaultThemeOrBackground() || nightMode()) { + return !usingThemeBackground(); + } + return !usingDefaultBackground(); +} + void ChatBackground::adjustPaletteUsingBackground(const QImage &img) { Assert(img.format() == QImage::Format_ARGB32_Premultiplied); @@ -522,9 +539,13 @@ void ChatBackground::adjustPaletteUsingBackground(const QImage &img) { } } - auto bgColor = QColor(components[0], components[1], components[2]); - auto hue = bgColor.hslHueF(); - auto saturation = bgColor.hslSaturationF(); + adjustPaletteUsingColor( + QColor(components[0], components[1], components[2])); +} + +void ChatBackground::adjustPaletteUsingColor(QColor color) { + auto hue = color.hslHueF(); + auto saturation = color.hslSaturationF(); for (const auto &color : _adjustableColors) { adjustColor(color.item, hue, saturation); } @@ -534,6 +555,18 @@ WallPaperId ChatBackground::id() const { return _paper.id; } +QImage ChatBackground::createCurrentImage() const { + if (const auto fill = color()) { + auto result = QImage( + kMinimumTiledSize, + kMinimumTiledSize, + QImage::Format_ARGB32_Premultiplied); + result.fill(*fill); + return result; + } + return pixmap().toImage(); +} + bool ChatBackground::tile() const { return nightMode() ? _tileNightValue : _tileDayValue; } @@ -683,21 +716,21 @@ void ChatBackground::setTestingDefaultTheme() { void ChatBackground::keepApplied(const QString &path, bool write) { setThemeAbsolutePath(path); if (id() == details::kTestingEditorBackground) { - _paper = { kCustomBackground }; + setPaper({ kCustomBackground }); _themeImage = QImage(); _themeTile = false; if (write) { writeNewBackgroundSettings(); } } else if (id() == details::kTestingThemeBackground) { - _paper = { kThemeBackground }; - _themeImage = _pixmap.toImage(); + setPaper({ kThemeBackground }); + _themeImage = prepareBackgroundImage(_pixmap.toImage()); _themeTile = tile(); if (write) { writeNewBackgroundSettings(); } } else if (id() == details::kTestingDefaultBackground) { - _paper = { kDefaultBackground }; + setPaper({ kDefaultBackground }); _themeImage = QImage(); _themeTile = false; if (write) { @@ -980,6 +1013,35 @@ bool IsPaletteTestingPath(const QString &path) { return false; } +std::optional GetWallPaperColor(const QString &slug) { + if (slug.size() != 6) { + return {}; + } else if (ranges::find_if(slug, [](QChar ch) { + return (ch < 'a' || ch > 'f') + && (ch < 'A' || ch > 'F') + && (ch < '0' || ch > '9'); + }) != slug.end()) { + return {}; + } + const auto component = [](const QString &text, int index) { + const auto decimal = [](QChar hex) { + const auto code = hex.unicode(); + return (code >= '0' && code <= '9') + ? int(code - '0') + : (code >= 'a' && code <= 'f') + ? int(code - 'a' + 0x0a) + : int(code - 'A' + 0x0a); + }; + index *= 2; + return decimal(text[index]) * 0x10 + decimal(text[index + 1]); + }; + return QColor( + component(slug, 0), + component(slug, 1), + component(slug, 2), + 255); +} + void ComputeBackgroundRects(QRect wholeFill, QSize imageSize, QRect &to, QRect &from) { if (uint64(imageSize.width()) * wholeFill.height() > uint64(imageSize.height()) * wholeFill.width()) { float64 pxsize = wholeFill.height() / float64(imageSize.height()); diff --git a/Telegram/SourceFiles/window/themes/window_theme.h b/Telegram/SourceFiles/window/themes/window_theme.h index d07b9c1cd..b4589bbc0 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.h +++ b/Telegram/SourceFiles/window/themes/window_theme.h @@ -41,6 +41,8 @@ constexpr auto kCustomBackground = details::FromLegacyBackgroundId(-1); constexpr auto kInitialBackground = details::FromLegacyBackgroundId(0); constexpr auto kDefaultBackground = details::FromLegacyBackgroundId(105); +constexpr auto kMinimumTiledSize = 512; + struct Cached { QByteArray colors; QByteArray background; @@ -78,16 +80,18 @@ void ApplyDefaultWithPath(const QString &themePath); bool ApplyEditedPalette(const QString &path, const QByteArray &content); void KeepApplied(); QString NightThemePath(); -bool IsNightMode(); +[[nodiscard]] bool IsNightMode(); void SetNightModeValue(bool nightMode); void ToggleNightMode(); void ToggleNightMode(const QString &themePath); -bool IsNonDefaultBackground(); +[[nodiscard]] bool IsNonDefaultBackground(); void Revert(); bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent); bool IsPaletteTestingPath(const QString &path); +[[nodiscard]] std::optional GetWallPaperColor(const QString &slug); + struct BackgroundUpdate { enum class Type { New, @@ -121,7 +125,7 @@ public: void setTileDayValue(bool tile); void setTileNightValue(bool tile); void setThemeAbsolutePath(const QString &path); - QString themeAbsolutePath() const; + [[nodiscard]] QString themeAbsolutePath() const; void reset(); void setTestingTheme(Instance &&theme); @@ -129,16 +133,20 @@ public: void setTestingDefaultTheme(); void revert(); - WallPaperId id() const; - const QPixmap &pixmap() const { + [[nodiscard]] WallPaperId id() const; + [[nodiscard]] const QPixmap &pixmap() const { return _pixmap; } - const QPixmap &pixmapForTiled() const { + [[nodiscard]] const QPixmap &pixmapForTiled() const { return _pixmapForTiled; } - bool tile() const; - bool tileDay() const; - bool tileNight() const; + [[nodiscard]] std::optional color() const { + return _paperColor; + } + [[nodiscard]] QImage createCurrentImage() const; + [[nodiscard]] bool tile() const; + [[nodiscard]] bool tileDay() const; + [[nodiscard]] bool tileNight() const; private: struct AdjustableColor { @@ -152,16 +160,19 @@ private: void saveForRevert(); void setPreparedImage(QImage &&image); void writeNewBackgroundSettings(); + void setPaper(const Data::WallPaper &paper); + [[nodiscard]] bool adjustPaletteRequired(); void adjustPaletteUsingBackground(const QImage &img); + void adjustPaletteUsingColor(QColor color); void restoreAdjustableColors(); void setNightModeValue(bool nightMode); - bool nightMode() const; + [[nodiscard]] bool nightMode() const; void toggleNightMode(std::optional themePath); void keepApplied(const QString &path, bool write); - bool isNonDefaultThemeOrBackground(); - bool isNonDefaultBackground(); + [[nodiscard]] bool isNonDefaultThemeOrBackground(); + [[nodiscard]] bool isNonDefaultBackground(); friend bool IsNightMode(); friend void SetNightModeValue(bool nightMode); @@ -171,6 +182,7 @@ private: friend bool IsNonDefaultBackground(); Data::WallPaper _paper = { details::kUninitializedBackground }; + std::optional _paperColor; QPixmap _pixmap; QPixmap _pixmapForTiled; bool _nightMode = false; diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp index cfdfcc76b..68da92db6 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp @@ -319,8 +319,8 @@ void Editor::Inner::prepare() { } Fn Editor::Inner::exportCallback() { - return App::LambdaDelayed(st::defaultRippleAnimation.hideDuration, this, [this] { - auto background = Background()->pixmap().toImage(); + return App::LambdaDelayed(st::defaultRippleAnimation.hideDuration, this, [=] { + auto background = Background()->createCurrentImage(); auto backgroundContent = QByteArray(); auto tiled = Background()->tile(); {