Show color / gradient wallpapers in WebPage previews.

This commit is contained in:
John Preston 2021-08-13 18:11:28 +03:00
parent 436d7b9d82
commit 1bc5277d51
6 changed files with 171 additions and 83 deletions

View file

@ -127,11 +127,12 @@ constexpr auto kVersion = 1;
if (!count || count > kMaxColors || view.size() != count * 7 - 1) { if (!count || count > kMaxColors || view.size() != count * 7 - 1) {
return {}; return {};
} }
const auto separator = QChar(count > 2 ? '~' : '-');
auto result = std::vector<QColor>(); auto result = std::vector<QColor>();
result.reserve(count); result.reserve(count);
for (auto i = 0; i != count; ++i) { 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 {}; return {};
} else if (const auto parsed = ColorFromString(view.mid(i * 7, 6))) { } else if (const auto parsed = ColorFromString(view.mid(i * 7, 6))) {
result.push_back(*parsed); result.push_back(*parsed);
@ -268,6 +269,9 @@ QString WallPaper::shareUrl(not_null<Main::Session*> session) const {
params.push_back("intensity=" + QString::number(_intensity)); params.push_back("intensity=" + QString::number(_intensity));
} }
} }
if (_rotation && backgroundColors().size() == 2) {
params.push_back("rotation=" + QString::number(_rotation));
}
auto mode = QStringList(); auto mode = QStringList();
if (_blurred) { if (_blurred) {
mode.push_back("blur"); mode.push_back("blur");
@ -355,6 +359,12 @@ WallPaper WallPaper::withUrlParams(
if (result._backgroundColors.empty()) { if (result._backgroundColors.empty()) {
result._backgroundColors = ColorsFromString(params.value("bg_color")); 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()) { if (const auto string = params.value("intensity"); !string.isEmpty()) {
auto ok = false; auto ok = false;
const auto intensity = string.toInt(&ok); const auto intensity = string.toInt(&ok);

View file

@ -58,9 +58,15 @@ void File::setLinks(
void File::refreshParentId(not_null<HistoryItem*> realParent) { void File::refreshParentId(not_null<HistoryItem*> realParent) {
const auto contextId = realParent->fullId(); const auto contextId = realParent->fullId();
_openl->setMessageId(contextId); if (_openl) {
_savel->setMessageId(contextId); _openl->setMessageId(contextId);
_cancell->setMessageId(contextId); }
if (_savel) {
_savel->setMessageId(contextId);
}
if (_cancell) {
_cancell->setMessageId(contextId);
}
} }
void File::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const { void File::setStatusSize(int newSize, int fullSize, int duration, qint64 realDuration) const {

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_wall_paper.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_media_grouped.h"
#include "history/view/media/history_view_photo.h" #include "history/view/media/history_view_photo.h"
@ -78,7 +79,7 @@ std::unique_ptr<Media> CreateAttach(
return std::make_unique<ThemeDocument>( return std::make_unique<ThemeDocument>(
parent, parent,
document, document,
webpageUrl); ThemeDocument::ParamsFromUrl(webpageUrl));
} }
return std::make_unique<Document>(parent, parent->data(), document); return std::make_unique<Document>(parent, parent->data(), document);
} else if (photo) { } else if (photo) {
@ -86,6 +87,8 @@ std::unique_ptr<Media> CreateAttach(
parent, parent,
parent->data(), parent->data(),
photo); photo);
} else if (const auto params = ThemeDocument::ParamsFromUrl(webpageUrl)) {
return std::make_unique<ThemeDocument>(parent, nullptr, params);
} }
return nullptr; return nullptr;
} }

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_wall_paper.h" #include "data/data_wall_paper.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
#include "core/local_url_handlers.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
@ -28,19 +29,48 @@ namespace HistoryView {
ThemeDocument::ThemeDocument( ThemeDocument::ThemeDocument(
not_null<Element*> parent, not_null<Element*> parent,
not_null<DocumentData*> document, DocumentData *document)
const QString &url) : ThemeDocument(parent, document, std::nullopt) {
}
ThemeDocument::ThemeDocument(
not_null<Element*> parent,
DocumentData *document,
const std::optional<Data::WallPaper> &params)
: File(parent, parent->data()) : File(parent, parent->data())
, _data(document) { , _data(document) {
Expects(_data->hasThumbnail() || _data->isTheme()); Expects(params.has_value() || _data->hasThumbnail() || _data->isTheme());
if (_data->isWallPaper()) { if (params) {
fillPatternFieldsFrom(url); _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()); private:
setDocumentLinks(_data, parent->data()); void onClickImpl() const override {
setStatusSize(Ui::FileStatusSizeReady, _data->size, -1, 0); }
};
// 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<EmptyFileClickHandler>(fullId),
nullptr,
nullptr);
}
} }
ThemeDocument::~ThemeDocument() { ThemeDocument::~ThemeDocument() {
@ -50,23 +80,27 @@ ThemeDocument::~ThemeDocument() {
} }
} }
void ThemeDocument::fillPatternFieldsFrom(const QString &url) { std::optional<Data::WallPaper> ThemeDocument::ParamsFromUrl(
const auto paramsPosition = url.indexOf('?'); const QString &url) {
const auto local = Core::TryConvertUrlToLocal(url);
const auto paramsPosition = local.indexOf('?');
if (paramsPosition < 0) { 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( const auto params = qthelp::url_parse_params(
paramsString, paramsString,
qthelp::UrlParamNameTransform::ToLower); qthelp::UrlParamNameTransform::ToLower);
const auto paper = Data::DefaultWallPaper().withUrlParams(params); auto paper = Data::DefaultWallPaper().withUrlParams(params);
_background = paper.backgroundColors(); return paper.backgroundColors().empty()
_patternOpacity = paper.patternOpacity(); ? std::nullopt
_gradientRotation = paper.gradientRotation(); : std::make_optional(std::move(paper));
} }
QSize ThemeDocument::countOptimalSize() { QSize ThemeDocument::countOptimalSize() {
if (_data->isTheme()) { if (!_data) {
return { st::maxWallPaperWidth, st::maxWallPaperHeight };
} else if (_data->isTheme()) {
return st::historyThemeSize; return st::historyThemeSize;
} }
const auto &location = _data->thumbnailLocation(); const auto &location = _data->thumbnailLocation();
@ -87,7 +121,11 @@ QSize ThemeDocument::countOptimalSize() {
} }
QSize ThemeDocument::countCurrentSize(int newWidth) { 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(); _pixw = st::historyThemeSize.width();
_pixh = st::historyThemeSize.height(); _pixh = st::historyThemeSize.height();
return st::historyThemeSize; return st::historyThemeSize;
@ -117,10 +155,12 @@ void ThemeDocument::draw(Painter &p, const QRect &r, TextSelection selection, cr
ensureDataMediaCreated(); ensureDataMediaCreated();
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data()); if (_data) {
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
}
auto selected = (selection == FullSelection); auto selected = (selection == FullSelection);
auto loaded = dataLoaded(); auto loaded = dataLoaded();
auto displayLoading = _data->displayLoading(); auto displayLoading = _data && _data->displayLoading();
auto paintx = 0, painty = 0, paintw = width(), painth = height(); 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); Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners);
} }
auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x(); if (_data) {
auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y(); auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x(); auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y();
auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y(); auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
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); auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
p.setFont(st::normalFont); 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.setPen(st::msgDateImgFg); p.setFont(st::normalFont);
p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x()); p.setPen(st::msgDateImgFg);
p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
if (radial || (!loaded && !_data->loading())) { if (radial || (!loaded && !_data->loading())) {
const auto radialOpacity = (radial && loaded && !_data->uploading()) const auto radialOpacity = (radial && loaded && !_data->uploading())
? _animation->radial.opacity() : ? _animation->radial.opacity() :
1.; 1.;
const auto innerSize = st::msgFileLayout.thumbSize; const auto innerSize = st::msgFileLayout.thumbSize;
QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize); QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
if (selected) { if (selected) {
p.setBrush(st::msgDateImgBgSelected); p.setBrush(st::msgDateImgBgSelected);
} else if (isThumbAnimation()) { } else if (isThumbAnimation()) {
auto over = _animation->a_thumbOver.value(1.); auto over = _animation->a_thumbOver.value(1.);
p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over)); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
} else { } else {
auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _openl); auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _openl);
p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg); p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
} }
p.setOpacity(radialOpacity * p.opacity()); p.setOpacity(radialOpacity * p.opacity());
{ {
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
p.drawEllipse(inner); p.drawEllipse(inner);
} }
p.setOpacity(radialOpacity); p.setOpacity(radialOpacity);
auto icon = ([radial, this, selected]() -> const style::icon* { auto icon = ([radial, this, selected]() -> const style::icon* {
if (radial || _data->loading()) { if (radial || _data->loading()) {
return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel); 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 { void ThemeDocument::ensureDataMediaCreated() const {
if (_dataMedia) { if (_dataMedia || !_data) {
return; return;
} }
_dataMedia = _data->createMediaView(); _dataMedia = _data->createMediaView();
@ -205,7 +246,7 @@ void ThemeDocument::ensureDataMediaCreated() const {
} }
bool ThemeDocument::checkGoodThumbnail() const { bool ThemeDocument::checkGoodThumbnail() const {
return !_data->hasThumbnail() || !_data->isPatternWallPaper(); return _data && (!_data->hasThumbnail() || !_data->isPatternWallPaper());
} }
void ThemeDocument::validateThumbnail() const { void ThemeDocument::validateThumbnail() const {
@ -222,6 +263,10 @@ void ThemeDocument::validateThumbnail() const {
if (_thumbnailGood >= 0) { if (_thumbnailGood >= 0) {
return; return;
} }
if (!_data) {
generateThumbnail();
return;
}
ensureDataMediaCreated(); ensureDataMediaCreated();
if (const auto normal = _dataMedia->thumbnail()) { if (const auto normal = _dataMedia->thumbnail()) {
prepareThumbnailFrom(normal, 0); 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( void ThemeDocument::prepareThumbnailFrom(
not_null<Image*> image, not_null<Image*> image,
int good) const { int good) const {
Expects(_data != nullptr);
Expects(_thumbnailGood <= good); Expects(_thumbnailGood <= good);
const auto isTheme = _data->isTheme(); 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(); auto paintx = 0, painty = 0, paintw = width(), painth = height();
if (QRect(paintx, painty, paintw, painth).contains(point)) { if (QRect(paintx, painty, paintw, painth).contains(point)) {
if (_data->uploading()) { if (!_data) {
result.link = _openl;
} else if (_data->uploading()) {
result.link = _cancell; result.link = _cancell;
} else if (dataLoaded()) { } else if (dataLoaded()) {
result.link = _openl; result.link = _openl;
@ -292,22 +350,23 @@ TextState ThemeDocument::textState(QPoint point, StateRequest request) const {
float64 ThemeDocument::dataProgress() const { float64 ThemeDocument::dataProgress() const {
ensureDataMediaCreated(); ensureDataMediaCreated();
return _dataMedia->progress(); return _data ? _dataMedia->progress() : 1.;
} }
bool ThemeDocument::dataFinished() const { bool ThemeDocument::dataFinished() const {
return !_data->loading() return !_data
&& (!_data->uploading() || _data->waitingForAlbum()); || (!_data->loading()
&& (!_data->uploading() || _data->waitingForAlbum()));
} }
bool ThemeDocument::dataLoaded() const { bool ThemeDocument::dataLoaded() const {
ensureDataMediaCreated(); ensureDataMediaCreated();
return _dataMedia->loaded(); return !_data || _dataMedia->loaded();
} }
bool ThemeDocument::isReadyForOpen() const { bool ThemeDocument::isReadyForOpen() const {
ensureDataMediaCreated(); ensureDataMediaCreated();
return _dataMedia->loaded(); return !_data || _dataMedia->loaded();
} }
QString ThemeDocument::additionalInfoString() const { QString ThemeDocument::additionalInfoString() const {

View file

@ -13,16 +13,18 @@ class Image;
namespace Data { namespace Data {
class DocumentMedia; class DocumentMedia;
class WallPaper;
} // namespace Data } // namespace Data
namespace HistoryView { namespace HistoryView {
class ThemeDocument final : public File { class ThemeDocument final : public File {
public: public:
ThemeDocument(not_null<Element*> parent, DocumentData *document);
ThemeDocument( ThemeDocument(
not_null<Element*> parent, not_null<Element*> parent,
not_null<DocumentData*> document, DocumentData *document,
const QString &url = QString()); const std::optional<Data::WallPaper> &params);
~ThemeDocument(); ~ThemeDocument();
void draw( void draw(
@ -51,6 +53,9 @@ public:
bool hasHeavyPart() const override; bool hasHeavyPart() const override;
void unloadHeavyPart() override; void unloadHeavyPart() override;
[[nodiscard]] static std::optional<Data::WallPaper> ParamsFromUrl(
const QString &url);
protected: protected:
float64 dataProgress() const override; float64 dataProgress() const override;
bool dataFinished() const override; bool dataFinished() const override;
@ -64,9 +69,10 @@ private:
[[nodiscard]] bool checkGoodThumbnail() const; [[nodiscard]] bool checkGoodThumbnail() const;
void validateThumbnail() const; void validateThumbnail() const;
void prepareThumbnailFrom(not_null<Image*> image, int good) const; void prepareThumbnailFrom(not_null<Image*> image, int good) const;
void generateThumbnail() const;
void ensureDataMediaCreated() const; void ensureDataMediaCreated() const;
const not_null<DocumentData*> _data; DocumentData *_data = nullptr;
int _pixw = 1; int _pixw = 1;
int _pixh = 1; int _pixh = 1;
mutable QPixmap _thumbnail; mutable QPixmap _thumbnail;

View file

@ -16,12 +16,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_media_common.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/image/image.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
#include "layout/layout_selection.h" // FullSelection #include "layout/layout_selection.h" // FullSelection
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_wall_paper.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "data/data_photo.h" #include "data/data_photo.h"
@ -707,6 +709,8 @@ ClickHandlerPtr WebPage::replaceAttachLink(
} else { } else {
return _openl; return _openl;
} }
} else if (ThemeDocument::ParamsFromUrl(_data->url).has_value()) {
return _openl;
} }
return link; return link;
} }