mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Allow sending media with spoilers.
This commit is contained in:
parent
3a38497c4c
commit
5bee6310c0
25 changed files with 465 additions and 111 deletions
|
@ -2327,6 +2327,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**.";
|
||||
"lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**.";
|
||||
|
||||
"lng_context_spoiler_effect" = "Spoiler Effect";
|
||||
"lng_context_disable_spoiler" = "Disable Spoiler Effect";
|
||||
|
||||
"lng_downloads_section" = "Downloads";
|
||||
"lng_downloads_view_in_chat" = "View in chat";
|
||||
"lng_downloads_view_in_section" = "View in downloads";
|
||||
|
|
|
@ -206,7 +206,7 @@ void EditMessageWithUploadedPhoto(
|
|||
EditMessageWithUploadedMedia(
|
||||
item,
|
||||
options,
|
||||
PrepareUploadedPhoto(std::move(info)));
|
||||
PrepareUploadedPhoto(item, std::move(info)));
|
||||
}
|
||||
|
||||
mtpRequestId EditCaption(
|
||||
|
|
|
@ -75,10 +75,14 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
|||
|
||||
} // namespace
|
||||
|
||||
MTPInputMedia PrepareUploadedPhoto(RemoteFileInfo info) {
|
||||
const auto flags = info.attachedStickers.empty()
|
||||
? MTPDinputMediaUploadedPhoto::Flags(0)
|
||||
: MTPDinputMediaUploadedPhoto::Flag::f_stickers;
|
||||
MTPInputMedia PrepareUploadedPhoto(
|
||||
not_null<HistoryItem*> item,
|
||||
RemoteFileInfo info) {
|
||||
using Flag = MTPDinputMediaUploadedPhoto::Flag;
|
||||
const auto spoiler = item->media()
|
||||
&& item->media()->hasSpoiler();
|
||||
const auto flags = (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag() : Flag::f_stickers);
|
||||
return MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(flags),
|
||||
info.file,
|
||||
|
@ -93,12 +97,13 @@ MTPInputMedia PrepareUploadedDocument(
|
|||
if (!item || !item->media() || !item->media()->document()) {
|
||||
return MTP_inputMediaEmpty();
|
||||
}
|
||||
const auto emptyFlag = MTPDinputMediaUploadedDocument::Flags(0);
|
||||
using DocFlags = MTPDinputMediaUploadedDocument::Flag;
|
||||
const auto flags = emptyFlag
|
||||
| (info.thumb ? DocFlags::f_thumb : emptyFlag)
|
||||
| (item->groupId() ? DocFlags::f_nosound_video : emptyFlag)
|
||||
| (info.attachedStickers.empty() ? DocFlags::f_stickers : emptyFlag);
|
||||
using Flag = MTPDinputMediaUploadedDocument::Flag;
|
||||
const auto spoiler = item->media()
|
||||
&& item->media()->hasSpoiler();
|
||||
const auto flags = (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
|
|
|
@ -13,7 +13,9 @@ namespace Api {
|
|||
|
||||
struct RemoteFileInfo;
|
||||
|
||||
MTPInputMedia PrepareUploadedPhoto(RemoteFileInfo info);
|
||||
MTPInputMedia PrepareUploadedPhoto(
|
||||
not_null<HistoryItem*> item,
|
||||
RemoteFileInfo info);
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
not_null<HistoryItem*> item,
|
||||
|
|
|
@ -441,13 +441,17 @@ void SendConfirmedFile(
|
|||
|
||||
const auto media = MTPMessageMedia([&] {
|
||||
if (file->type == SendMediaType::Photo) {
|
||||
using Flag = MTPDmessageMediaPhoto::Flag;
|
||||
return MTP_messageMediaPhoto(
|
||||
MTP_flags(MTPDmessageMediaPhoto::Flag::f_photo),
|
||||
MTP_flags(Flag::f_photo
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->photo,
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::File) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(MTPDmessageMediaDocument::Flag::f_document),
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
|
|
|
@ -3365,7 +3365,8 @@ void ApiWrap::editMedia(
|
|||
std::move(file.information),
|
||||
type,
|
||||
to,
|
||||
caption));
|
||||
caption,
|
||||
file.spoiler));
|
||||
}
|
||||
|
||||
void ApiWrap::sendFiles(
|
||||
|
@ -3406,6 +3407,7 @@ void ApiWrap::sendFiles(
|
|||
uploadWithType,
|
||||
to,
|
||||
caption,
|
||||
file.spoiler,
|
||||
album));
|
||||
caption = TextWithTags();
|
||||
}
|
||||
|
@ -3425,14 +3427,17 @@ void ApiWrap::sendFile(
|
|||
const SendAction &action) {
|
||||
const auto to = fileLoadTaskOptions(action);
|
||||
auto caption = TextWithTags();
|
||||
const auto spoiler = false;
|
||||
const auto information = nullptr;
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
QString(),
|
||||
fileContent,
|
||||
nullptr,
|
||||
information,
|
||||
type,
|
||||
to,
|
||||
caption));
|
||||
caption,
|
||||
spoiler));
|
||||
}
|
||||
|
||||
void ApiWrap::sendUploadedPhoto(
|
||||
|
@ -3440,7 +3445,7 @@ void ApiWrap::sendUploadedPhoto(
|
|||
Api::RemoteFileInfo info,
|
||||
Api::SendOptions options) {
|
||||
if (const auto item = _session->data().message(localId)) {
|
||||
const auto media = Api::PrepareUploadedPhoto(std::move(info));
|
||||
const auto media = Api::PrepareUploadedPhoto(item, std::move(info));
|
||||
if (const auto groupId = item->groupId()) {
|
||||
uploadAlbumMedia(item, groupId, media);
|
||||
} else {
|
||||
|
|
|
@ -215,19 +215,35 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
|||
|
||||
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
|
||||
if (!_isAlbum) {
|
||||
if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(
|
||||
_preview.get());
|
||||
media->setSendWay(way);
|
||||
}
|
||||
return;
|
||||
}
|
||||
applyAlbumOrder();
|
||||
applyChanges();
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
album->setSendWay(way);
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::applyAlbumOrder() {
|
||||
void SendFilesBox::Block::applyChanges() {
|
||||
if (!_isAlbum) {
|
||||
if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(
|
||||
_preview.get());
|
||||
(*_items)[_from].spoiler = media->hasSpoiler();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
const auto order = album->takeOrder();
|
||||
const auto spoilered = album->collectSpoileredIndices();
|
||||
const auto guard = gsl::finally([&] {
|
||||
for (auto i = 0, count = int(order.size()); i != count; ++i) {
|
||||
(*_items)[_from + i].spoiler = spoilered.contains(i);
|
||||
}
|
||||
});
|
||||
const auto isIdentity = [&] {
|
||||
for (auto i = 0, count = int(order.size()); i != count; ++i) {
|
||||
if (order[i] != i) {
|
||||
|
@ -385,19 +401,25 @@ void SendFilesBox::setupDragArea() {
|
|||
areas.photo->setDroppedCallback(droppedCallback(true));
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshAllAfterChanges(int fromItem) {
|
||||
void SendFilesBox::refreshAllAfterChanges(int fromItem, Fn<void()> perform) {
|
||||
auto fromBlock = 0;
|
||||
for (auto count = int(_blocks.size()); fromBlock != count; ++fromBlock) {
|
||||
if (_blocks[fromBlock].tillIndex() >= fromItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto index = fromBlock; index < _blocks.size(); ++index) {
|
||||
_blocks[index].applyChanges();
|
||||
}
|
||||
if (perform) {
|
||||
perform();
|
||||
}
|
||||
generatePreviewFrom(fromBlock);
|
||||
{
|
||||
auto sendWay = _sendWay.current();
|
||||
sendWay.setHasCompressedStickers(_list.hasSticker());
|
||||
_sendWay = sendWay;
|
||||
}
|
||||
generatePreviewFrom(fromBlock);
|
||||
_inner->resizeToWidth(st::boxWideWidth);
|
||||
refreshControls();
|
||||
captionResized();
|
||||
|
@ -489,11 +511,7 @@ void SendFilesBox::generatePreviewFrom(int fromBlock) {
|
|||
|
||||
using Type = Ui::PreparedFile::Type;
|
||||
|
||||
const auto eraseFrom = _blocks.begin() + fromBlock;
|
||||
for (auto i = eraseFrom; i != _blocks.end(); ++i) {
|
||||
i->applyAlbumOrder();
|
||||
}
|
||||
_blocks.erase(eraseFrom, _blocks.end());
|
||||
_blocks.erase(_blocks.begin() + fromBlock, _blocks.end());
|
||||
|
||||
const auto fromItem = _blocks.empty() ? 0 : _blocks.back().tillIndex();
|
||||
Assert(fromItem <= _list.files.size());
|
||||
|
@ -559,8 +577,9 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
closeBox();
|
||||
return;
|
||||
}
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
refreshAllAfterChanges(from);
|
||||
refreshAllAfterChanges(index, [&] {
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
});
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
|
@ -571,8 +590,9 @@ void SendFilesBox::pushBlock(int from, int till) {
|
|||
if (list.files.empty()) {
|
||||
return;
|
||||
}
|
||||
_list.files[index] = std::move(list.files.front());
|
||||
refreshAllAfterChanges(from);
|
||||
refreshAllAfterChanges(from, [&] {
|
||||
_list.files[index] = std::move(list.files.front());
|
||||
});
|
||||
};
|
||||
const auto checkResult = [=](const Ui::PreparedList &list) {
|
||||
if (_sendLimit != SendLimit::One) {
|
||||
|
@ -1076,7 +1096,7 @@ void SendFilesBox::send(
|
|||
}
|
||||
|
||||
for (auto &block : _blocks) {
|
||||
block.applyAlbumOrder();
|
||||
block.applyChanges();
|
||||
}
|
||||
|
||||
Storage::ApplyModifications(_list);
|
||||
|
|
|
@ -106,7 +106,7 @@ private:
|
|||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
void applyAlbumOrder();
|
||||
void applyChanges();
|
||||
|
||||
private:
|
||||
base::unique_qptr<Ui::RpWidget> _preview;
|
||||
|
@ -152,7 +152,7 @@ private:
|
|||
void pushBlock(int from, int till);
|
||||
|
||||
void openDialogToAddFileToAlbum();
|
||||
void refreshAllAfterChanges(int fromItem);
|
||||
void refreshAllAfterChanges(int fromItem, Fn<void()> perform = nullptr);
|
||||
|
||||
void enqueueNextPrepare();
|
||||
void addPreparedAsyncFile(Ui::PreparedFile &&file);
|
||||
|
|
|
@ -129,7 +129,8 @@ void DicePack::generateLocal(int index, const QString &name) {
|
|||
nullptr,
|
||||
SendMediaType::File,
|
||||
FileLoadTo(0, {}, 0, 0, 0),
|
||||
{});
|
||||
{},
|
||||
false);
|
||||
task.process({ .generateGoodThumbnail = false });
|
||||
const auto result = task.peekResult();
|
||||
Assert(result != nullptr);
|
||||
|
|
|
@ -446,6 +446,10 @@ QString Media::errorTextForForward(not_null<PeerData*> peer) const {
|
|||
return QString();
|
||||
}
|
||||
|
||||
bool Media::hasSpoiler() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Media::consumeMessageText(const TextWithEntities &text) {
|
||||
return false;
|
||||
}
|
||||
|
@ -663,6 +667,10 @@ QString MediaPhoto::errorTextForForward(not_null<PeerData*> peer) const {
|
|||
).value_or(QString());
|
||||
}
|
||||
|
||||
bool MediaPhoto::hasSpoiler() const {
|
||||
return _spoiler;
|
||||
}
|
||||
|
||||
bool MediaPhoto::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
if (media.type() != mtpc_messageMediaPhoto) {
|
||||
return false;
|
||||
|
@ -1037,6 +1045,10 @@ QString MediaFile::errorTextForForward(not_null<PeerData*> peer) const {
|
|||
return QString();
|
||||
}
|
||||
|
||||
bool MediaFile::hasSpoiler() const {
|
||||
return _spoiler;
|
||||
}
|
||||
|
||||
bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
if (media.type() != mtpc_messageMediaDocument) {
|
||||
return false;
|
||||
|
|
|
@ -129,6 +129,7 @@ public:
|
|||
virtual bool dropForwardedInfo() const;
|
||||
virtual bool forceForwardedInfo() const;
|
||||
virtual QString errorTextForForward(not_null<PeerData*> peer) const;
|
||||
[[nodiscard]] virtual bool hasSpoiler() const;
|
||||
|
||||
[[nodiscard]] virtual bool consumeMessageText(
|
||||
const TextWithEntities &text);
|
||||
|
@ -190,6 +191,7 @@ public:
|
|||
bool allowsEditCaption() const override;
|
||||
bool allowsEditMedia() const override;
|
||||
QString errorTextForForward(not_null<PeerData*> peer) const override;
|
||||
bool hasSpoiler() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
|
@ -233,6 +235,7 @@ public:
|
|||
bool forwardedBecomesUnread() const override;
|
||||
bool dropForwardedInfo() const override;
|
||||
QString errorTextForForward(not_null<PeerData*> peer) const override;
|
||||
bool hasSpoiler() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
|
|
|
@ -834,12 +834,27 @@ void Gif::validateSpoilerImageCache(
|
|||
&& _spoiler->backgroundRounding == rounding) {
|
||||
return;
|
||||
}
|
||||
const auto normal = _dataMedia->thumbnail();
|
||||
auto container = std::optional<Image>();
|
||||
const auto downscale = [&](Image *image) {
|
||||
if (!image || (image->width() <= 40 && image->height() <= 40)) {
|
||||
return image;
|
||||
}
|
||||
container.emplace(image->original().scaled(
|
||||
{ 40, 40 },
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
return &*container;
|
||||
};
|
||||
const auto videothumb = _videoThumbnailFrame.get();
|
||||
const auto embedded = _dataMedia->thumbnailInline();
|
||||
const auto blurred = embedded ? embedded : downscale(normal);
|
||||
_spoiler->background = Images::Round(
|
||||
PrepareWithBlurredBackground(
|
||||
outer,
|
||||
::Media::Streaming::ExpandDecision(),
|
||||
nullptr,
|
||||
_dataMedia->thumbnailInline()),
|
||||
blurred),
|
||||
MediaRoundingMask(rounding));
|
||||
_spoiler->backgroundRounding = rounding;
|
||||
}
|
||||
|
|
|
@ -1588,6 +1588,7 @@ void FormController::uploadEncryptedFile(
|
|||
file.uploadData->fileId,
|
||||
FileLoadTo(PeerId(), Api::SendOptions(), MsgId(), MsgId(), MsgId()),
|
||||
TextWithTags(),
|
||||
false,
|
||||
std::shared_ptr<SendingAlbum>(nullptr));
|
||||
prepared->type = SendMediaType::Secure;
|
||||
prepared->content = QByteArray::fromRawData(
|
||||
|
|
|
@ -488,12 +488,14 @@ FileLoadResult::FileLoadResult(
|
|||
uint64 id,
|
||||
const FileLoadTo &to,
|
||||
const TextWithTags &caption,
|
||||
bool spoiler,
|
||||
std::shared_ptr<SendingAlbum> album)
|
||||
: taskId(taskId)
|
||||
, id(id)
|
||||
, to(to)
|
||||
, album(std::move(album))
|
||||
, caption(caption) {
|
||||
, caption(caption)
|
||||
, spoiler(spoiler) {
|
||||
}
|
||||
|
||||
void FileLoadResult::setFileData(const QByteArray &filedata) {
|
||||
|
@ -530,6 +532,7 @@ FileLoadTask::FileLoadTask(
|
|||
SendMediaType type,
|
||||
const FileLoadTo &to,
|
||||
const TextWithTags &caption,
|
||||
bool spoiler,
|
||||
std::shared_ptr<SendingAlbum> album)
|
||||
: _id(base::RandomValue<uint64>())
|
||||
, _session(session)
|
||||
|
@ -540,7 +543,8 @@ FileLoadTask::FileLoadTask(
|
|||
, _content(content)
|
||||
, _information(std::move(information))
|
||||
, _type(type)
|
||||
, _caption(caption) {
|
||||
, _caption(caption)
|
||||
, _spoiler(spoiler) {
|
||||
Expects(to.options.scheduled
|
||||
|| !to.replaceMediaOf
|
||||
|| IsServerMsgId(to.replaceMediaOf));
|
||||
|
@ -736,6 +740,7 @@ void FileLoadTask::process(Args &&args) {
|
|||
_id,
|
||||
_to,
|
||||
_caption,
|
||||
_spoiler,
|
||||
_album);
|
||||
|
||||
QString filename, filemime;
|
||||
|
|
|
@ -224,6 +224,7 @@ struct FileLoadResult {
|
|||
uint64 id,
|
||||
const FileLoadTo &to,
|
||||
const TextWithTags &caption,
|
||||
bool spoiler,
|
||||
std::shared_ptr<SendingAlbum> album);
|
||||
|
||||
TaskId taskId;
|
||||
|
@ -256,6 +257,7 @@ struct FileLoadResult {
|
|||
|
||||
PreparedPhotoThumbs photoThumbs;
|
||||
TextWithTags caption;
|
||||
bool spoiler = false;
|
||||
|
||||
std::vector<MTPInputDocument> attachedStickers;
|
||||
|
||||
|
@ -285,6 +287,7 @@ public:
|
|||
SendMediaType type,
|
||||
const FileLoadTo &to,
|
||||
const TextWithTags &caption,
|
||||
bool spoiler,
|
||||
std::shared_ptr<SendingAlbum> album = nullptr);
|
||||
FileLoadTask(
|
||||
not_null<Main::Session*> session,
|
||||
|
@ -343,6 +346,7 @@ private:
|
|||
VoiceWaveform _waveform;
|
||||
SendMediaType _type;
|
||||
TextWithTags _caption;
|
||||
bool _spoiler = false;
|
||||
|
||||
std::shared_ptr<FileLoadResult> _result;
|
||||
|
||||
|
|
|
@ -9,12 +9,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "editor/photo_editor_common.h"
|
||||
#include "ui/chat/attach/attach_controls.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
@ -29,7 +33,6 @@ AbstractSingleMediaPreview::AbstractSingleMediaPreview(
|
|||
: AbstractSinglePreview(parent)
|
||||
, _minThumbH(st::sendBoxAlbumGroupSize.height()
|
||||
+ st::sendBoxAlbumGroupSkipTop * 2)
|
||||
, _photoEditorButton(base::make_unique_q<AbstractButton>(this))
|
||||
, _controls(base::make_unique_q<AttachControlsWidget>(this, type)) {
|
||||
}
|
||||
|
||||
|
@ -44,7 +47,25 @@ rpl::producer<> AbstractSingleMediaPreview::editRequests() const {
|
|||
}
|
||||
|
||||
rpl::producer<> AbstractSingleMediaPreview::modifyRequests() const {
|
||||
return _photoEditorButton->clicks() | rpl::to_empty;
|
||||
return _photoEditorRequests.events();
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::setSendWay(SendFilesWay way) {
|
||||
if (_sendWay != way) {
|
||||
_sendWay = way;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::setSpoiler(bool spoiler) {
|
||||
_spoiler = spoiler
|
||||
? std::make_unique<SpoilerAnimation>([=] { update(); })
|
||||
: nullptr;
|
||||
update();
|
||||
}
|
||||
|
||||
bool AbstractSingleMediaPreview::hasSpoiler() const {
|
||||
return _spoiler != nullptr;
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::preparePreview(QImage preview) {
|
||||
|
@ -105,16 +126,17 @@ void AbstractSingleMediaPreview::preparePreview(QImage preview) {
|
|||
preview = Images::Opaque(std::move(preview));
|
||||
_preview = PixmapFromImage(std::move(preview));
|
||||
_preview.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
|
||||
updatePhotoEditorButton();
|
||||
_previewBlurred = QPixmap();
|
||||
|
||||
resize(width(), std::max(_previewHeight, _minThumbH));
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::updatePhotoEditorButton() {
|
||||
_photoEditorButton->resize(_previewWidth, _previewHeight);
|
||||
_photoEditorButton->moveToLeft(_previewLeft, _previewTop);
|
||||
_photoEditorButton->setVisible(isPhoto());
|
||||
bool AbstractSingleMediaPreview::isOverPreview(QPoint position) const {
|
||||
return QRect(
|
||||
_previewLeft,
|
||||
_previewTop,
|
||||
_previewWidth,
|
||||
_previewHeight).contains(position);
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::resizeEvent(QResizeEvent *e) {
|
||||
|
@ -127,6 +149,15 @@ void AbstractSingleMediaPreview::resizeEvent(QResizeEvent *e) {
|
|||
void AbstractSingleMediaPreview::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
auto takenSpoiler = (drawBackground() || _sendWay.sendImagesAsPhotos())
|
||||
? nullptr
|
||||
: base::take(_spoiler);
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (takenSpoiler) {
|
||||
_spoiler = base::take(takenSpoiler);
|
||||
}
|
||||
});
|
||||
|
||||
if (drawBackground()) {
|
||||
const auto &padding = st::boxPhotoPadding;
|
||||
if (_previewLeft > padding.left()) {
|
||||
|
@ -154,10 +185,23 @@ void AbstractSingleMediaPreview::paintEvent(QPaintEvent *e) {
|
|||
st::confirmBg);
|
||||
}
|
||||
}
|
||||
if (!tryPaintAnimation(p)) {
|
||||
p.drawPixmap(_previewLeft, _previewTop, _preview);
|
||||
|
||||
if (_spoiler && _previewBlurred.isNull()) {
|
||||
_previewBlurred = BlurredPreviewFromPixmap(_preview, RectPart::None);
|
||||
}
|
||||
if (_animated && !isAnimatedPreviewReady()) {
|
||||
if (_spoiler || !tryPaintAnimation(p)) {
|
||||
const auto &pixmap = _spoiler ? _previewBlurred : _preview;
|
||||
const auto position = QPoint(_previewLeft, _previewTop);
|
||||
p.drawPixmap(position, pixmap);
|
||||
if (_spoiler) {
|
||||
FillSpoilerRect(
|
||||
p,
|
||||
QRect(position, pixmap.size() / pixmap.devicePixelRatio()),
|
||||
DefaultImageSpoiler().frame(
|
||||
_spoiler->index(crl::now(), false)));
|
||||
}
|
||||
}
|
||||
if (_animated && !isAnimatedPreviewReady() && !_spoiler) {
|
||||
const auto innerSize = st::msgFileLayout.thumbSize;
|
||||
auto inner = QRect(
|
||||
_previewLeft + (_previewWidth - innerSize) / 2,
|
||||
|
@ -177,6 +221,56 @@ void AbstractSingleMediaPreview::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::mousePressEvent(QMouseEvent *e) {
|
||||
if (isOverPreview(e->pos())) {
|
||||
_pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::mouseMoveEvent(QMouseEvent *e) {
|
||||
applyCursor((isPhoto() && isOverPreview(e->pos()))
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (base::take(_pressed) && isOverPreview(e->pos())) {
|
||||
if (e->button() == Qt::RightButton) {
|
||||
showContextMenu(e->globalPos());
|
||||
} else if (isPhoto()) {
|
||||
_photoEditorRequests.fire({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::applyCursor(style::cursor cursor) {
|
||||
if (_cursor != cursor) {
|
||||
_cursor = cursor;
|
||||
setCursor(_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSingleMediaPreview::showContextMenu(QPoint position) {
|
||||
if (!_sendWay.sendImagesAsPhotos()) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
_menu->addAction(hasSpoiler()
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now), [=] {
|
||||
setSpoiler(!hasSpoiler());
|
||||
}, &st::menuIconCopy);
|
||||
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(position);
|
||||
}
|
||||
}
|
||||
|
||||
int AbstractSingleMediaPreview::previewLeft() const {
|
||||
return _previewLeft;
|
||||
}
|
||||
|
|
|
@ -9,27 +9,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "ui/chat/attach/attach_abstract_single_preview.h"
|
||||
#include "ui/chat/attach/attach_controls.h"
|
||||
#include "ui/chat/attach/attach_send_files_way.h"
|
||||
#include "ui/abstract_button.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class PopupMenu;
|
||||
|
||||
class AbstractSingleMediaPreview : public AbstractSinglePreview {
|
||||
public:
|
||||
AbstractSingleMediaPreview(QWidget *parent, AttachControls::Type type);
|
||||
~AbstractSingleMediaPreview();
|
||||
|
||||
void setSendWay(SendFilesWay way);
|
||||
|
||||
[[nodiscard]] rpl::producer<> deleteRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> editRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> modifyRequests() const override;
|
||||
|
||||
[[nodiscard]] bool isPhoto() const;
|
||||
|
||||
void setSpoiler(bool spoiler);
|
||||
[[nodiscard]] bool hasSpoiler() const;
|
||||
|
||||
protected:
|
||||
virtual bool drawBackground() const = 0;
|
||||
virtual bool tryPaintAnimation(QPainter &p) = 0;
|
||||
virtual bool isAnimatedPreviewReady() const = 0;
|
||||
|
||||
void updatePhotoEditorButton();
|
||||
void preparePreview(QImage preview);
|
||||
|
||||
int previewLeft() const;
|
||||
|
@ -42,17 +49,33 @@ protected:
|
|||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
[[nodiscard]] bool isOverPreview(QPoint position) const;
|
||||
void applyCursor(style::cursor cursor);
|
||||
void showContextMenu(QPoint position);
|
||||
|
||||
SendFilesWay _sendWay;
|
||||
bool _animated = false;
|
||||
QPixmap _preview;
|
||||
QPixmap _previewBlurred;
|
||||
int _previewLeft = 0;
|
||||
int _previewTop = 0;
|
||||
int _previewWidth = 0;
|
||||
int _previewHeight = 0;
|
||||
|
||||
std::unique_ptr<SpoilerAnimation> _spoiler;
|
||||
|
||||
const int _minThumbH;
|
||||
const base::unique_qptr<AbstractButton> _photoEditorButton;
|
||||
const base::unique_qptr<AttachControlsWidget> _controls;
|
||||
rpl::event_stream<> _photoEditorRequests;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
bool _pressed = false;
|
||||
|
||||
base::unique_qptr<PopupMenu> _menu;
|
||||
|
||||
rpl::event_stream<> _modifyRequests;
|
||||
|
||||
|
|
|
@ -9,10 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "ui/chat/attach/attach_album_thumbnail.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
|
@ -61,6 +64,19 @@ void AlbumPreview::updateFileRows() {
|
|||
}
|
||||
}
|
||||
|
||||
base::flat_set<int> AlbumPreview::collectSpoileredIndices() {
|
||||
auto result = base::flat_set<int>();
|
||||
result.reserve(_thumbs.size());
|
||||
auto i = 0;
|
||||
for (const auto &thumb : _thumbs) {
|
||||
if (thumb->hasSpoiler()) {
|
||||
result.emplace(i);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> AlbumPreview::takeOrder() {
|
||||
//Expects(_thumbs.size() == _order.size());
|
||||
//Expects(_itemsShownDimensions.size() == _order.size());
|
||||
|
@ -112,6 +128,7 @@ void AlbumPreview::prepareThumbs(gsl::span<Ui::PreparedFile> items) {
|
|||
items[i],
|
||||
layout[i],
|
||||
this,
|
||||
[=] { update(); },
|
||||
[=] { changeThumbByIndex(thumbIndex(thumbUnderCursor())); },
|
||||
[=] { deleteThumbByIndex(thumbIndex(thumbUnderCursor())); }));
|
||||
if (_thumbs.back()->isCompressedSticker()) {
|
||||
|
@ -365,11 +382,7 @@ void AlbumPreview::paintFiles(Painter &p, QRect clip) const {
|
|||
} else if (bottom <= clip.y()) {
|
||||
continue;
|
||||
}
|
||||
if (thumb->isCompressedSticker()) {
|
||||
thumb->paintPhoto(p, left, top, outerWidth);
|
||||
} else {
|
||||
thumb->paintFile(p, left, top, outerWidth);
|
||||
}
|
||||
thumb->paintFile(p, left, top, outerWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -393,15 +406,6 @@ void AlbumPreview::deleteThumbByIndex(int index) {
|
|||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
const auto orderIt = ranges::find(_order, index);
|
||||
Expects(orderIt != _order.end());
|
||||
|
||||
_order.erase(orderIt);
|
||||
ranges::for_each(_order, [=](auto &i) {
|
||||
if (i > index) {
|
||||
i--;
|
||||
}
|
||||
});
|
||||
_thumbDeleted.fire(std::move(index));
|
||||
}
|
||||
|
||||
|
@ -448,7 +452,8 @@ void AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
|||
|
||||
const auto isAlbum = _sendWay.sendImagesAsPhotos()
|
||||
&& _sendWay.groupFiles();
|
||||
if (!isAlbum) {
|
||||
if (!isAlbum || e->button() != Qt::LeftButton) {
|
||||
_dragTimer.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -554,13 +559,38 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
|
|||
} else if (const auto thumb = base::take(_pressedThumb)) {
|
||||
const auto was = _pressedButtonType;
|
||||
const auto now = thumb->buttonTypeFromPoint(e->pos());
|
||||
if (was == now) {
|
||||
if (e->button() == Qt::RightButton) {
|
||||
showContextMenu(thumb, e->globalPos());
|
||||
} else if (was == now) {
|
||||
thumbButtonsCallback(thumb, now);
|
||||
}
|
||||
}
|
||||
_pressedButtonType = AttachButtonType::None;
|
||||
}
|
||||
|
||||
void AlbumPreview::showContextMenu(
|
||||
not_null<AlbumThumbnail*> thumb,
|
||||
QPoint position) {
|
||||
if (!_sendWay.sendImagesAsPhotos()) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
_menu->addAction(thumb->hasSpoiler()
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now), [=] {
|
||||
thumb->setSpoiler(!thumb->hasSpoiler());
|
||||
}, &st::menuIconCopy);
|
||||
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(position);
|
||||
}
|
||||
}
|
||||
|
||||
void AlbumPreview::switchToDrag() {
|
||||
_paintedAbove
|
||||
= _suggestedThumb
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace Ui {
|
|||
struct PreparedFile;
|
||||
struct GroupMediaLayout;
|
||||
class AlbumThumbnail;
|
||||
class PopupMenu;
|
||||
|
||||
class AlbumPreview final : public RpWidget {
|
||||
public:
|
||||
|
@ -26,7 +27,9 @@ public:
|
|||
~AlbumPreview();
|
||||
|
||||
void setSendWay(SendFilesWay way);
|
||||
std::vector<int> takeOrder();
|
||||
|
||||
[[nodiscard]] base::flat_set<int> collectSpoileredIndices();
|
||||
[[nodiscard]] std::vector<int> takeOrder();
|
||||
|
||||
[[nodiscard]] rpl::producer<int> thumbDeleted() const {
|
||||
return _thumbDeleted.events();
|
||||
|
@ -79,6 +82,8 @@ private:
|
|||
void cancelDrag();
|
||||
void finishDrag();
|
||||
|
||||
void showContextMenu(not_null<AlbumThumbnail*> thumb, QPoint position);
|
||||
|
||||
SendFilesWay _sendWay;
|
||||
style::cursor _cursor = style::cur_default;
|
||||
std::vector<int> _order;
|
||||
|
@ -103,6 +108,8 @@ private:
|
|||
rpl::event_stream<int> _thumbChanged;
|
||||
rpl::event_stream<int> _thumbModified;
|
||||
|
||||
base::unique_qptr<PopupMenu> _menu;
|
||||
|
||||
mutable Animations::Simple _thumbsHeightAnimation;
|
||||
mutable Animations::Simple _shrinkAnimation;
|
||||
mutable Animations::Simple _finishDragAnimation;
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/painter.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
@ -26,6 +27,7 @@ AlbumThumbnail::AlbumThumbnail(
|
|||
const PreparedFile &file,
|
||||
const GroupMediaLayout &layout,
|
||||
QWidget *parent,
|
||||
Fn<void()> repaint,
|
||||
Fn<void()> editCallback,
|
||||
Fn<void()> deleteCallback)
|
||||
: _layout(layout)
|
||||
|
@ -33,7 +35,8 @@ AlbumThumbnail::AlbumThumbnail(
|
|||
, _shrinkSize(int(std::ceil(st::roundRadiusLarge / 1.4)))
|
||||
, _isPhoto(file.type == PreparedFile::Type::Photo)
|
||||
, _isVideo(file.type == PreparedFile::Type::Video)
|
||||
, _isCompressedSticker(Core::IsMimeSticker(file.information->filemime)) {
|
||||
, _isCompressedSticker(Core::IsMimeSticker(file.information->filemime))
|
||||
, _repaint(std::move(repaint)) {
|
||||
Expects(!_fullPreview.isNull());
|
||||
|
||||
moveToLayout(layout);
|
||||
|
@ -107,9 +110,23 @@ AlbumThumbnail::AlbumThumbnail(
|
|||
_editMedia->setIconOverride(&st::sendBoxAlbumGroupEditButtonIconFile);
|
||||
_deleteMedia->setIconOverride(&st::sendBoxAlbumGroupDeleteButtonIconFile);
|
||||
|
||||
setSpoiler(file.spoiler);
|
||||
setButtonVisible(false);
|
||||
}
|
||||
|
||||
void AlbumThumbnail::setSpoiler(bool spoiler) {
|
||||
Expects(_repaint != nullptr);
|
||||
|
||||
_spoiler = spoiler
|
||||
? std::make_unique<SpoilerAnimation>(_repaint)
|
||||
: nullptr;
|
||||
_repaint();
|
||||
}
|
||||
|
||||
bool AlbumThumbnail::hasSpoiler() const {
|
||||
return _spoiler != nullptr;
|
||||
}
|
||||
|
||||
void AlbumThumbnail::setButtonVisible(bool value) {
|
||||
_editMedia->setVisible(value);
|
||||
_deleteMedia->setVisible(value);
|
||||
|
@ -135,7 +152,7 @@ void AlbumThumbnail::animateLayoutToInitial() {
|
|||
}
|
||||
|
||||
void AlbumThumbnail::moveToLayout(const GroupMediaLayout &layout) {
|
||||
using Option = Images::Option;
|
||||
using namespace Images;
|
||||
|
||||
animateLayoutToInitial();
|
||||
_layout = layout;
|
||||
|
@ -143,27 +160,20 @@ void AlbumThumbnail::moveToLayout(const GroupMediaLayout &layout) {
|
|||
const auto width = _layout.geometry.width();
|
||||
const auto height = _layout.geometry.height();
|
||||
_albumCorners = GetCornersFromSides(_layout.sides);
|
||||
const auto corner = [&](RectPart part, Option skip) {
|
||||
return !(_albumCorners & part) ? skip : Option();
|
||||
};
|
||||
const auto options = Option::RoundLarge
|
||||
| corner(RectPart::TopLeft, Option::RoundSkipTopLeft)
|
||||
| corner(RectPart::TopRight, Option::RoundSkipTopRight)
|
||||
| corner(RectPart::BottomLeft, Option::RoundSkipBottomLeft)
|
||||
| corner(RectPart::BottomRight, Option::RoundSkipBottomRight);
|
||||
const auto pixSize = GetImageScaleSizeForGeometry(
|
||||
{ _fullPreview.width(), _fullPreview.height() },
|
||||
{ width, height });
|
||||
const auto pixWidth = pixSize.width() * style::DevicePixelRatio();
|
||||
const auto pixHeight = pixSize.height() * style::DevicePixelRatio();
|
||||
|
||||
_albumImage = PixmapFromImage(Images::Prepare(
|
||||
_albumImage = PixmapFromImage(Prepare(
|
||||
_fullPreview,
|
||||
QSize(pixWidth, pixHeight),
|
||||
{
|
||||
.options = options,
|
||||
.options = RoundOptions(ImageRoundRadius::Large, _albumCorners),
|
||||
.outer = { width, height },
|
||||
}));
|
||||
_albumImageBlurred = QPixmap();
|
||||
}
|
||||
|
||||
int AlbumThumbnail::photoHeight() const {
|
||||
|
@ -188,46 +198,83 @@ void AlbumThumbnail::paintInAlbum(
|
|||
float64 moveProgress) {
|
||||
const auto shrink = anim::interpolate(0, _shrinkSize, shrinkProgress);
|
||||
_lastShrinkValue = shrink;
|
||||
const auto geometry = countCurrentGeometry(moveProgress);
|
||||
const auto x = left + geometry.x();
|
||||
const auto y = top + geometry.y();
|
||||
if (shrink > 0 || moveProgress < 1.) {
|
||||
const auto size = geometry.size();
|
||||
if (shrinkProgress < 1 && _albumCorners != RectPart::None) {
|
||||
prepareCache(size, shrink);
|
||||
p.drawImage(x, y, _albumCache);
|
||||
} else {
|
||||
const auto to = QRect({ x, y }, size).marginsRemoved(
|
||||
const auto geometry = countCurrentGeometry(
|
||||
moveProgress
|
||||
).translated(left, top);
|
||||
auto paintedTo = geometry;
|
||||
const auto revealed = _spoiler ? shrinkProgress : 1.;
|
||||
if (revealed > 0.) {
|
||||
if (shrink > 0 || moveProgress < 1.) {
|
||||
const auto size = geometry.size();
|
||||
paintedTo = geometry.marginsRemoved(
|
||||
{ shrink, shrink, shrink, shrink }
|
||||
);
|
||||
drawSimpleFrame(p, to, size);
|
||||
if (shrinkProgress < 1 && _albumCorners != RectPart::None) {
|
||||
prepareCache(size, shrink);
|
||||
p.drawImage(geometry.topLeft(), _albumCache);
|
||||
} else {
|
||||
drawSimpleFrame(p, paintedTo, size);
|
||||
}
|
||||
} else {
|
||||
p.drawPixmap(geometry.topLeft(), _albumImage);
|
||||
}
|
||||
if (_isVideo) {
|
||||
paintPlayVideo(p, geometry);
|
||||
}
|
||||
} else {
|
||||
p.drawPixmap(x, y, _albumImage);
|
||||
}
|
||||
if (_isVideo) {
|
||||
const auto innerSize = st::msgFileLayout.thumbSize;
|
||||
const auto inner = QRect(
|
||||
x + (geometry.width() - innerSize) / 2,
|
||||
y + (geometry.height() - innerSize) / 2,
|
||||
innerSize,
|
||||
innerSize);
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
p.drawEllipse(inner);
|
||||
if (revealed < 1.) {
|
||||
auto corners = Images::CornersMaskRef(
|
||||
Images::CornersMask(ImageRoundRadius::Large));
|
||||
if (!(_albumCorners & RectPart::TopLeft)) {
|
||||
corners.p[0] = nullptr;
|
||||
}
|
||||
st::historyFileThumbPlay.paintInCenter(p, inner);
|
||||
if (!(_albumCorners & RectPart::TopRight)) {
|
||||
corners.p[1] = nullptr;
|
||||
}
|
||||
if (!(_albumCorners & RectPart::BottomLeft)) {
|
||||
corners.p[2] = nullptr;
|
||||
}
|
||||
if (!(_albumCorners & RectPart::BottomRight)) {
|
||||
corners.p[3] = nullptr;
|
||||
}
|
||||
p.setOpacity(1. - revealed);
|
||||
if (_albumImageBlurred.isNull()) {
|
||||
_albumImageBlurred = BlurredPreviewFromPixmap(
|
||||
_albumImage,
|
||||
_albumCorners);
|
||||
}
|
||||
p.drawPixmap(paintedTo, _albumImageBlurred);
|
||||
FillSpoilerRect(
|
||||
p,
|
||||
paintedTo,
|
||||
corners,
|
||||
DefaultImageSpoiler().frame(_spoiler->index(crl::now(), false)),
|
||||
_cornerCache);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
_lastRectOfButtons = paintButtons(
|
||||
p,
|
||||
{ x, y },
|
||||
geometry.topLeft(),
|
||||
geometry.width(),
|
||||
shrinkProgress);
|
||||
_lastRectOfModify = geometry;
|
||||
}
|
||||
|
||||
_lastRectOfModify = QRect(QPoint(x, y), geometry.size());
|
||||
void AlbumThumbnail::paintPlayVideo(QPainter &p, QRect geometry) {
|
||||
const auto innerSize = st::msgFileLayout.thumbSize;
|
||||
const auto inner = QRect(
|
||||
geometry.x() + (geometry.width() - innerSize) / 2,
|
||||
geometry.y() + (geometry.height() - innerSize) / 2,
|
||||
innerSize,
|
||||
innerSize);
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
p.drawEllipse(inner);
|
||||
}
|
||||
st::historyFileThumbPlay.paintInCenter(p, inner);
|
||||
}
|
||||
|
||||
void AlbumThumbnail::prepareCache(QSize size, int shrink) {
|
||||
|
@ -376,11 +423,33 @@ void AlbumThumbnail::drawSimpleFrame(QPainter &p, QRect to, QSize size) const {
|
|||
|
||||
void AlbumThumbnail::paintPhoto(Painter &p, int left, int top, int outerWidth) {
|
||||
const auto size = _photo.size() / style::DevicePixelRatio();
|
||||
if (_spoiler && _photoBlurred.isNull()) {
|
||||
_photoBlurred = BlurredPreviewFromPixmap(
|
||||
_photo,
|
||||
RectPart::AllCorners);
|
||||
}
|
||||
const auto &pixmap = _spoiler ? _photoBlurred : _photo;
|
||||
const auto rect = QRect(
|
||||
left + (st::sendMediaPreviewSize - size.width()) / 2,
|
||||
top,
|
||||
pixmap.width() / pixmap.devicePixelRatio(),
|
||||
pixmap.height() / pixmap.devicePixelRatio());
|
||||
p.drawPixmapLeft(
|
||||
left + (st::sendMediaPreviewSize - size.width()) / 2,
|
||||
top,
|
||||
outerWidth,
|
||||
_photo);
|
||||
pixmap);
|
||||
if (_spoiler) {
|
||||
FillSpoilerRect(
|
||||
p,
|
||||
rect,
|
||||
Images::CornersMaskRef(
|
||||
Images::CornersMask(ImageRoundRadius::Large)),
|
||||
DefaultImageSpoiler().frame(_spoiler->index(crl::now(), false)),
|
||||
_cornerCache);
|
||||
} else if (_isVideo) {
|
||||
paintPlayVideo(p, rect);
|
||||
}
|
||||
|
||||
const auto topLeft = QPoint{ left, top };
|
||||
|
||||
|
@ -398,6 +467,13 @@ void AlbumThumbnail::paintFile(
|
|||
int left,
|
||||
int top,
|
||||
int outerWidth) {
|
||||
|
||||
if (isCompressedSticker()) {
|
||||
auto spoiler = base::take(_spoiler);
|
||||
paintPhoto(p, left, top, outerWidth);
|
||||
_spoiler = base::take(spoiler);
|
||||
return;
|
||||
}
|
||||
const auto &st = st::attachPreviewThumbLayout;
|
||||
const auto textLeft = left + st.thumbSize + st.thumbSkip;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Ui {
|
|||
|
||||
struct PreparedFile;
|
||||
class IconButton;
|
||||
class SpoilerAnimation;
|
||||
|
||||
class AlbumThumbnail final {
|
||||
public:
|
||||
|
@ -25,6 +26,7 @@ public:
|
|||
const PreparedFile &file,
|
||||
const GroupMediaLayout &layout,
|
||||
QWidget *parent,
|
||||
Fn<void()> repaint,
|
||||
Fn<void()> editCallback,
|
||||
Fn<void()> deleteCallback);
|
||||
|
||||
|
@ -32,6 +34,9 @@ public:
|
|||
void animateLayoutToInitial();
|
||||
void resetLayoutAnimation();
|
||||
|
||||
void setSpoiler(bool spoiler);
|
||||
[[nodiscard]] bool hasSpoiler() const;
|
||||
|
||||
int photoHeight() const;
|
||||
int fileHeight() const;
|
||||
|
||||
|
@ -71,6 +76,7 @@ private:
|
|||
QPoint point,
|
||||
int outerWidth,
|
||||
float64 shrinkProgress);
|
||||
void paintPlayVideo(QPainter &p, QRect geometry);
|
||||
|
||||
GroupMediaLayout _layout;
|
||||
std::optional<QRect> _animateFromGeometry;
|
||||
|
@ -79,10 +85,12 @@ private:
|
|||
const bool _isPhoto;
|
||||
const bool _isVideo;
|
||||
QPixmap _albumImage;
|
||||
QPixmap _albumImageBlurred;
|
||||
QImage _albumCache;
|
||||
QPoint _albumPosition;
|
||||
RectParts _albumCorners = RectPart::None;
|
||||
QPixmap _photo;
|
||||
QPixmap _photoBlurred;
|
||||
QPixmap _fileThumb;
|
||||
QString _name;
|
||||
QString _status;
|
||||
|
@ -94,6 +102,9 @@ private:
|
|||
AttachControls _buttons;
|
||||
|
||||
bool _isCompressedSticker = false;
|
||||
std::unique_ptr<SpoilerAnimation> _spoiler;
|
||||
QImage _cornerCache;
|
||||
Fn<void()> _repaint;
|
||||
|
||||
QRect _lastRectOfModify;
|
||||
QRect _lastRectOfButtons;
|
||||
|
|
|
@ -295,4 +295,28 @@ QPixmap PrepareSongCoverForThumbnail(QImage image, int size) {
|
|||
}));
|
||||
}
|
||||
|
||||
QPixmap BlurredPreviewFromPixmap(QPixmap pixmap, RectParts corners) {
|
||||
const auto image = pixmap.toImage();
|
||||
const auto skip = st::roundRadiusLarge * image.devicePixelRatio();
|
||||
auto small = image.copy(
|
||||
skip,
|
||||
skip,
|
||||
image.width() - 2 * skip,
|
||||
image.height() - 2 * skip
|
||||
).scaled(
|
||||
40,
|
||||
40,
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation);
|
||||
|
||||
using namespace Images;
|
||||
return PixmapFromImage(Prepare(
|
||||
Blur(std::move(small), true),
|
||||
image.size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.options = RoundOptions(ImageRoundRadius::Large, corners),
|
||||
.outer = image.size() / style::DevicePixelRatio(),
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "editor/photo_editor_common.h"
|
||||
#include "ui/rect_part.h"
|
||||
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <deque>
|
||||
|
@ -80,6 +81,7 @@ struct PreparedFile {
|
|||
QImage preview;
|
||||
QSize shownDimensions;
|
||||
Type type = Type::File;
|
||||
bool spoiler = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool CanBeInAlbumType(PreparedFile::Type type, AlbumType album);
|
||||
|
@ -143,4 +145,8 @@ struct PreparedGroup {
|
|||
|
||||
[[nodiscard]] QPixmap PrepareSongCoverForThumbnail(QImage image, int size);
|
||||
|
||||
[[nodiscard]] QPixmap BlurredPreviewFromPixmap(
|
||||
QPixmap pixmap,
|
||||
RectParts corners);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -47,6 +47,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
|
|||
preview,
|
||||
animated,
|
||||
Core::IsMimeSticker(file.information->filemime),
|
||||
file.spoiler,
|
||||
animationPreview ? file.path : QString(),
|
||||
type);
|
||||
}
|
||||
|
@ -57,6 +58,7 @@ SingleMediaPreview::SingleMediaPreview(
|
|||
QImage preview,
|
||||
bool animated,
|
||||
bool sticker,
|
||||
bool spoiler,
|
||||
const QString &animatedPreviewPath,
|
||||
AttachControls::Type type)
|
||||
: AbstractSingleMediaPreview(parent, type)
|
||||
|
@ -67,7 +69,7 @@ SingleMediaPreview::SingleMediaPreview(
|
|||
|
||||
preparePreview(preview);
|
||||
prepareAnimatedPreview(animatedPreviewPath, animated);
|
||||
updatePhotoEditorButton();
|
||||
setSpoiler(spoiler);
|
||||
}
|
||||
|
||||
bool SingleMediaPreview::drawBackground() const {
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
QImage preview,
|
||||
bool animated,
|
||||
bool sticker,
|
||||
bool spoiler,
|
||||
const QString &animatedPreviewPath,
|
||||
AttachControls::Type type);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue