Use new HistoryWallPaper media type for wallpaper.

This commit is contained in:
John Preston 2019-01-29 19:26:19 +03:00
parent fd8e9dad92
commit 5ca12a73c3
24 changed files with 683 additions and 192 deletions

View file

@ -92,7 +92,7 @@ msgServiceFont: semiboldFont;
msgServiceNameFont: semiboldFont;
msgServicePhotoWidth: 100px;
msgDateFont: font(13px);
msgMinWidth: 190px;
msgMinWidth: 160px;
msgPhotoSize: 33px;
msgPhotoSkip: 40px;
msgPadding: margins(13px, 7px, 13px, 8px);

View file

@ -1037,6 +1037,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_animation_title" = "Animated GIFs";
"lng_media_size_limit" = "Limit by size";
"lng_media_size_up_to" = "up to {size}";
"lng_media_chat_background" = "Chat background";
"lng_emoji_category1" = "People";
"lng_emoji_category2" = "Nature";

View file

@ -152,7 +152,7 @@ QImage PrepareScaledFromFull(
if (patternBackground) {
result = ColorizePattern(
std::move(result),
Window::Theme::PatternColor(*patternBackground));
Data::PatternColor(*patternBackground));
}
return result;
}

View file

@ -593,6 +593,7 @@ bool DocumentData::checkWallPaperProperties() {
return false;
}
type = WallPaperDocument;
validateGoodThumbnail();
return true;
}
@ -615,6 +616,10 @@ bool DocumentData::isWallPaper() const {
return (type == WallPaperDocument);
}
bool DocumentData::isPatternWallPaper() const {
return isWallPaper() && hasMimeType(qstr("image/png"));
}
bool DocumentData::hasThumbnail() const {
return !_thumbnail->isNull();
}
@ -642,7 +647,7 @@ Image *DocumentData::goodThumbnail() const {
}
void DocumentData::validateGoodThumbnail() {
if (!isVideoFile() && !isAnimation()) {
if (!isVideoFile() && !isAnimation() && !isWallPaper()) {
_goodThumbnail = nullptr;
} else if (!_goodThumbnail && hasRemoteLocation()) {
_goodThumbnail = std::make_unique<Image>(

View file

@ -166,6 +166,7 @@ public:
}
bool checkWallPaperProperties();
[[nodiscard]] bool isWallPaper() const;
[[nodiscard]] bool isPatternWallPaper() const;
[[nodiscard]] bool hasThumbnail() const;
void loadThumbnail(Data::FileOrigin origin);
@ -342,6 +343,26 @@ protected:
};
class DocumentWrappedClickHandler : public DocumentClickHandler {
public:
DocumentWrappedClickHandler(
ClickHandlerPtr wrapped,
not_null<DocumentData*> document,
FullMsgId context = FullMsgId())
: DocumentClickHandler(document, context)
, _wrapped(wrapped) {
}
protected:
void onClickImpl() const override {
_wrapped->onClick({ Qt::LeftButton });
}
private:
ClickHandlerPtr _wrapped;
};
QString FileNameForSave(
const QString &title,
const QString &filter,

View file

@ -17,6 +17,41 @@ namespace Data {
namespace {
constexpr auto kGoodThumbQuality = 87;
constexpr auto kWallPaperSize = 960;
QImage Prepare(
const QString &path,
QByteArray data,
bool isWallPaper) {
if (!isWallPaper) {
return Media::Clip::PrepareForSending(path, data).thumbnail;
}
const auto validateSize = [](QSize size) {
return (size.width() + size.height()) < 10'000;
};
auto buffer = QBuffer(&data);
auto file = QFile(path);
auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
auto reader = QImageReader(device);
#ifndef OS_MAC_OLD
reader.setAutoTransform(true);
#endif // OS_MAC_OLD
if (!reader.canRead() || !validateSize(reader.size())) {
return QImage();
}
auto result = reader.read();
if (!result.width() || !result.height()) {
return QImage();
}
return (result.width() > kWallPaperSize
|| result.height() > kWallPaperSize)
? result.scaled(
kWallPaperSize,
kWallPaperSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation)
: result;
}
} // namespace
@ -29,6 +64,7 @@ void GoodThumbSource::generate(base::binary_guard &&guard) {
return;
}
const auto data = _document->data();
const auto isWallPaper = _document->isWallPaper();
auto location = _document->location().isEmpty()
? nullptr
: std::make_unique<FileLocation>(_document->location());
@ -44,11 +80,14 @@ void GoodThumbSource::generate(base::binary_guard &&guard) {
const auto filepath = (location && location->accessEnable())
? location->name()
: QString();
auto result = Media::Clip::PrepareForSending(filepath, data);
auto result = Prepare(filepath, data, isWallPaper);
auto bytes = QByteArray();
if (!result.thumbnail.isNull()) {
QBuffer buffer(&bytes);
result.thumbnail.save(&buffer, "JPG", kGoodThumbQuality);
if (!result.isNull()) {
auto buffer = QBuffer(&bytes);
const auto format = (isWallPaper && result.hasAlphaChannel())
? "PNG"
: "JPG";
result.save(&buffer, format, kGoodThumbQuality);
}
if (!filepath.isEmpty()) {
location->accessDisable();
@ -56,7 +95,7 @@ void GoodThumbSource::generate(base::binary_guard &&guard) {
const auto bytesSize = bytes.size();
ready(
std::move(guard),
std::move(result.thumbnail),
std::move(result),
bytesSize,
std::move(bytes));
});
@ -119,7 +158,10 @@ void GoodThumbSource::load(
guard = std::move(guard),
value = std::move(value)
]() mutable {
ready(std::move(guard), App::readImage(value), value.size());
ready(
std::move(guard),
App::readImage(value, nullptr, false),
value.size());
});
};

View file

@ -1017,7 +1017,7 @@ WebPageData *MediaWebPage::webpage() const {
bool MediaWebPage::hasReplyPreview() const {
if (const auto document = MediaWebPage::document()) {
return document->hasThumbnail();
return document->hasThumbnail() && !document->isPatternWallPaper();
} else if (const auto photo = MediaWebPage::photo()) {
return !photo->isNull();
}

View file

@ -294,6 +294,7 @@ class DocumentClickHandler;
class DocumentSaveClickHandler;
class DocumentOpenClickHandler;
class DocumentCancelClickHandler;
class DocumentWrappedClickHandler;
class GifOpenClickHandler;
class VoiceSeekClickHandler;

View file

@ -15,6 +15,8 @@ maxStickerSize: 256px;
maxGifSize: 320px;
maxVideoMessageSize: 240px;
maxSignatureSize: 144px;
maxWallPaperWidth: 160px;
maxWallPaperHeight: 240px;
historyMinimalWidth: 380px;

View file

@ -1503,39 +1503,26 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
});
}
};
const auto link = ClickHandler::getActive();
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get());
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
if (lnkPhoto || lnkDocument) {
const auto item = _dragStateItem;
const auto itemId = item ? item->fullId() : FullMsgId();
if (isUponSelected > 0) {
_menu->addAction(
lang((isUponSelected > 1)
? lng_context_copy_selected_items
: lng_context_copy_selected),
[=] { copySelectedText(); });
}
addItemActions(item);
if (lnkPhoto) {
const auto photo = lnkPhoto->photo();
const auto addPhotoActions = [&](not_null<PhotoData*> photo) {
_menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] {
savePhotoToFile(photo);
}));
_menu->addAction(lang(lng_context_copy_image), [=] {
copyContextImage(photo);
});
} else {
auto document = lnkDocument->document();
};
const auto addDocumentActions = [&](not_null<DocumentData*> document) {
if (document->loading()) {
_menu->addAction(lang(lng_context_cancel_download), [=] {
cancelContextDownload(document);
});
} else {
return;
}
const auto item = _dragStateItem;
const auto itemId = item ? item->fullId() : FullMsgId();
const auto lnkIsVideo = document->isVideoFile();
const auto lnkIsVoice = document->isVoiceMessage();
const auto lnkIsAudio = document->isAudioFile();
if (document->loaded() && document->isGifv()) {
if (!cAutoPlayGif()) {
_menu->addAction(lang(lng_context_open_gif), [=] {
@ -1554,7 +1541,25 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsVoice ? lng_context_save_audio : (lnkIsAudio ? lng_context_save_audio_file : lng_context_save_file))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] {
saveDocumentToFile(itemId, document);
}));
};
const auto link = ClickHandler::getActive();
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(link.get());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(link.get());
if (lnkPhoto || lnkDocument) {
const auto item = _dragStateItem;
const auto itemId = item ? item->fullId() : FullMsgId();
if (isUponSelected > 0) {
_menu->addAction(
lang((isUponSelected > 1)
? lng_context_copy_selected_items
: lng_context_copy_selected),
[=] { copySelectedText(); });
}
addItemActions(item);
if (lnkPhoto) {
addPhotoActions(lnkPhoto->photo());
} else {
addDocumentActions(lnkDocument->document());
}
if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) {
_menu->addAction(lang(item->history()->peer->isMegagroup() ? lng_context_copy_link : lng_context_copy_post_link), [=] {

View file

@ -47,37 +47,44 @@ public:
HistoryMedia(not_null<Element*> parent) : _parent(parent) {
}
not_null<History*> history() const;
[[nodiscard]] not_null<History*> history() const;
virtual TextWithEntities selectedText(TextSelection selection) const {
[[nodiscard]] virtual TextWithEntities selectedText(
TextSelection selection) const {
return TextWithEntities();
}
virtual bool isDisplayed() const;
[[nodiscard]] virtual bool isDisplayed() const;
virtual void updateNeedBubbleState() {
}
virtual bool hasTextForCopy() const {
[[nodiscard]] virtual bool hasTextForCopy() const {
return false;
}
virtual bool hideMessageText() const {
[[nodiscard]] virtual bool hideMessageText() const {
return true;
}
virtual bool allowsFastShare() const {
[[nodiscard]] virtual bool allowsFastShare() const {
return false;
}
virtual void refreshParentId(not_null<HistoryItem*> realParent) {
}
virtual void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const = 0;
virtual PointState pointState(QPoint point) const;
virtual TextState textState(QPoint point, StateRequest request) const = 0;
virtual void draw(
Painter &p,
const QRect &r,
TextSelection selection,
TimeMs ms) const = 0;
[[nodiscard]] virtual PointState pointState(QPoint point) const;
[[nodiscard]] virtual TextState textState(
QPoint point,
StateRequest request) const = 0;
virtual void updatePressed(QPoint point) {
}
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
[[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
// if we are in selecting items mode perhaps we want to
// toggle selection instead of activating the pressed link
virtual bool toggleSelectionByHandlerClick(
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const = 0;
// if we press and drag on this media should we drag the item
@ -99,21 +106,22 @@ public:
TextSelection selection) const;
// if we press and drag this link should we drag the item
virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0;
[[nodiscard]] virtual bool dragItemByHandler(
const ClickHandlerPtr &p) const = 0;
virtual void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
}
virtual void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
}
virtual bool uploading() const {
[[nodiscard]] virtual bool uploading() const {
return false;
}
virtual PhotoData *getPhoto() const {
[[nodiscard]] virtual PhotoData *getPhoto() const {
return nullptr;
}
virtual DocumentData *getDocument() const {
[[nodiscard]] virtual DocumentData *getDocument() const {
return nullptr;
}
@ -126,7 +134,7 @@ public:
virtual void stopAnimation() {
}
virtual QSize sizeForGrouping() const {
[[nodiscard]] virtual QSize sizeForGrouping() const {
Unexpected("Grouping method call.");
}
virtual void drawGrouped(
@ -140,54 +148,62 @@ public:
not_null<QPixmap*> cache) const {
Unexpected("Grouping method call.");
}
virtual TextState getStateGrouped(
[[nodiscard]] virtual TextState getStateGrouped(
const QRect &geometry,
QPoint point,
StateRequest request) const;
virtual bool animating() const {
[[nodiscard]] virtual bool animating() const {
return false;
}
virtual TextWithEntities getCaption() const {
[[nodiscard]] virtual TextWithEntities getCaption() const {
return TextWithEntities();
}
virtual bool needsBubble() const = 0;
virtual bool customInfoLayout() const = 0;
virtual QMargins bubbleMargins() const {
[[nodiscard]] virtual bool needsBubble() const = 0;
[[nodiscard]] virtual bool customInfoLayout() const = 0;
[[nodiscard]] virtual QMargins bubbleMargins() const {
return QMargins();
}
virtual bool hideForwardedFrom() const {
[[nodiscard]] virtual bool hideForwardedFrom() const {
return false;
}
virtual bool overrideEditedDate() const {
[[nodiscard]] virtual bool overrideEditedDate() const {
return false;
}
virtual HistoryMessageEdited *displayedEditBadge() const {
[[nodiscard]] virtual HistoryMessageEdited *displayedEditBadge() const {
Unexpected("displayedEditBadge() on non-grouped media.");
}
// An attach media in a web page can provide an
// additional text to be displayed below the attach.
// For example duration / progress for video messages.
virtual QString additionalInfoString() const {
[[nodiscard]] virtual QString additionalInfoString() const {
return QString();
}
void setInBubbleState(MediaInBubbleState state) {
_inBubbleState = state;
}
MediaInBubbleState inBubbleState() const {
[[nodiscard]] MediaInBubbleState inBubbleState() const {
return _inBubbleState;
}
bool isBubbleTop() const {
return (_inBubbleState == MediaInBubbleState::Top) || (_inBubbleState == MediaInBubbleState::None);
[[nodiscard]] bool isBubbleTop() const {
return (_inBubbleState == MediaInBubbleState::Top)
|| (_inBubbleState == MediaInBubbleState::None);
}
bool isBubbleBottom() const {
return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None);
[[nodiscard]] bool isBubbleBottom() const {
return (_inBubbleState == MediaInBubbleState::Bottom)
|| (_inBubbleState == MediaInBubbleState::None);
}
virtual bool skipBubbleTail() const {
[[nodiscard]] virtual bool skipBubbleTail() const {
return false;
}
// Sometimes webpages can force the bubble to fit their size instead of
// allowing message text to be as wide as possible (like wallpapers).
[[nodiscard]] virtual bool enforceBubbleWidth() const {
return false;
}
@ -196,7 +212,7 @@ public:
// But the overloading click handler should be used only when media
// is already loaded (not a photo or GIF waiting for load with auto
// load being disabled - in such case media should handle the click).
virtual bool isReadyForOpen() const {
[[nodiscard]] virtual bool isReadyForOpen() const {
return true;
}

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/media/history_media_document.h"
#include "history/media/history_media_sticker.h"
#include "history/media/history_media_video.h"
#include "history/media/history_media_wall_paper.h"
#include "styles/style_history.h"
int documentMaxStatusWidth(DocumentData *document) {
@ -60,25 +61,27 @@ std::unique_ptr<HistoryMedia> CreateAttach(
not_null<HistoryView::Element*> parent,
DocumentData *document,
PhotoData *photo,
const std::vector<std::unique_ptr<Data::Media>> &collage) {
const std::vector<std::unique_ptr<Data::Media>> &collage,
const QString &webpageUrl) {
if (!collage.empty()) {
return std::make_unique<HistoryGroupedMedia>(parent, collage);
} else if (document) {
if (document->sticker()) {
return std::make_unique<HistorySticker>(parent, document);
} else if (document->isAnimation()) {
return std::make_unique<HistoryGif>(
parent,
document);
return std::make_unique<HistoryGif>(parent, document);
} else if (document->isVideoFile()) {
return std::make_unique<HistoryVideo>(
parent,
parent->data(),
document);
}
return std::make_unique<HistoryDocument>(
} else if (document->isWallPaper()) {
return std::make_unique<HistoryWallPaper>(
parent,
document);
document,
webpageUrl);
}
return std::make_unique<HistoryDocument>(parent, document);
} else if (photo) {
return std::make_unique<HistoryPhoto>(
parent,

View file

@ -32,5 +32,6 @@ std::unique_ptr<HistoryMedia> CreateAttach(
not_null<HistoryView::Element*> parent,
DocumentData *document,
PhotoData *photo,
const std::vector<std::unique_ptr<Data::Media>> &collage = {});
const std::vector<std::unique_ptr<Data::Media>> &collage = {},
const QString &webpageUrl = QString());
int unitedLineHeight();

View file

@ -34,11 +34,6 @@ HistoryPhoto::HistoryPhoto(
: HistoryFileMedia(parent, realParent)
, _data(photo)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto fullId = realParent->fullId();
setLinks(
std::make_shared<PhotoOpenClickHandler>(_data, fullId),
std::make_shared<PhotoSaveClickHandler>(_data, fullId),
std::make_shared<PhotoCancelClickHandler>(_data, fullId));
_caption = createCaption(realParent);
create(realParent->fullId());
}

View file

@ -0,0 +1,277 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/media/history_media_wall_paper.h"
#include "layout.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "base/qthelp_url.h"
#include "window/themes/window_theme.h"
#include "styles/style_history.h"
namespace {
using TextState = HistoryView::TextState;
} // namespace
HistoryWallPaper::HistoryWallPaper(
not_null<Element*> parent,
not_null<DocumentData*> document,
const QString &url)
: HistoryFileMedia(parent, parent->data())
, _data(document) {
Expects(_data->hasThumbnail());
fillPatternFieldsFrom(url);
_data->thumbnail()->load(parent->data()->fullId());
setDocumentLinks(_data, parent->data());
setStatusSize(FileStatusSizeReady, _data->size, -1, 0);
}
void HistoryWallPaper::fillPatternFieldsFrom(const QString &url) {
const auto paramsPosition = url.indexOf('?');
if (paramsPosition < 0) {
return;
}
const auto paramsString = url.mid(paramsPosition + 1);
const auto params = qthelp::url_parse_params(
paramsString,
qthelp::UrlParamNameTransform::ToLower);
const auto kDefaultBackground = QColor(213, 223, 233);
const auto paper = Data::DefaultWallPaper().withUrlParams(params);
_intensity = paper.patternIntensity();
_background = paper.backgroundColor().value_or(kDefaultBackground);
}
QSize HistoryWallPaper::countOptimalSize() {
auto tw = ConvertScale(_data->thumbnail()->width());
auto th = ConvertScale(_data->thumbnail()->height());
if (!tw || !th) {
tw = th = 1;
}
th = (st::maxWallPaperWidth * th) / tw;
tw = st::maxWallPaperWidth;
const auto maxWidth = tw;
const auto minHeight = std::clamp(
th,
st::minPhotoSize,
st::maxWallPaperHeight);
return { maxWidth, minHeight };
}
QSize HistoryWallPaper::countCurrentSize(int newWidth) {
auto tw = ConvertScale(_data->thumbnail()->width());
auto th = ConvertScale(_data->thumbnail()->height());
if (!tw || !th) {
tw = th = 1;
}
// We use pix() for image copies, because we rely that backgrounds
// are always displayed with the same dimensions (not pixSingle()).
_pixw = maxWidth();// std::min(newWidth, maxWidth());
_pixh = minHeight();// (_pixw * th / tw);
newWidth = _pixw;
const auto newHeight = _pixh; /*std::clamp(
_pixh,
st::minPhotoSize,
st::maxWallPaperHeight);*/
return { newWidth, newHeight };
}
void HistoryWallPaper::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
_data->automaticLoad(_realParent->fullId(), _parent->data());
auto selected = (selection == FullSelection);
auto loaded = _data->loaded();
auto displayLoading = _data->displayLoading();
auto inWebPage = (_parent->media() != this);
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
if (displayLoading) {
ensureAnimation();
if (!_animation->radial.animating()) {
_animation->radial.start(_data->progress());
}
}
bool radial = isRadialAnimation(ms);
auto rthumb = rtlrect(paintx, painty, paintw, painth, width());
auto roundRadius = ImageRoundRadius::Small;
auto roundCorners = RectPart::AllCorners;
validateThumbnail();
p.drawPixmap(rthumb.topLeft(), _thumbnail);
if (selected) {
App::complexOverlayRect(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();
App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : 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.;
QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(st::msgDateImgBgSelected);
} else if (isThumbAnimation(ms)) {
auto over = _animation->a_thumbOver.current();
p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
} else {
auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
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);
}
}
}
void HistoryWallPaper::validateThumbnail() const {
if (_thumbnailGood > 0) {
return;
}
const auto good = _data->goodThumbnail();
if (good) {
if (good->loaded()) {
prepareThumbnailFrom(good, 1);
return;
} else {
good->load({});
}
}
if (_thumbnailGood >= 0) {
return;
}
if (_data->thumbnail()->loaded()) {
prepareThumbnailFrom(_data->thumbnail(), 0);
} else if (const auto blurred = _data->thumbnailInline()) {
if (_thumbnail.isNull()) {
prepareThumbnailFrom(blurred, -1);
}
}
}
void HistoryWallPaper::prepareThumbnailFrom(
not_null<Image*> image,
int good) const {
Expects(_thumbnailGood <= good);
const auto isPattern = _data->isPatternWallPaper();
auto options = Images::Option::Smooth
| (good >= 0 ? Images::Option(0) : Images::Option::Blurred)
| (isPattern
? Images::Option::TransparentBackground
: Images::Option(0));
auto original = image->original();
auto tw = ConvertScale(_data->thumbnail()->width());
auto th = ConvertScale(_data->thumbnail()->height());
if (!tw || !th) {
tw = th = 1;
}
original = Images::prepare(
std::move(original),
_pixw,
(_pixw * th) / tw,
options,
_pixw,
_pixh);
if (isPattern) {
original = Data::PreparePatternImage(
std::move(original),
_background,
Data::PatternColor(_background),
_intensity);
}
_thumbnail = App::pixmapFromImageInPlace(std::move(original));
_thumbnailGood = good;
}
TextState HistoryWallPaper::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto bubble = _parent->hasBubble();
if (QRect(paintx, painty, paintw, painth).contains(point)) {
if (_data->uploading()) {
result.link = _cancell;
} else if (_data->loaded()) {
result.link = _openl;
} else if (_data->loading()) {
result.link = _cancell;
} else {
result.link = _savel;
}
}
return result;
}
float64 HistoryWallPaper::dataProgress() const {
return _data->progress();
}
bool HistoryWallPaper::dataFinished() const {
return !_data->loading()
&& (!_data->uploading() || _data->waitingForAlbum());
}
bool HistoryWallPaper::dataLoaded() const {
return _data->loaded();
}
bool HistoryWallPaper::isReadyForOpen() const {
return _data->loaded();
}
QString HistoryWallPaper::additionalInfoString() const {
// This will force message info (time) to be displayed below
// this attachment in HistoryWebPage media.
static auto result = QString(" ");
return result;
}

View file

@ -0,0 +1,63 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/media/history_media_file.h"
class HistoryWallPaper : public HistoryFileMedia {
public:
HistoryWallPaper(
not_null<Element*> parent,
not_null<DocumentData*> document,
const QString &url = QString());
void draw(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms) const override;
TextState textState(QPoint point, StateRequest request) const override;
DocumentData *getDocument() const override {
return _data;
}
bool needsBubble() const override {
return false;
}
bool customInfoLayout() const override {
return false;
}
bool skipBubbleTail() const override {
return true;
}
bool isReadyForOpen() const override;
QString additionalInfoString() const override;
protected:
float64 dataProgress() const override;
bool dataFinished() const override;
bool dataLoaded() const override;
private:
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
void fillPatternFieldsFrom(const QString &url);
void validateThumbnail() const;
void prepareThumbnailFrom(not_null<Image*> image, int good) const;
not_null<DocumentData*> _data;
int _pixw = 1;
int _pixh = 1;
mutable QPixmap _thumbnail;
mutable int _thumbnailGood = -1; // -1 inline, 0 thumbnail, 1 good
QColor _background;
int _intensity = 0;
};

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "layout.h"
#include "core/click_handler_types.h"
#include "lang/lang_keys.h"
#include "history/history_item_components.h"
#include "history/history_item.h"
#include "history/history.h"
@ -132,6 +133,12 @@ QSize HistoryWebPage::countOptimalSize() {
_openl = previewOfHiddenUrl
? std::make_shared<HiddenUrlClickHandler>(_data->url)
: std::make_shared<UrlClickHandler>(_data->url, true);
if (_data->document && _data->document->isWallPaper()) {
_openl = std::make_shared<DocumentWrappedClickHandler>(
std::move(_openl),
_data->document,
_parent->data()->fullId());
}
}
// init layout
@ -169,7 +176,8 @@ QSize HistoryWebPage::countOptimalSize() {
_parent,
_data->document,
_data->photo,
_collage);
_collage,
_data->url);
}
auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom();
@ -202,8 +210,8 @@ QSize HistoryWebPage::countOptimalSize() {
title,
Ui::WebpageTextTitleOptions());
}
if (!_siteNameWidth && !_data->siteName.isEmpty()) {
_siteNameWidth = st::webPageTitleFont->width(_data->siteName);
if (!_siteNameWidth && !displayedSiteName().isEmpty()) {
_siteNameWidth = st::webPageTitleFont->width(displayedSiteName());
}
// init dimensions
@ -212,7 +220,7 @@ QSize HistoryWebPage::countOptimalSize() {
auto maxWidth = skipBlockWidth;
auto minHeight = 0;
auto siteNameHeight = _data->siteName.isEmpty() ? 0 : lineHeight;
auto siteNameHeight = _siteNameWidth ? lineHeight : 0;
auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight);
@ -223,7 +231,7 @@ QSize HistoryWebPage::countOptimalSize() {
}
if (_siteNameWidth) {
if (_title.isEmpty() && _description.isEmpty()) {
if (_title.isEmpty() && _description.isEmpty() && textFloatsAroundInfo) {
accumulate_max(maxWidth, _siteNameWidth + _parent->skipBlockWidth());
} else {
accumulate_max(maxWidth, _siteNameWidth + articlePhotoMaxWidth);
@ -441,7 +449,7 @@ void HistoryWebPage::draw(Painter &p, const QRect &r, TextSelection selection, T
if (_siteNameWidth) {
p.setFont(st::webPageTitleFont);
p.setPen(semibold);
p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? _data->siteName : st::webPageTitleFont->elided(_data->siteName, paintw));
p.drawTextLeft(padding.left(), tshift, width(), (paintw >= _siteNameWidth) ? displayedSiteName() : st::webPageTitleFont->elided(displayedSiteName(), paintw));
tshift += lineHeight;
}
if (_titleLines) {
@ -595,19 +603,7 @@ TextState HistoryWebPage::textState(QPoint point, StateRequest request) const {
auto attachTop = tshift - bubble.top();
if (rtl()) attachLeft = width() - attachLeft - _attach->width();
result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
if (result.link && !_data->document && _data->photo && _collage.empty() && _attach->isReadyForOpen()) {
if (_data->type == WebPageType::Profile
|| _data->type == WebPageType::Video) {
result.link = _openl;
} else if (_data->type == WebPageType::Photo
|| _data->siteName == qstr("Twitter")
|| _data->siteName == qstr("Facebook")) {
// leave photo link
} else {
result.link = _openl;
}
}
result.link = replaceAttachLink(result.link);
}
}
@ -615,6 +611,30 @@ TextState HistoryWebPage::textState(QPoint point, StateRequest request) const {
return result;
}
ClickHandlerPtr HistoryWebPage::replaceAttachLink(
const ClickHandlerPtr &link) const {
if (!link || !_attach->isReadyForOpen() || !_collage.empty()) {
return link;
}
if (_data->document) {
if (_data->document->isWallPaper()) {
return _openl;
}
} else if (_data->photo) {
if (_data->type == WebPageType::Profile
|| _data->type == WebPageType::Video) {
return _openl;
} else if (_data->type == WebPageType::Photo
|| _data->siteName == qstr("Twitter")
|| _data->siteName == qstr("Facebook")) {
// leave photo link
} else {
return _openl;
}
}
return link;
}
TextSelection HistoryWebPage::adjustSelection(TextSelection selection, TextSelectType type) const {
if (!_descriptionLines || selection.to <= _title.length()) {
return _title.adjustSelection(selection, type);
@ -639,6 +659,12 @@ void HistoryWebPage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool p
}
}
bool HistoryWebPage::enforceBubbleWidth() const {
return (_attach != nullptr)
&& (_data->document != nullptr)
&& _data->document->isWallPaper();
}
void HistoryWebPage::playAnimation(bool autoplay) {
if (_attach) {
if (autoplay) {
@ -699,6 +725,12 @@ int HistoryWebPage::bottomInfoPadding() const {
return result;
}
QString HistoryWebPage::displayedSiteName() const {
return (_data->document && _data->document->isWallPaper())
? lang(lng_media_chat_background)
: _data->siteName;
}
HistoryWebPage::~HistoryWebPage() {
history()->owner().unregisterWebPageView(_data, _parent);
}

View file

@ -74,6 +74,7 @@ public:
bool allowsFastShare() const override {
return true;
}
bool enforceBubbleWidth() const override;
HistoryMedia *attach() const {
return _attach.get();
@ -92,6 +93,9 @@ private:
int bottomInfoPadding() const;
bool isLogEntryOriginal() const;
QString displayedSiteName() const;
ClickHandlerPtr replaceAttachLink(const ClickHandlerPtr &link) const;
not_null<WebPageData*> _data;
std::vector<std::unique_ptr<Data::Media>> _collage;
ClickHandlerPtr _openl;

View file

@ -272,7 +272,15 @@ QSize Message::performCountOptimalSize() {
}
if (mediaDisplayed) {
// Parts don't participate in maxWidth() in case of media message.
if (media->enforceBubbleWidth()) {
maxWidth = media->maxWidth();
if (hasVisibleText() && maxWidth < plainMaxWidth()) {
minHeight -= item->_text.minHeight();
minHeight += item->_text.countHeight(maxWidth - st::msgPadding.left() - st::msgPadding.right());
}
} else {
accumulate_max(maxWidth, media->maxWidth());
}
minHeight += media->minHeight();
} else {
// Count parts in maxWidth(), don't count them in minHeight().
@ -1558,7 +1566,8 @@ QRect Message::countGeometry() const {
accumulate_min(contentWidth, st::msgMaxWidth);
if (mediaWidth < contentWidth) {
const auto textualWidth = plainMaxWidth();
if (mediaWidth < textualWidth) {
if (mediaWidth < textualWidth
&& (!media || !media->enforceBubbleWidth())) {
accumulate_min(contentWidth, textualWidth);
} else {
contentWidth = mediaWidth;
@ -1601,7 +1610,8 @@ int Message::resizeContentGetHeight(int newWidth) {
media->resizeGetHeight(contentWidth);
if (media->width() < contentWidth) {
const auto textualWidth = plainMaxWidth();
if (media->width() < textualWidth) {
if (media->width() < textualWidth
&& !media->enforceBubbleWidth()) {
accumulate_min(contentWidth, textualWidth);
} else {
contentWidth = media->width();

View file

@ -52,7 +52,7 @@ constexpr auto kSavedBackgroundFormat = QImage::Format_ARGB32_Premultiplied;
constexpr auto kWallPaperLegacySerializeTagId = int32(-111);
constexpr auto kWallPaperSerializeTagId = int32(-112);
constexpr auto kWallPaperSidesLimit = 10000;
constexpr auto kWallPaperSidesLimit = 10'000;
constexpr auto kSinglePeerTypeUser = qint32(1);
constexpr auto kSinglePeerTypeChat = qint32(2);

View file

@ -325,9 +325,11 @@ QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, in
}
{
QPainter p(&result);
if (!(options & Images::Option::TransparentBackground)) {
if (w < outerw || h < outerh) {
p.fillRect(0, 0, result.width(), result.height(), st::imageBg);
}
}
p.drawImage((result.width() - img.width()) / (2 * cIntRetinaFactor()), (result.height() - img.height()) / (2 * cIntRetinaFactor()), img);
}
img = result;

View file

@ -104,63 +104,6 @@ std::optional<QColor> ColorFromString(const QString &string) {
255);
}
QImage PreparePatternImage(QImage image, QColor bg, QColor fg, int intensity) {
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
// Similar to ColorizePattern.
// But here we set bg to all 'alpha=0' pixels and fg to opaque ones.
const auto width = image.width();
const auto height = image.height();
const auto alpha = anim::interpolate(
0,
255,
fg.alphaF() * std::clamp(intensity / 100., 0., 1.));
if (!alpha) {
image.fill(bg);
return image;
}
fg.setAlpha(255);
const auto patternBg = anim::shifted(bg);
const auto patternFg = anim::shifted(fg);
const auto resultBytesPerPixel = (image.depth() >> 3);
constexpr auto resultIntsPerPixel = 1;
const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(image.bits());
Assert(resultIntsAdded >= 0);
Assert(image.depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
Assert(image.bytesPerLine() == (resultIntsPerLine << 2));
const auto maskBytesPerPixel = (image.depth() >> 3);
const auto maskBytesPerLine = image.bytesPerLine();
const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
// We want to read the last byte of four available.
// This is the difference with style::colorizeImage.
auto maskBytes = image.constBits() + (maskBytesPerPixel - 1);
Assert(maskBytesAdded >= 0);
Assert(image.depth() == (maskBytesPerPixel << 3));
for (auto y = 0; y != height; ++y) {
for (auto x = 0; x != width; ++x) {
const auto maskOpacity = static_cast<anim::ShiftedMultiplier>(
*maskBytes) + 1;
const auto fgOpacity = (maskOpacity * alpha) >> 8;
const auto bgOpacity = 256 - fgOpacity;
*resultInts = anim::unshifted(
patternBg * bgOpacity + patternFg * fgOpacity);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
}
maskBytes += maskBytesAdded;
resultInts += resultIntsAdded;
}
return image;
}
} // namespace
WallPaper::WallPaper(WallPaperId id) : _id(id) {
@ -258,7 +201,7 @@ WallPaper WallPaper::withUrlParams(
if (const auto string = params.value("intensity"); !string.isEmpty()) {
auto ok = false;
const auto intensity = string.toInt(&ok);
if (ok && base::in_range(intensity, 0, 100)) {
if (ok && base::in_range(intensity, 0, 101)) {
result._intensity = intensity;
}
}
@ -442,6 +385,81 @@ bool IsDefaultWallPaper(const WallPaper &paper) {
return (paper.id() == kDefaultBackground);
}
QColor PatternColor(QColor background) {
const auto hue = background.hueF();
const auto saturation = background.saturationF();
const auto value = background.valueF();
return QColor::fromHsvF(
hue,
std::min(1.0, saturation + 0.05 + 0.1 * (1. - saturation)),
(value > 0.5
? std::max(0., value * 0.65)
: std::max(0., std::min(1., 1. - value * 0.65))),
0.4
).toRgb();
}
QImage PreparePatternImage(
QImage image,
QColor bg,
QColor fg,
int intensity) {
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
// Similar to ColorizePattern.
// But here we set bg to all 'alpha=0' pixels and fg to opaque ones.
const auto width = image.width();
const auto height = image.height();
const auto alpha = anim::interpolate(
0,
255,
fg.alphaF() * std::clamp(intensity / 100., 0., 1.));
if (!alpha) {
image.fill(bg);
return image;
}
fg.setAlpha(255);
const auto patternBg = anim::shifted(bg);
const auto patternFg = anim::shifted(fg);
const auto resultBytesPerPixel = (image.depth() >> 3);
constexpr auto resultIntsPerPixel = 1;
const auto resultIntsPerLine = (image.bytesPerLine() >> 2);
const auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(image.bits());
Assert(resultIntsAdded >= 0);
Assert(image.depth() == static_cast<int>((resultIntsPerPixel * sizeof(uint32)) << 3));
Assert(image.bytesPerLine() == (resultIntsPerLine << 2));
const auto maskBytesPerPixel = (image.depth() >> 3);
const auto maskBytesPerLine = image.bytesPerLine();
const auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
// We want to read the last byte of four available.
// This is the difference with style::colorizeImage.
auto maskBytes = image.constBits() + (maskBytesPerPixel - 1);
Assert(maskBytesAdded >= 0);
Assert(image.depth() == (maskBytesPerPixel << 3));
for (auto y = 0; y != height; ++y) {
for (auto x = 0; x != width; ++x) {
const auto maskOpacity = static_cast<anim::ShiftedMultiplier>(
*maskBytes) + 1;
const auto fgOpacity = (maskOpacity * alpha) >> 8;
const auto bgOpacity = 256 - fgOpacity;
*resultInts = anim::unshifted(
patternBg * bgOpacity + patternFg * fgOpacity);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
}
maskBytes += maskBytesAdded;
resultInts += resultIntsAdded;
}
return image;
}
namespace details {
WallPaper UninitializedWallPaper() {
@ -907,7 +925,7 @@ void ChatBackground::set(const Data::WallPaper &paper, QImage image) {
Data::PreparePatternImage(
image,
*fill,
PatternColor(*fill),
Data::PatternColor(*fill),
_paper.patternIntensity()));
setPreparedImage(std::move(image), std::move(prepared));
} else {
@ -1592,19 +1610,5 @@ bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QL
return true;
}
QColor PatternColor(QColor background) {
const auto hue = background.hueF();
const auto saturation = background.saturationF();
const auto value = background.valueF();
return QColor::fromHsvF(
hue,
std::min(1.0, saturation + 0.05 + 0.1 * (1. - saturation)),
(value > 0.5
? std::max(0., value * 0.65)
: std::max(0., std::min(1., 1. - value * 0.65))),
0.4
).toRgb();
}
} // namespace Theme
} // namespace Window

View file

@ -81,6 +81,13 @@ private:
[[nodiscard]] WallPaper DefaultWallPaper();
[[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper);
QColor PatternColor(QColor background);
QImage PreparePatternImage(
QImage image,
QColor bg,
QColor fg,
int intensity);
namespace details {
[[nodiscard]] WallPaper UninitializedWallPaper();
@ -147,8 +154,6 @@ void Revert();
bool LoadFromFile(const QString &file, Instance *out, QByteArray *outContent);
bool IsPaletteTestingPath(const QString &path);
QColor PatternColor(QColor background);
struct BackgroundUpdate {
enum class Type {
New,

View file

@ -275,6 +275,8 @@
<(src_loc)/history/media/history_media_sticker.cpp
<(src_loc)/history/media/history_media_video.h
<(src_loc)/history/media/history_media_video.cpp
<(src_loc)/history/media/history_media_wall_paper.h
<(src_loc)/history/media/history_media_wall_paper.cpp
<(src_loc)/history/media/history_media_web_page.h
<(src_loc)/history/media/history_media_web_page.cpp
<(src_loc)/history/view/history_view_context_menu.cpp