mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-24 09:16:57 +00:00
Generate mini-previews for photos and files.
This commit is contained in:
parent
8c21fad642
commit
792b9090a7
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "history/view/media/history_view_slot_machine.h"
|
||||
#include "history/view/media/history_view_dice.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/text/format_values.h"
|
||||
@ -37,18 +38,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_auto_download.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
@ -134,6 +140,130 @@ using ItemPreview = HistoryView::ItemPreview;
|
||||
caption);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PreparePreviewImage(
|
||||
not_null<const Image*> image,
|
||||
ImageRoundRadius radius = ImageRoundRadius::Small) {
|
||||
const auto original = image->original();
|
||||
if (original.width() * 10 < original.height()
|
||||
|| original.height() * 10 < original.width()) {
|
||||
return QImage();
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::dialogsMiniPreview * factor;
|
||||
const auto scaled = original.scaled(
|
||||
QSize(size, size),
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation);
|
||||
auto square = scaled.copy(
|
||||
(scaled.width() - size) / 2,
|
||||
(scaled.height() - size) / 2,
|
||||
size,
|
||||
size
|
||||
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
Images::prepareRound(square, radius);
|
||||
square.setDevicePixelRatio(factor);
|
||||
return square;
|
||||
}
|
||||
|
||||
struct PreparedPreview {
|
||||
QImage preview;
|
||||
bool loading = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !preview.isNull() || loading;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] PreparedPreview PreparePhotoPreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<PhotoMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
if (const auto small = media->image(PhotoSize::Small)) {
|
||||
return { PreparePreviewImage(small, radius) };
|
||||
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
|
||||
return { PreparePreviewImage(thumbnail, radius) };
|
||||
} else if (const auto large = media->image(PhotoSize::Large)) {
|
||||
return { PreparePreviewImage(large, radius) };
|
||||
}
|
||||
const auto allowedToDownload = [&] {
|
||||
const auto photo = media->owner();
|
||||
if (media->loaded() || photo->cancelled()) {
|
||||
return false;
|
||||
}
|
||||
return photo->hasExact(PhotoSize::Small)
|
||||
|| photo->hasExact(PhotoSize::Thumbnail)
|
||||
|| AutoDownload::Should(
|
||||
photo->session().settings().autoDownload(),
|
||||
item->history()->peer,
|
||||
photo);
|
||||
}();
|
||||
if (allowedToDownload) {
|
||||
media->owner()->load(PhotoSize::Small, item->fullId());
|
||||
}
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), allowedToDownload };
|
||||
}
|
||||
return { QImage(), allowedToDownload };
|
||||
}
|
||||
|
||||
[[nodiscard]] PreparedPreview PrepareFilePreviewImage(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
Expects(media->owner()->hasThumbnail());
|
||||
|
||||
if (const auto thumbnail = media->thumbnail()) {
|
||||
return { PreparePreviewImage(thumbnail, radius) };
|
||||
}
|
||||
media->owner()->loadThumbnail(item->fullId());
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), true };
|
||||
}
|
||||
return { QImage(), true };
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
|
||||
Expects(!preview.isNull());
|
||||
|
||||
{
|
||||
QPainter p(&preview);
|
||||
//st::overviewVideoPlay.paintInCenter(
|
||||
// p,
|
||||
// QRect(QPoint(), preview.size() / preview.devicePixelRatio()));
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
[[nodiscard]] PreparedPreview PrepareFilePreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
if (auto result = PrepareFilePreviewImage(item, media, radius)) {
|
||||
const auto document = media->owner();
|
||||
if (!result.preview.isNull()
|
||||
&& (document->isVideoFile() || document->isVideoMessage())) {
|
||||
result.preview = PutPlayIcon(std::move(result.preview));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Expects(media->owner()->hasThumbnail());
|
||||
|
||||
if (const auto thumbnail = media->thumbnail()) {
|
||||
return { PreparePreviewImage(thumbnail, radius) };
|
||||
}
|
||||
media->owner()->loadThumbnail(item->fullId());
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), true };
|
||||
}
|
||||
return { QImage(), true };
|
||||
}
|
||||
|
||||
[[nodiscard]] bool TryFilePreview(not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail()
|
||||
&& !document->sticker()
|
||||
&& !document->isAudioFile();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextForMimeData WithCaptionClipboardText(
|
||||
@ -348,11 +478,25 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
// #TODO minis generate images and support albums
|
||||
auto images = std::vector<QImage>();
|
||||
// #TODO minis support albums
|
||||
const auto media = _photo->createMediaView();
|
||||
const auto radius = _chat
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
auto context = std::any();
|
||||
if (auto prepared = PreparePhotoPreview(parent(), media, radius)) {
|
||||
images.push_back(std::move(prepared.preview));
|
||||
if (prepared.loading) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
return {
|
||||
.text = WithCaptionDialogsText(
|
||||
tr::lng_in_dlg_photo(tr::now),
|
||||
caption)
|
||||
caption),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
@ -544,9 +688,27 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
// #TODO minis generate images and support albums
|
||||
// #TODO minis support albums
|
||||
auto images = std::vector<QImage>();
|
||||
const auto media = TryFilePreview(_document)
|
||||
? _document->createMediaView()
|
||||
: nullptr;
|
||||
const auto radius = _document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
auto context = std::any();
|
||||
if (media) {
|
||||
if (auto prepared = PrepareFilePreview(parent(), media, radius)) {
|
||||
images.push_back(std::move(prepared.preview));
|
||||
if (prepared.loading) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -296,6 +296,6 @@ dialogsScamFont: font(9px semibold);
|
||||
dialogsScamSkip: 4px;
|
||||
dialogsScamRadius: 2px;
|
||||
|
||||
dialogsMiniPreview: 32px;
|
||||
dialogsMiniPreviewSkip: 4px;
|
||||
dialogsMiniPreviewRight: 6px;
|
||||
dialogsMiniPreview: 16px;
|
||||
dialogsMiniPreviewSkip: 2px;
|
||||
dialogsMiniPreviewRight: 3px;
|
||||
|
@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Dialogs::Ui {
|
||||
@ -16,6 +19,11 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
struct MessageView::LoadingContext {
|
||||
std::any context;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
MessageView::MessageView()
|
||||
: _textCache(st::dialogsTextWidthMin) {
|
||||
}
|
||||
@ -43,12 +51,41 @@ void MessageView::paint(
|
||||
return;
|
||||
}
|
||||
if (_textCachedFor != item.get()) {
|
||||
const auto preview = item->toPreview(options);
|
||||
auto preview = item->toPreview(options);
|
||||
_textCache.setText(
|
||||
st::dialogsTextStyle,
|
||||
preview.text,
|
||||
DialogTextOptions());
|
||||
_textCachedFor = item;
|
||||
_imagesCache = std::move(preview.images);
|
||||
if (preview.loadingContext.has_value()) {
|
||||
if (!_loadingContext) {
|
||||
_loadingContext = std::make_unique<LoadingContext>();
|
||||
item->history()->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
_textCachedFor = nullptr;
|
||||
}, _loadingContext->lifetime);
|
||||
}
|
||||
_loadingContext->context = std::move(preview.loadingContext);
|
||||
} else {
|
||||
_loadingContext = nullptr;
|
||||
}
|
||||
}
|
||||
auto rect = geometry;
|
||||
for (const auto &image : _imagesCache) {
|
||||
if (rect.width() < st::dialogsMiniPreview) {
|
||||
break;
|
||||
}
|
||||
p.drawImage(rect.topLeft(), image);
|
||||
rect.setLeft(rect.x()
|
||||
+ st::dialogsMiniPreview
|
||||
+ st::dialogsMiniPreviewSkip);
|
||||
}
|
||||
if (!_imagesCache.empty()) {
|
||||
rect.setLeft(rect.x() + st::dialogsMiniPreviewRight);
|
||||
}
|
||||
if (rect.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
p.setTextPalette(active
|
||||
? st::dialogsTextPaletteActive
|
||||
@ -59,10 +96,10 @@ void MessageView::paint(
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
_textCache.drawElided(
|
||||
p,
|
||||
geometry.left(),
|
||||
geometry.top(),
|
||||
geometry.width(),
|
||||
geometry.height() / st::dialogsTextFont->height);
|
||||
rect.left(),
|
||||
rect.top(),
|
||||
rect.width(),
|
||||
rect.height() / st::dialogsTextFont->height);
|
||||
p.restoreTextPalette();
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
|
||||
class Image;
|
||||
class HistoryItem;
|
||||
enum class ImageRoundRadius;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
@ -42,9 +45,12 @@ public:
|
||||
ToPreviewOptions options) const;
|
||||
|
||||
private:
|
||||
struct LoadingContext;
|
||||
|
||||
mutable const HistoryItem *_textCachedFor = nullptr;
|
||||
mutable Ui::Text::String _textCache;
|
||||
mutable std::vector<QImage> _imagesCache;
|
||||
mutable std::unique_ptr<LoadingContext> _loadingContext;
|
||||
|
||||
};
|
||||
|
||||
|
@ -926,7 +926,7 @@ bool HistoryItem::isEmpty() const {
|
||||
|
||||
QString HistoryItem::notificationText() const {
|
||||
const auto result = [&] {
|
||||
if (_media) {
|
||||
if (_media && !serviceMsg()) {
|
||||
return _media->notificationText();
|
||||
} else if (!emptyText()) {
|
||||
return _text.toString();
|
||||
|
@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/value_ordering.h"
|
||||
#include "data/data_media_types.h"
|
||||
|
||||
#include <any>
|
||||
|
||||
enum class UnreadMentionType;
|
||||
struct HistoryMessageReplyMarkup;
|
||||
class ReplyKeyboard;
|
||||
@ -63,6 +65,7 @@ struct ToPreviewOptions {
|
||||
struct ItemPreview {
|
||||
QString text;
|
||||
std::vector<QImage> images;
|
||||
std::any loadingContext;
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
|
@ -926,9 +926,13 @@ bool HistoryService::needCheck() const {
|
||||
}
|
||||
|
||||
ItemPreview HistoryService::toPreview(ToPreviewOptions options) const {
|
||||
// #TODO minis generate images
|
||||
// Don't show for service messages (chat photo changed).
|
||||
// Because larger version is shown exactly to the left of the preview.
|
||||
//auto media = _media ? _media->toPreview(options) : ItemPreview();
|
||||
return {
|
||||
.text = textcmdLink(1, TextUtilities::Clean(notificationText())),
|
||||
//.images = std::move(media.images),
|
||||
//.loadingContext = std::move(media.loadingContext),
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user