Move SingleMediaPreview to td_ui.
This commit is contained in:
parent
8b96f4c214
commit
64ac6b18bf
|
@ -498,7 +498,7 @@ void EditCaptionBox::updateEditPreview() {
|
||||||
auto shouldAsDoc = true;
|
auto shouldAsDoc = true;
|
||||||
auto docPhotoSize = QSize();
|
auto docPhotoSize = QSize();
|
||||||
if (const auto image = std::get_if<Info::Image>(fileMedia)) {
|
if (const auto image = std::get_if<Info::Image>(fileMedia)) {
|
||||||
shouldAsDoc = !Storage::ValidateThumbDimensions(
|
shouldAsDoc = !Ui::ValidateThumbDimensions(
|
||||||
image->data.width(),
|
image->data.width(),
|
||||||
image->data.height());
|
image->data.height());
|
||||||
if (shouldAsDoc) {
|
if (shouldAsDoc) {
|
||||||
|
@ -694,7 +694,7 @@ bool EditCaptionBox::fileFromClipboard(not_null<const QMimeData*> data) {
|
||||||
using Info = Ui::PreparedFileInformation;
|
using Info = Ui::PreparedFileInformation;
|
||||||
const auto fileMedia = &file->information->media;
|
const auto fileMedia = &file->information->media;
|
||||||
if (const auto image = std::get_if<Info::Image>(fileMedia)) {
|
if (const auto image = std::get_if<Info::Image>(fileMedia)) {
|
||||||
return !Storage::ValidateThumbDimensions(
|
return !Ui::ValidateThumbDimensions(
|
||||||
image->data.width(),
|
image->data.width(),
|
||||||
image->data.height());
|
image->data.height());
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/chat/attach/attach_prepare.h"
|
#include "ui/chat/attach/attach_prepare.h"
|
||||||
#include "ui/chat/attach/attach_album_preview.h"
|
#include "ui/chat/attach/attach_album_preview.h"
|
||||||
#include "ui/chat/attach/attach_single_file_preview.h"
|
#include "ui/chat/attach/attach_single_file_preview.h"
|
||||||
|
#include "ui/chat/attach/attach_single_media_preview.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/grouped_layout.h"
|
#include "ui/grouped_layout.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
|
@ -56,8 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMinPreviewWidth = 20;
|
|
||||||
|
|
||||||
using Ui::SendFilesWay;
|
using Ui::SendFilesWay;
|
||||||
|
|
||||||
inline bool CanAddUrls(const QList<QUrl> &urls) {
|
inline bool CanAddUrls(const QList<QUrl> &urls) {
|
||||||
|
@ -95,259 +94,6 @@ void FileDialogCallback(
|
||||||
callback(std::move(*list));
|
callback(std::move(*list));
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleMediaPreview : public Ui::RpWidget {
|
|
||||||
public:
|
|
||||||
static SingleMediaPreview *Create(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> controller,
|
|
||||||
const Ui::PreparedFile &file);
|
|
||||||
|
|
||||||
SingleMediaPreview(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> controller,
|
|
||||||
QImage preview,
|
|
||||||
bool animated,
|
|
||||||
bool sticker,
|
|
||||||
const QString &animatedPreviewPath);
|
|
||||||
|
|
||||||
bool canSendAsPhoto() const {
|
|
||||||
return _canSendAsPhoto;
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<int> desiredHeightValue() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void preparePreview(
|
|
||||||
QImage preview,
|
|
||||||
const QString &animatedPreviewPath);
|
|
||||||
void prepareAnimatedPreview(const QString &animatedPreviewPath);
|
|
||||||
void clipCallback(Media::Clip::Notification notification);
|
|
||||||
|
|
||||||
not_null<Window::SessionController*> _controller;
|
|
||||||
bool _animated = false;
|
|
||||||
bool _sticker = false;
|
|
||||||
bool _canSendAsPhoto = false;
|
|
||||||
QPixmap _preview;
|
|
||||||
int _previewLeft = 0;
|
|
||||||
int _previewWidth = 0;
|
|
||||||
int _previewHeight = 0;
|
|
||||||
Media::Clip::ReaderPointer _gifPreview;
|
|
||||||
std::unique_ptr<Lottie::SinglePlayer> _lottiePreview;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
SingleMediaPreview *SingleMediaPreview::Create(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> controller,
|
|
||||||
const Ui::PreparedFile &file) {
|
|
||||||
auto preview = QImage();
|
|
||||||
bool animated = false;
|
|
||||||
bool animationPreview = false;
|
|
||||||
if (const auto image = std::get_if<Ui::PreparedFileInformation::Image>(
|
|
||||||
&file.information->media)) {
|
|
||||||
preview = image->data;
|
|
||||||
animated = animationPreview = image->animated;
|
|
||||||
} else if (const auto video = std::get_if<Ui::PreparedFileInformation::Video>(
|
|
||||||
&file.information->media)) {
|
|
||||||
preview = video->thumbnail;
|
|
||||||
animated = true;
|
|
||||||
animationPreview = video->isGifv;
|
|
||||||
}
|
|
||||||
if (preview.isNull()) {
|
|
||||||
return nullptr;
|
|
||||||
} else if (!animated && !Storage::ValidateThumbDimensions(
|
|
||||||
preview.width(),
|
|
||||||
preview.height())) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return Ui::CreateChild<SingleMediaPreview>(
|
|
||||||
parent,
|
|
||||||
controller,
|
|
||||||
preview,
|
|
||||||
animated,
|
|
||||||
Core::IsMimeSticker(file.information->filemime),
|
|
||||||
animationPreview ? file.path : QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleMediaPreview::SingleMediaPreview(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> controller,
|
|
||||||
QImage preview,
|
|
||||||
bool animated,
|
|
||||||
bool sticker,
|
|
||||||
const QString &animatedPreviewPath)
|
|
||||||
: RpWidget(parent)
|
|
||||||
, _controller(controller)
|
|
||||||
, _animated(animated)
|
|
||||||
, _sticker(sticker) {
|
|
||||||
Expects(!preview.isNull());
|
|
||||||
|
|
||||||
_canSendAsPhoto = !_animated
|
|
||||||
&& !_sticker
|
|
||||||
&& Storage::ValidateThumbDimensions(
|
|
||||||
preview.width(),
|
|
||||||
preview.height());
|
|
||||||
|
|
||||||
preparePreview(preview, animatedPreviewPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleMediaPreview::preparePreview(
|
|
||||||
QImage preview,
|
|
||||||
const QString &animatedPreviewPath) {
|
|
||||||
auto maxW = 0;
|
|
||||||
auto maxH = 0;
|
|
||||||
if (_animated && !_sticker) {
|
|
||||||
auto limitW = st::sendMediaPreviewSize;
|
|
||||||
auto limitH = st::confirmMaxHeight;
|
|
||||||
maxW = qMax(preview.width(), 1);
|
|
||||||
maxH = qMax(preview.height(), 1);
|
|
||||||
if (maxW * limitH > maxH * limitW) {
|
|
||||||
if (maxW < limitW) {
|
|
||||||
maxH = maxH * limitW / maxW;
|
|
||||||
maxW = limitW;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (maxH < limitH) {
|
|
||||||
maxW = maxW * limitH / maxH;
|
|
||||||
maxH = limitH;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preview = Images::prepare(
|
|
||||||
preview,
|
|
||||||
maxW * cIntRetinaFactor(),
|
|
||||||
maxH * cIntRetinaFactor(),
|
|
||||||
Images::Option::Smooth | Images::Option::Blurred,
|
|
||||||
maxW,
|
|
||||||
maxH);
|
|
||||||
}
|
|
||||||
auto originalWidth = preview.width();
|
|
||||||
auto originalHeight = preview.height();
|
|
||||||
if (!originalWidth || !originalHeight) {
|
|
||||||
originalWidth = originalHeight = 1;
|
|
||||||
}
|
|
||||||
_previewWidth = st::sendMediaPreviewSize;
|
|
||||||
if (preview.width() < _previewWidth) {
|
|
||||||
_previewWidth = qMax(preview.width(), kMinPreviewWidth);
|
|
||||||
}
|
|
||||||
auto maxthumbh = qMin(qRound(1.5 * _previewWidth), st::confirmMaxHeight);
|
|
||||||
_previewHeight = qRound(originalHeight * float64(_previewWidth) / originalWidth);
|
|
||||||
if (_previewHeight > maxthumbh) {
|
|
||||||
_previewWidth = qRound(_previewWidth * float64(maxthumbh) / _previewHeight);
|
|
||||||
accumulate_max(_previewWidth, kMinPreviewWidth);
|
|
||||||
_previewHeight = maxthumbh;
|
|
||||||
}
|
|
||||||
_previewLeft = (st::boxWideWidth - _previewWidth) / 2;
|
|
||||||
|
|
||||||
preview = std::move(preview).scaled(
|
|
||||||
_previewWidth * cIntRetinaFactor(),
|
|
||||||
_previewHeight * cIntRetinaFactor(),
|
|
||||||
Qt::IgnoreAspectRatio,
|
|
||||||
Qt::SmoothTransformation);
|
|
||||||
preview = Images::prepareOpaque(std::move(preview));
|
|
||||||
_preview = App::pixmapFromImageInPlace(std::move(preview));
|
|
||||||
_preview.setDevicePixelRatio(cRetinaFactor());
|
|
||||||
|
|
||||||
prepareAnimatedPreview(animatedPreviewPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleMediaPreview::prepareAnimatedPreview(
|
|
||||||
const QString &animatedPreviewPath) {
|
|
||||||
if (_sticker && _animated) {
|
|
||||||
const auto box = QSize(_previewWidth, _previewHeight)
|
|
||||||
* cIntRetinaFactor();
|
|
||||||
_lottiePreview = std::make_unique<Lottie::SinglePlayer>(
|
|
||||||
Lottie::ReadContent(QByteArray(), animatedPreviewPath),
|
|
||||||
Lottie::FrameRequest{ box });
|
|
||||||
_lottiePreview->updates(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
update();
|
|
||||||
}, lifetime());
|
|
||||||
} else if (!animatedPreviewPath.isEmpty()) {
|
|
||||||
auto callback = [=](Media::Clip::Notification notification) {
|
|
||||||
clipCallback(notification);
|
|
||||||
};
|
|
||||||
_gifPreview = Media::Clip::MakeReader(
|
|
||||||
animatedPreviewPath,
|
|
||||||
std::move(callback));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleMediaPreview::clipCallback(Media::Clip::Notification notification) {
|
|
||||||
using namespace Media::Clip;
|
|
||||||
switch (notification) {
|
|
||||||
case NotificationReinit: {
|
|
||||||
if (_gifPreview && _gifPreview->state() == State::Error) {
|
|
||||||
_gifPreview.setBad();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
|
|
||||||
auto s = QSize(_previewWidth, _previewHeight);
|
|
||||||
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case NotificationRepaint: {
|
|
||||||
if (_gifPreview && !_gifPreview->currentDisplayed()) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleMediaPreview::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
if (!_sticker) {
|
|
||||||
if (_previewLeft > st::boxPhotoPadding.left()) {
|
|
||||||
p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _previewLeft - st::boxPhotoPadding.left(), _previewHeight, st::confirmBg);
|
|
||||||
}
|
|
||||||
if (_previewLeft + _previewWidth < width() - st::boxPhotoPadding.right()) {
|
|
||||||
p.fillRect(_previewLeft + _previewWidth, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _previewLeft - _previewWidth, _previewHeight, st::confirmBg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_gifPreview && _gifPreview->started()) {
|
|
||||||
auto s = QSize(_previewWidth, _previewHeight);
|
|
||||||
auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
|
|
||||||
auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
|
|
||||||
p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame);
|
|
||||||
} else if (_lottiePreview && _lottiePreview->ready()) {
|
|
||||||
const auto frame = _lottiePreview->frame();
|
|
||||||
const auto size = frame.size() / cIntRetinaFactor();
|
|
||||||
p.drawImage(
|
|
||||||
QRect(
|
|
||||||
_previewLeft + (_previewWidth - size.width()) / 2,
|
|
||||||
st::boxPhotoPadding.top() + (_previewHeight - size.height()) / 2,
|
|
||||||
size.width(),
|
|
||||||
size.height()),
|
|
||||||
frame);
|
|
||||||
_lottiePreview->markFrameShown();
|
|
||||||
} else {
|
|
||||||
p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview);
|
|
||||||
}
|
|
||||||
if (_animated && !_gifPreview && !_lottiePreview) {
|
|
||||||
auto inner = QRect(_previewLeft + (_previewWidth - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_previewHeight - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(st::msgDateImgBg);
|
|
||||||
|
|
||||||
{
|
|
||||||
PainterHighQualityEnabler hq(p);
|
|
||||||
p.drawEllipse(inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto icon = &st::historyFileInPlay;
|
|
||||||
icon->paintInCenter(p, inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<int> SingleMediaPreview::desiredHeightValue() const {
|
|
||||||
return rpl::single(st::boxPhotoPadding.top() + _previewHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
rpl::producer<QString> FieldPlaceholder(
|
rpl::producer<QString> FieldPlaceholder(
|
||||||
const Ui::PreparedList &list,
|
const Ui::PreparedList &list,
|
||||||
SendFilesWay way) {
|
SendFilesWay way) {
|
||||||
|
@ -410,7 +156,11 @@ void SendFilesBox::prepareSingleFilePreview() {
|
||||||
Expects(IsSingleItem(_list));
|
Expects(IsSingleItem(_list));
|
||||||
|
|
||||||
const auto &file = _list.files[0];
|
const auto &file = _list.files[0];
|
||||||
const auto media = SingleMediaPreview::Create(this, _controller, file);
|
const auto controller = _controller;
|
||||||
|
const auto media = Ui::SingleMediaPreview::Create(this, [=] {
|
||||||
|
return controller->isGifPausedAtLeastFor(
|
||||||
|
Window::GifPauseReason::Layer);
|
||||||
|
}, file);
|
||||||
if (media) {
|
if (media) {
|
||||||
if (!media->canSendAsPhoto()) {
|
if (!media->canSendAsPhoto()) {
|
||||||
_compressConfirm = CompressConfirm::None;
|
_compressConfirm = CompressConfirm::None;
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/wrap/fade_wrap.h"
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "ui/chat/attach/attach_prepare.h"
|
||||||
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
|
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
|
@ -41,7 +42,7 @@ std::variant<ReadScanError, QByteArray> ProcessImage(QByteArray &&bytes) {
|
||||||
auto image = App::readImage(base::take(bytes));
|
auto image = App::readImage(base::take(bytes));
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
return ReadScanError::CantReadImage;
|
return ReadScanError::CantReadImage;
|
||||||
} else if (!Storage::ValidateThumbDimensions(image.width(), image.height())) {
|
} else if (!Ui::ValidateThumbDimensions(image.width(), image.height())) {
|
||||||
return ReadScanError::BadImageSize;
|
return ReadScanError::BadImageSize;
|
||||||
}
|
}
|
||||||
if (std::max(image.width(), image.height()) > kMaxDimensions) {
|
if (std::max(image.width(), image.height()) > kMaxDimensions) {
|
||||||
|
|
|
@ -40,7 +40,7 @@ constexpr auto kThumbnailQuality = 87;
|
||||||
constexpr auto kThumbnailSize = 320;
|
constexpr auto kThumbnailSize = 320;
|
||||||
constexpr auto kPhotoUploadPartSize = 32 * 1024;
|
constexpr auto kPhotoUploadPartSize = 32 * 1024;
|
||||||
|
|
||||||
using Storage::ValidateThumbDimensions;
|
using Ui::ValidateThumbDimensions;
|
||||||
|
|
||||||
struct PreparedFileThumbnail {
|
struct PreparedFileThumbnail {
|
||||||
uint64 id = 0;
|
uint64 id = 0;
|
||||||
|
|
|
@ -43,13 +43,13 @@ bool ValidPhotoForAlbum(
|
||||||
}
|
}
|
||||||
const auto width = image.data.width();
|
const auto width = image.data.width();
|
||||||
const auto height = image.data.height();
|
const auto height = image.data.height();
|
||||||
return ValidateThumbDimensions(width, height);
|
return Ui::ValidateThumbDimensions(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) {
|
bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) {
|
||||||
const auto width = video.thumbnail.width();
|
const auto width = video.thumbnail.width();
|
||||||
const auto height = video.thumbnail.height();
|
const auto height = video.thumbnail.height();
|
||||||
return ValidateThumbDimensions(width, height);
|
return Ui::ValidateThumbDimensions(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize PrepareShownDimensions(const QImage &preview) {
|
QSize PrepareShownDimensions(const QImage &preview) {
|
||||||
|
@ -163,13 +163,6 @@ bool ValidateDragData(not_null<const QMimeData*> data, bool isAlbum) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ValidateThumbDimensions(int width, int height) {
|
|
||||||
return (width > 0)
|
|
||||||
&& (height > 0)
|
|
||||||
&& (width < 20 * height)
|
|
||||||
&& (height < 20 * width);
|
|
||||||
}
|
|
||||||
|
|
||||||
MimeDataState ComputeMimeDataState(const QMimeData *data) {
|
MimeDataState ComputeMimeDataState(const QMimeData *data) {
|
||||||
if (!data || data->hasFormat(qsl("application/x-td-forward"))) {
|
if (!data || data->hasFormat(qsl("application/x-td-forward"))) {
|
||||||
return MimeDataState::None;
|
return MimeDataState::None;
|
||||||
|
@ -272,7 +265,7 @@ PreparedList PrepareMediaFromImage(
|
||||||
QByteArray &&content,
|
QByteArray &&content,
|
||||||
int previewWidth) {
|
int previewWidth) {
|
||||||
auto result = Storage::PreparedList();
|
auto result = Storage::PreparedList();
|
||||||
result.allFilesForCompress = ValidateThumbDimensions(
|
result.allFilesForCompress = Ui::ValidateThumbDimensions(
|
||||||
image.width(),
|
image.width(),
|
||||||
image.height());
|
image.height());
|
||||||
auto file = PreparedFile(QString());
|
auto file = PreparedFile(QString());
|
||||||
|
@ -345,7 +338,7 @@ std::optional<PreparedList> PreparedFileFromFilesDialog(
|
||||||
|
|
||||||
const auto media = &file.information->media;
|
const auto media = &file.information->media;
|
||||||
const auto valid = v::match(*media, [](const Info::Image &data) {
|
const auto valid = v::match(*media, [](const Info::Image &data) {
|
||||||
return Storage::ValidateThumbDimensions(
|
return Ui::ValidateThumbDimensions(
|
||||||
data.data.width(),
|
data.data.width(),
|
||||||
data.data.height())
|
data.data.height())
|
||||||
&& !data.animated;
|
&& !data.animated;
|
||||||
|
|
|
@ -32,7 +32,6 @@ std::optional<Ui::PreparedList> PreparedFileFromFilesDialog(
|
||||||
int previewWidth);
|
int previewWidth);
|
||||||
MimeDataState ComputeMimeDataState(const QMimeData *data);
|
MimeDataState ComputeMimeDataState(const QMimeData *data);
|
||||||
bool ValidateDragData(not_null<const QMimeData*> data, bool isAlbum);
|
bool ValidateDragData(not_null<const QMimeData*> data, bool isAlbum);
|
||||||
bool ValidateThumbDimensions(int width, int height);
|
|
||||||
Ui::PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth);
|
Ui::PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth);
|
||||||
Ui::PreparedList PrepareMediaList(const QStringList &files, int previewWidth);
|
Ui::PreparedList PrepareMediaList(const QStringList &files, int previewWidth);
|
||||||
Ui::PreparedList PrepareMediaFromImage(
|
Ui::PreparedList PrepareMediaFromImage(
|
||||||
|
|
|
@ -88,4 +88,11 @@ int MaxAlbumItems() {
|
||||||
return kMaxAlbumCount;
|
return kMaxAlbumCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ValidateThumbDimensions(int width, int height) {
|
||||||
|
return (width > 0)
|
||||||
|
&& (height > 0)
|
||||||
|
&& (width < 20 * height)
|
||||||
|
&& (height < 20 * width);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -81,5 +81,6 @@ struct PreparedList {
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] int MaxAlbumItems();
|
[[nodiscard]] int MaxAlbumItems();
|
||||||
|
[[nodiscard]] bool ValidateThumbDimensions(int width, int height);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
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 "ui/chat/attach/attach_single_media_preview.h"
|
||||||
|
|
||||||
|
#include "ui/chat/attach/attach_prepare.h"
|
||||||
|
#include "core/mime_type.h"
|
||||||
|
#include "lottie/lottie_single_player.h"
|
||||||
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMinPreviewWidth = 20;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SingleMediaPreview *SingleMediaPreview::Create(
|
||||||
|
QWidget *parent,
|
||||||
|
Fn<bool()> gifPaused,
|
||||||
|
const PreparedFile &file) {
|
||||||
|
auto preview = QImage();
|
||||||
|
bool animated = false;
|
||||||
|
bool animationPreview = false;
|
||||||
|
if (const auto image = std::get_if<PreparedFileInformation::Image>(
|
||||||
|
&file.information->media)) {
|
||||||
|
preview = image->data;
|
||||||
|
animated = animationPreview = image->animated;
|
||||||
|
} else if (const auto video = std::get_if<PreparedFileInformation::Video>(
|
||||||
|
&file.information->media)) {
|
||||||
|
preview = video->thumbnail;
|
||||||
|
animated = true;
|
||||||
|
animationPreview = video->isGifv;
|
||||||
|
}
|
||||||
|
if (preview.isNull()) {
|
||||||
|
return nullptr;
|
||||||
|
} else if (!animated && !ValidateThumbDimensions(
|
||||||
|
preview.width(),
|
||||||
|
preview.height())) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return CreateChild<SingleMediaPreview>(
|
||||||
|
parent,
|
||||||
|
std::move(gifPaused),
|
||||||
|
preview,
|
||||||
|
animated,
|
||||||
|
Core::IsMimeSticker(file.information->filemime),
|
||||||
|
animationPreview ? file.path : QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleMediaPreview::SingleMediaPreview(
|
||||||
|
QWidget *parent,
|
||||||
|
Fn<bool()> gifPaused,
|
||||||
|
QImage preview,
|
||||||
|
bool animated,
|
||||||
|
bool sticker,
|
||||||
|
const QString &animatedPreviewPath)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _gifPaused(std::move(gifPaused))
|
||||||
|
, _animated(animated)
|
||||||
|
, _sticker(sticker) {
|
||||||
|
Expects(!preview.isNull());
|
||||||
|
|
||||||
|
_canSendAsPhoto = !_animated
|
||||||
|
&& !_sticker
|
||||||
|
&& ValidateThumbDimensions(
|
||||||
|
preview.width(),
|
||||||
|
preview.height());
|
||||||
|
|
||||||
|
preparePreview(preview, animatedPreviewPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleMediaPreview::~SingleMediaPreview() = default;
|
||||||
|
|
||||||
|
void SingleMediaPreview::preparePreview(
|
||||||
|
QImage preview,
|
||||||
|
const QString &animatedPreviewPath) {
|
||||||
|
auto maxW = 0;
|
||||||
|
auto maxH = 0;
|
||||||
|
if (_animated && !_sticker) {
|
||||||
|
auto limitW = st::sendMediaPreviewSize;
|
||||||
|
auto limitH = st::confirmMaxHeight;
|
||||||
|
maxW = qMax(preview.width(), 1);
|
||||||
|
maxH = qMax(preview.height(), 1);
|
||||||
|
if (maxW * limitH > maxH * limitW) {
|
||||||
|
if (maxW < limitW) {
|
||||||
|
maxH = maxH * limitW / maxW;
|
||||||
|
maxW = limitW;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (maxH < limitH) {
|
||||||
|
maxW = maxW * limitH / maxH;
|
||||||
|
maxH = limitH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preview = Images::prepare(
|
||||||
|
preview,
|
||||||
|
maxW * style::DevicePixelRatio(),
|
||||||
|
maxH * style::DevicePixelRatio(),
|
||||||
|
Images::Option::Smooth | Images::Option::Blurred,
|
||||||
|
maxW,
|
||||||
|
maxH);
|
||||||
|
}
|
||||||
|
auto originalWidth = preview.width();
|
||||||
|
auto originalHeight = preview.height();
|
||||||
|
if (!originalWidth || !originalHeight) {
|
||||||
|
originalWidth = originalHeight = 1;
|
||||||
|
}
|
||||||
|
_previewWidth = st::sendMediaPreviewSize;
|
||||||
|
if (preview.width() < _previewWidth) {
|
||||||
|
_previewWidth = qMax(preview.width(), kMinPreviewWidth);
|
||||||
|
}
|
||||||
|
auto maxthumbh = qMin(qRound(1.5 * _previewWidth), st::confirmMaxHeight);
|
||||||
|
_previewHeight = qRound(originalHeight * float64(_previewWidth) / originalWidth);
|
||||||
|
if (_previewHeight > maxthumbh) {
|
||||||
|
_previewWidth = qRound(_previewWidth * float64(maxthumbh) / _previewHeight);
|
||||||
|
accumulate_max(_previewWidth, kMinPreviewWidth);
|
||||||
|
_previewHeight = maxthumbh;
|
||||||
|
}
|
||||||
|
_previewLeft = (st::boxWideWidth - _previewWidth) / 2;
|
||||||
|
|
||||||
|
preview = std::move(preview).scaled(
|
||||||
|
_previewWidth * style::DevicePixelRatio(),
|
||||||
|
_previewHeight * style::DevicePixelRatio(),
|
||||||
|
Qt::IgnoreAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
preview = Images::prepareOpaque(std::move(preview));
|
||||||
|
_preview = PixmapFromImage(std::move(preview));
|
||||||
|
_preview.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
|
||||||
|
prepareAnimatedPreview(animatedPreviewPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleMediaPreview::prepareAnimatedPreview(
|
||||||
|
const QString &animatedPreviewPath) {
|
||||||
|
if (_sticker && _animated) {
|
||||||
|
const auto box = QSize(_previewWidth, _previewHeight)
|
||||||
|
* style::DevicePixelRatio();
|
||||||
|
_lottiePreview = std::make_unique<Lottie::SinglePlayer>(
|
||||||
|
Lottie::ReadContent(QByteArray(), animatedPreviewPath),
|
||||||
|
Lottie::FrameRequest{ box });
|
||||||
|
_lottiePreview->updates(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
update();
|
||||||
|
}, lifetime());
|
||||||
|
} else if (!animatedPreviewPath.isEmpty()) {
|
||||||
|
auto callback = [=](Media::Clip::Notification notification) {
|
||||||
|
clipCallback(notification);
|
||||||
|
};
|
||||||
|
_gifPreview = Media::Clip::MakeReader(
|
||||||
|
animatedPreviewPath,
|
||||||
|
std::move(callback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleMediaPreview::clipCallback(Media::Clip::Notification notification) {
|
||||||
|
using namespace Media::Clip;
|
||||||
|
switch (notification) {
|
||||||
|
case NotificationReinit: {
|
||||||
|
if (_gifPreview && _gifPreview->state() == State::Error) {
|
||||||
|
_gifPreview.setBad();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
|
||||||
|
auto s = QSize(_previewWidth, _previewHeight);
|
||||||
|
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case NotificationRepaint: {
|
||||||
|
if (_gifPreview && !_gifPreview->currentDisplayed()) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SingleMediaPreview::paintEvent(QPaintEvent *e) {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
if (!_sticker) {
|
||||||
|
if (_previewLeft > st::boxPhotoPadding.left()) {
|
||||||
|
p.fillRect(st::boxPhotoPadding.left(), st::boxPhotoPadding.top(), _previewLeft - st::boxPhotoPadding.left(), _previewHeight, st::confirmBg);
|
||||||
|
}
|
||||||
|
if (_previewLeft + _previewWidth < width() - st::boxPhotoPadding.right()) {
|
||||||
|
p.fillRect(_previewLeft + _previewWidth, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _previewLeft - _previewWidth, _previewHeight, st::confirmBg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_gifPreview && _gifPreview->started()) {
|
||||||
|
auto s = QSize(_previewWidth, _previewHeight);
|
||||||
|
auto paused = _gifPaused();
|
||||||
|
auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
|
||||||
|
p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame);
|
||||||
|
} else if (_lottiePreview && _lottiePreview->ready()) {
|
||||||
|
const auto frame = _lottiePreview->frame();
|
||||||
|
const auto size = frame.size() / style::DevicePixelRatio();
|
||||||
|
p.drawImage(
|
||||||
|
QRect(
|
||||||
|
_previewLeft + (_previewWidth - size.width()) / 2,
|
||||||
|
st::boxPhotoPadding.top() + (_previewHeight - size.height()) / 2,
|
||||||
|
size.width(),
|
||||||
|
size.height()),
|
||||||
|
frame);
|
||||||
|
_lottiePreview->markFrameShown();
|
||||||
|
} else {
|
||||||
|
p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview);
|
||||||
|
}
|
||||||
|
if (_animated && !_gifPreview && !_lottiePreview) {
|
||||||
|
auto inner = QRect(_previewLeft + (_previewWidth - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_previewHeight - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(st::msgDateImgBg);
|
||||||
|
|
||||||
|
{
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.drawEllipse(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto icon = &st::historyFileInPlay;
|
||||||
|
icon->paintInCenter(p, inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> SingleMediaPreview::desiredHeightValue() const {
|
||||||
|
return rpl::single(st::boxPhotoPadding.top() + _previewHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
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 "ui/rp_widget.h"
|
||||||
|
#include "media/clip/media_clip_reader.h"
|
||||||
|
|
||||||
|
namespace Lottie {
|
||||||
|
class SinglePlayer;
|
||||||
|
} // namespace Lottie
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
struct PreparedFile;
|
||||||
|
|
||||||
|
class SingleMediaPreview : public RpWidget {
|
||||||
|
public:
|
||||||
|
static SingleMediaPreview *Create(
|
||||||
|
QWidget *parent,
|
||||||
|
Fn<bool()> gifPaused,
|
||||||
|
const PreparedFile &file);
|
||||||
|
|
||||||
|
SingleMediaPreview(
|
||||||
|
QWidget *parent,
|
||||||
|
Fn<bool()> gifPaused,
|
||||||
|
QImage preview,
|
||||||
|
bool animated,
|
||||||
|
bool sticker,
|
||||||
|
const QString &animatedPreviewPath);
|
||||||
|
~SingleMediaPreview();
|
||||||
|
|
||||||
|
bool canSendAsPhoto() const {
|
||||||
|
return _canSendAsPhoto;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> desiredHeightValue() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void preparePreview(
|
||||||
|
QImage preview,
|
||||||
|
const QString &animatedPreviewPath);
|
||||||
|
void prepareAnimatedPreview(const QString &animatedPreviewPath);
|
||||||
|
void clipCallback(Media::Clip::Notification notification);
|
||||||
|
|
||||||
|
Fn<bool()> _gifPaused;
|
||||||
|
bool _animated = false;
|
||||||
|
bool _sticker = false;
|
||||||
|
bool _canSendAsPhoto = false;
|
||||||
|
QPixmap _preview;
|
||||||
|
int _previewLeft = 0;
|
||||||
|
int _previewWidth = 0;
|
||||||
|
int _previewHeight = 0;
|
||||||
|
Media::Clip::ReaderPointer _gifPreview;
|
||||||
|
std::unique_ptr<Lottie::SinglePlayer> _lottiePreview;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -75,6 +75,8 @@ PRIVATE
|
||||||
ui/chat/attach/attach_prepare.h
|
ui/chat/attach/attach_prepare.h
|
||||||
ui/chat/attach/attach_single_file_preview.cpp
|
ui/chat/attach/attach_single_file_preview.cpp
|
||||||
ui/chat/attach/attach_single_file_preview.h
|
ui/chat/attach/attach_single_file_preview.h
|
||||||
|
ui/chat/attach/attach_single_media_preview.cpp
|
||||||
|
ui/chat/attach/attach_single_media_preview.h
|
||||||
ui/chat/message_bar.cpp
|
ui/chat/message_bar.cpp
|
||||||
ui/chat/message_bar.h
|
ui/chat/message_bar.h
|
||||||
ui/chat/pinned_bar.cpp
|
ui/chat/pinned_bar.cpp
|
||||||
|
@ -107,4 +109,5 @@ PUBLIC
|
||||||
tdesktop::td_lang
|
tdesktop::td_lang
|
||||||
desktop-app::lib_ui
|
desktop-app::lib_ui
|
||||||
desktop-app::lib_ffmpeg
|
desktop-app::lib_ffmpeg
|
||||||
|
desktop-app::lib_lottie
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue