From 1bc5277d51e22b591f448ca2a7120e6fec329416 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Aug 2021 18:11:28 +0300 Subject: [PATCH] Show color / gradient wallpapers in WebPage previews. --- Telegram/SourceFiles/data/data_wall_paper.cpp | 14 +- .../history/view/media/history_view_file.cpp | 12 +- .../view/media/history_view_media_common.cpp | 5 +- .../media/history_view_theme_document.cpp | 207 +++++++++++------- .../view/media/history_view_theme_document.h | 12 +- .../view/media/history_view_web_page.cpp | 4 + 6 files changed, 171 insertions(+), 83 deletions(-) diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index ade068261..14c553704 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -127,11 +127,12 @@ constexpr auto kVersion = 1; if (!count || count > kMaxColors || view.size() != count * 7 - 1) { return {}; } - const auto separator = QChar(count > 2 ? '~' : '-'); auto result = std::vector(); result.reserve(count); for (auto i = 0; i != count; ++i) { - if (i + 1 < count && view[i * 7 + 6] != separator) { + if (i + 1 < count + && view[i * 7 + 6] != '~' + && (count > 2 || view[i * 7 + 6] != '-')) { return {}; } else if (const auto parsed = ColorFromString(view.mid(i * 7, 6))) { result.push_back(*parsed); @@ -268,6 +269,9 @@ QString WallPaper::shareUrl(not_null session) const { params.push_back("intensity=" + QString::number(_intensity)); } } + if (_rotation && backgroundColors().size() == 2) { + params.push_back("rotation=" + QString::number(_rotation)); + } auto mode = QStringList(); if (_blurred) { mode.push_back("blur"); @@ -355,6 +359,12 @@ WallPaper WallPaper::withUrlParams( if (result._backgroundColors.empty()) { result._backgroundColors = ColorsFromString(params.value("bg_color")); } + if (result._backgroundColors.empty()) { + result._backgroundColors = ColorsFromString(params.value("gradient")); + } + if (result._backgroundColors.empty()) { + result._backgroundColors = ColorsFromString(params.value("color")); + } if (const auto string = params.value("intensity"); !string.isEmpty()) { auto ok = false; const auto intensity = string.toInt(&ok); diff --git a/Telegram/SourceFiles/history/view/media/history_view_file.cpp b/Telegram/SourceFiles/history/view/media/history_view_file.cpp index 45cc23e05..b06f01785 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_file.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_file.cpp @@ -58,9 +58,15 @@ void File::setLinks( void File::refreshParentId(not_null realParent) { const auto contextId = realParent->fullId(); - _openl->setMessageId(contextId); - _savel->setMessageId(contextId); - _cancell->setMessageId(contextId); + if (_openl) { + _openl->setMessageId(contextId); + } + if (_savel) { + _savel->setMessageId(contextId); + } + if (_cancell) { + _cancell->setMessageId(contextId); + } } void File::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index 5ab2ea3b5..00f28fa0c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "data/data_document.h" +#include "data/data_wall_paper.h" #include "history/view/history_view_element.h" #include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_photo.h" @@ -78,7 +79,7 @@ std::unique_ptr CreateAttach( return std::make_unique( parent, document, - webpageUrl); + ThemeDocument::ParamsFromUrl(webpageUrl)); } return std::make_unique(parent, parent->data(), document); } else if (photo) { @@ -86,6 +87,8 @@ std::unique_ptr CreateAttach( parent, parent->data(), photo); + } else if (const auto params = ThemeDocument::ParamsFromUrl(webpageUrl)) { + return std::make_unique(parent, nullptr, params); } return nullptr; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index 17511acbd..df4163ebf 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_wall_paper.h" #include "base/qthelp_url.h" +#include "core/local_url_handlers.h" #include "ui/text/format_values.h" #include "ui/cached_round_corners.h" #include "ui/ui_utility.h" @@ -28,19 +29,48 @@ namespace HistoryView { ThemeDocument::ThemeDocument( not_null parent, - not_null document, - const QString &url) + DocumentData *document) +: ThemeDocument(parent, document, std::nullopt) { +} + +ThemeDocument::ThemeDocument( + not_null parent, + DocumentData *document, + const std::optional ¶ms) : File(parent, parent->data()) , _data(document) { - Expects(_data->hasThumbnail() || _data->isTheme()); + Expects(params.has_value() || _data->hasThumbnail() || _data->isTheme()); - if (_data->isWallPaper()) { - fillPatternFieldsFrom(url); + if (params) { + _background = params->backgroundColors(); + _patternOpacity = params->patternOpacity(); + _gradientRotation = params->gradientRotation(); } + const auto fullId = _parent->data()->fullId(); + if (_data) { + _data->loadThumbnail(fullId); + setDocumentLinks(_data, parent->data()); + setStatusSize(Ui::FileStatusSizeReady, _data->size, -1, 0); + } else { + class EmptyFileClickHandler final : public FileClickHandler { + public: + using FileClickHandler::FileClickHandler; - _data->loadThumbnail(_parent->data()->fullId()); - setDocumentLinks(_data, parent->data()); - setStatusSize(Ui::FileStatusSizeReady, _data->size, -1, 0); + private: + void onClickImpl() const override { + } + + }; + + // We could open BackgroundPreviewBox here, but right now + // WebPage that created ThemeDocument as its attachment does it. + // + // So just provide a non-null click handler for this hack to work. + setLinks( + std::make_shared(fullId), + nullptr, + nullptr); + } } ThemeDocument::~ThemeDocument() { @@ -50,23 +80,27 @@ ThemeDocument::~ThemeDocument() { } } -void ThemeDocument::fillPatternFieldsFrom(const QString &url) { - const auto paramsPosition = url.indexOf('?'); +std::optional ThemeDocument::ParamsFromUrl( + const QString &url) { + const auto local = Core::TryConvertUrlToLocal(url); + const auto paramsPosition = local.indexOf('?'); if (paramsPosition < 0) { - return; + return std::nullopt; } - const auto paramsString = url.mid(paramsPosition + 1); + const auto paramsString = local.mid(paramsPosition + 1); const auto params = qthelp::url_parse_params( paramsString, qthelp::UrlParamNameTransform::ToLower); - const auto paper = Data::DefaultWallPaper().withUrlParams(params); - _background = paper.backgroundColors(); - _patternOpacity = paper.patternOpacity(); - _gradientRotation = paper.gradientRotation(); + auto paper = Data::DefaultWallPaper().withUrlParams(params); + return paper.backgroundColors().empty() + ? std::nullopt + : std::make_optional(std::move(paper)); } QSize ThemeDocument::countOptimalSize() { - if (_data->isTheme()) { + if (!_data) { + return { st::maxWallPaperWidth, st::maxWallPaperHeight }; + } else if (_data->isTheme()) { return st::historyThemeSize; } const auto &location = _data->thumbnailLocation(); @@ -87,7 +121,11 @@ QSize ThemeDocument::countOptimalSize() { } QSize ThemeDocument::countCurrentSize(int newWidth) { - if (_data->isTheme()) { + if (!_data) { + _pixw = st::maxWallPaperWidth; + _pixh = st::maxWallPaperHeight; + return { _pixw, _pixh }; + } else if (_data->isTheme()) { _pixw = st::historyThemeSize.width(); _pixh = st::historyThemeSize.height(); return st::historyThemeSize; @@ -117,10 +155,12 @@ void ThemeDocument::draw(Painter &p, const QRect &r, TextSelection selection, cr ensureDataMediaCreated(); - _dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); + if (_data) { + _dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); + } auto selected = (selection == FullSelection); auto loaded = dataLoaded(); - auto displayLoading = _data->displayLoading(); + auto displayLoading = _data && _data->displayLoading(); auto paintx = 0, painty = 0, paintw = width(), painth = height(); @@ -141,59 +181,60 @@ void ThemeDocument::draw(Painter &p, const QRect &r, TextSelection selection, cr Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners); } - auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); - auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y(); - auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); - auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); - Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); - p.setFont(st::normalFont); - p.setPen(st::msgDateImgFg); - p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); - - if (radial || (!loaded && !_data->loading())) { - const auto radialOpacity = (radial && loaded && !_data->uploading()) - ? _animation->radial.opacity() : - 1.; - const auto innerSize = st::msgFileLayout.thumbSize; - QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize); - p.setPen(Qt::NoPen); - if (selected) { - p.setBrush(st::msgDateImgBgSelected); - } else if (isThumbAnimation()) { - auto over = _animation->a_thumbOver.value(1.); - p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); - } else { - auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _openl); - p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); - } - - p.setOpacity(radialOpacity * p.opacity()); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(inner); - } - - p.setOpacity(radialOpacity); - auto icon = ([radial, this, selected]() -> const style::icon* { - if (radial || _data->loading()) { - return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + if (_data) { + auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); + auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y(); + auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); + auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); + Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::DateSelectedCorners : Ui::DateCorners); + p.setFont(st::normalFont); + p.setPen(st::msgDateImgFg); + p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); + if (radial || (!loaded && !_data->loading())) { + const auto radialOpacity = (radial && loaded && !_data->uploading()) + ? _animation->radial.opacity() : + 1.; + const auto innerSize = st::msgFileLayout.thumbSize; + QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize); + p.setPen(Qt::NoPen); + if (selected) { + p.setBrush(st::msgDateImgBgSelected); + } else if (isThumbAnimation()) { + auto over = _animation->a_thumbOver.value(1.); + p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); + } else { + auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _openl); + p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); + } + + p.setOpacity(radialOpacity * p.opacity()); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + p.setOpacity(radialOpacity); + auto icon = ([radial, this, selected]() -> const style::icon* { + if (radial || _data->loading()) { + return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); + } + return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); + })(); + if (icon) { + icon->paintInCenter(p, inner); + } + p.setOpacity(1); + if (radial) { + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } - return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload); - })(); - if (icon) { - icon->paintInCenter(p, inner); - } - p.setOpacity(1); - if (radial) { - QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); - _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg); } } } void ThemeDocument::ensureDataMediaCreated() const { - if (_dataMedia) { + if (_dataMedia || !_data) { return; } _dataMedia = _data->createMediaView(); @@ -205,7 +246,7 @@ void ThemeDocument::ensureDataMediaCreated() const { } bool ThemeDocument::checkGoodThumbnail() const { - return !_data->hasThumbnail() || !_data->isPatternWallPaper(); + return _data && (!_data->hasThumbnail() || !_data->isPatternWallPaper()); } void ThemeDocument::validateThumbnail() const { @@ -222,6 +263,10 @@ void ThemeDocument::validateThumbnail() const { if (_thumbnailGood >= 0) { return; } + if (!_data) { + generateThumbnail(); + return; + } ensureDataMediaCreated(); if (const auto normal = _dataMedia->thumbnail()) { prepareThumbnailFrom(normal, 0); @@ -232,9 +277,20 @@ void ThemeDocument::validateThumbnail() const { } } +void ThemeDocument::generateThumbnail() const { + _thumbnail = Ui::PixmapFromImage(Data::GenerateWallPaper( + QSize(_pixw, _pixh) * cIntRetinaFactor(), + _background, + _gradientRotation, + _patternOpacity)); + _thumbnail.setDevicePixelRatio(cRetinaFactor()); + _thumbnailGood = 1; +} + void ThemeDocument::prepareThumbnailFrom( not_null image, int good) const { + Expects(_data != nullptr); Expects(_thumbnailGood <= good); const auto isTheme = _data->isTheme(); @@ -277,7 +333,9 @@ TextState ThemeDocument::textState(QPoint point, StateRequest request) const { } auto paintx = 0, painty = 0, paintw = width(), painth = height(); if (QRect(paintx, painty, paintw, painth).contains(point)) { - if (_data->uploading()) { + if (!_data) { + result.link = _openl; + } else if (_data->uploading()) { result.link = _cancell; } else if (dataLoaded()) { result.link = _openl; @@ -292,22 +350,23 @@ TextState ThemeDocument::textState(QPoint point, StateRequest request) const { float64 ThemeDocument::dataProgress() const { ensureDataMediaCreated(); - return _dataMedia->progress(); + return _data ? _dataMedia->progress() : 1.; } bool ThemeDocument::dataFinished() const { - return !_data->loading() - && (!_data->uploading() || _data->waitingForAlbum()); + return !_data + || (!_data->loading() + && (!_data->uploading() || _data->waitingForAlbum())); } bool ThemeDocument::dataLoaded() const { ensureDataMediaCreated(); - return _dataMedia->loaded(); + return !_data || _dataMedia->loaded(); } bool ThemeDocument::isReadyForOpen() const { ensureDataMediaCreated(); - return _dataMedia->loaded(); + return !_data || _dataMedia->loaded(); } QString ThemeDocument::additionalInfoString() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.h b/Telegram/SourceFiles/history/view/media/history_view_theme_document.h index 3eb9b62a1..a3b48cb6a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.h @@ -13,16 +13,18 @@ class Image; namespace Data { class DocumentMedia; +class WallPaper; } // namespace Data namespace HistoryView { class ThemeDocument final : public File { public: + ThemeDocument(not_null parent, DocumentData *document); ThemeDocument( not_null parent, - not_null document, - const QString &url = QString()); + DocumentData *document, + const std::optional ¶ms); ~ThemeDocument(); void draw( @@ -51,6 +53,9 @@ public: bool hasHeavyPart() const override; void unloadHeavyPart() override; + [[nodiscard]] static std::optional ParamsFromUrl( + const QString &url); + protected: float64 dataProgress() const override; bool dataFinished() const override; @@ -64,9 +69,10 @@ private: [[nodiscard]] bool checkGoodThumbnail() const; void validateThumbnail() const; void prepareThumbnailFrom(not_null image, int good) const; + void generateThumbnail() const; void ensureDataMediaCreated() const; - const not_null _data; + DocumentData *_data = nullptr; int _pixw = 1; int _pixh = 1; mutable QPixmap _thumbnail; diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 3ef407a2e..7af5ac3b5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -16,12 +16,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_media_common.h" +#include "history/view/media/history_view_theme_document.h" #include "ui/image/image.h" #include "ui/text/text_options.h" #include "ui/text/format_values.h" #include "ui/cached_round_corners.h" #include "layout/layout_selection.h" // FullSelection #include "data/data_session.h" +#include "data/data_wall_paper.h" #include "data/data_media_types.h" #include "data/data_web_page.h" #include "data/data_photo.h" @@ -707,6 +709,8 @@ ClickHandlerPtr WebPage::replaceAttachLink( } else { return _openl; } + } else if (ThemeDocument::ParamsFromUrl(_data->url).has_value()) { + return _openl; } return link; }