347 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 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 "window/window_media_preview.h"
 | |
| 
 | |
| #include "data/data_photo.h"
 | |
| #include "data/data_document.h"
 | |
| #include "ui/image/image.h"
 | |
| #include "ui/emoji_config.h"
 | |
| #include "lottie/lottie_single_player.h"
 | |
| #include "main/main_session.h"
 | |
| #include "chat_helpers/stickers.h"
 | |
| #include "window/window_session_controller.h"
 | |
| #include "styles/style_layers.h"
 | |
| #include "styles/style_chat_helpers.h"
 | |
| #include "styles/style_history.h"
 | |
| 
 | |
| namespace Window {
 | |
| namespace {
 | |
| 
 | |
| constexpr int kStickerPreviewEmojiLimit = 10;
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| MediaPreviewWidget::MediaPreviewWidget(
 | |
| 	QWidget *parent,
 | |
| 	not_null<Window::SessionController*> controller)
 | |
| : RpWidget(parent)
 | |
| , _controller(controller)
 | |
| , _emojiSize(Ui::Emoji::GetSizeLarge() / cIntRetinaFactor()) {
 | |
| 	setAttribute(Qt::WA_TransparentForMouseEvents);
 | |
| 	subscribe(_controller->session().downloaderTaskFinished(), [=] {
 | |
| 		update();
 | |
| 	});
 | |
| }
 | |
| 
 | |
| QRect MediaPreviewWidget::updateArea() const {
 | |
| 	const auto size = currentDimensions();
 | |
| 	return QRect(
 | |
| 		QPoint((width() - size.width()) / 2, (height() - size.height()) / 2),
 | |
| 		size);
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::paintEvent(QPaintEvent *e) {
 | |
| 	Painter p(this);
 | |
| 	QRect r(e->rect());
 | |
| 
 | |
| 	const auto image = [&] {
 | |
| 		if (!_lottie || !_lottie->ready()) {
 | |
| 			return QImage();
 | |
| 		}
 | |
| 		_lottie->markFrameShown();
 | |
| 		return _lottie->frame();
 | |
| 	}();
 | |
| 	const auto pixmap = image.isNull() ? currentImage() : QPixmap();
 | |
| 	const auto size = image.isNull() ? pixmap.size() : image.size();
 | |
| 	int w = size.width() / cIntRetinaFactor(), h = size.height() / cIntRetinaFactor();
 | |
| 	auto shown = _a_shown.value(_hiding ? 0. : 1.);
 | |
| 	if (!_a_shown.animating()) {
 | |
| 		if (_hiding) {
 | |
| 			hide();
 | |
| 			_controller->disableGifPauseReason(Window::GifPauseReason::MediaPreview);
 | |
| 			return;
 | |
| 		}
 | |
| 	} else {
 | |
| 		p.setOpacity(shown);
 | |
| //		w = qMax(qRound(w * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(w % 2), 1);
 | |
| //		h = qMax(qRound(h * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(h % 2), 1);
 | |
| 	}
 | |
| 	p.fillRect(r, st::stickerPreviewBg);
 | |
| 	if (image.isNull()) {
 | |
| 		p.drawPixmap((width() - w) / 2, (height() - h) / 2, pixmap);
 | |
| 	} else {
 | |
| 		p.drawImage(
 | |
| 			QRect((width() - w) / 2, (height() - h) / 2, w, h),
 | |
| 			image);
 | |
| 	}
 | |
| 	if (!_emojiList.empty()) {
 | |
| 		const auto emojiCount = _emojiList.size();
 | |
| 		const auto emojiWidth = (emojiCount * _emojiSize) + (emojiCount - 1) * st::stickerEmojiSkip;
 | |
| 		auto emojiLeft = (width() - emojiWidth) / 2;
 | |
| 		const auto esize = Ui::Emoji::GetSizeLarge();
 | |
| 		for (const auto emoji : _emojiList) {
 | |
| 			Ui::Emoji::Draw(
 | |
| 				p,
 | |
| 				emoji,
 | |
| 				esize,
 | |
| 				emojiLeft,
 | |
| 				(height() - h) / 2 - (_emojiSize * 2));
 | |
| 			emojiLeft += _emojiSize + st::stickerEmojiSkip;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::resizeEvent(QResizeEvent *e) {
 | |
| 	update();
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::showPreview(
 | |
| 		Data::FileOrigin origin,
 | |
| 		not_null<DocumentData*> document) {
 | |
| 	if (!document
 | |
| 		|| (!document->isAnimation() && !document->sticker())
 | |
| 		|| document->isVideoMessage()) {
 | |
| 		hidePreview();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	startShow();
 | |
| 	_origin = origin;
 | |
| 	_photo = nullptr;
 | |
| 	_document = document;
 | |
| 	fillEmojiString();
 | |
| 	resetGifAndCache();
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::showPreview(
 | |
| 		Data::FileOrigin origin,
 | |
| 		not_null<PhotoData*> photo) {
 | |
| 	startShow();
 | |
| 	_origin = origin;
 | |
| 	_photo = photo;
 | |
| 	_document = nullptr;
 | |
| 	fillEmojiString();
 | |
| 	resetGifAndCache();
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::startShow() {
 | |
| 	_cache = QPixmap();
 | |
| 	if (isHidden() || _a_shown.animating()) {
 | |
| 		if (isHidden()) {
 | |
| 			show();
 | |
| 			_controller->enableGifPauseReason(Window::GifPauseReason::MediaPreview);
 | |
| 		}
 | |
| 		_hiding = false;
 | |
| 		_a_shown.start([this] { update(); }, 0., 1., st::stickerPreviewDuration);
 | |
| 	} else {
 | |
| 		update();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::hidePreview() {
 | |
| 	if (isHidden()) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (_gif) _cache = currentImage();
 | |
| 	_hiding = true;
 | |
| 	_a_shown.start([this] { update(); }, 1., 0., st::stickerPreviewDuration);
 | |
| 	_photo = nullptr;
 | |
| 	_document = nullptr;
 | |
| 	resetGifAndCache();
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::fillEmojiString() {
 | |
| 	_emojiList.clear();
 | |
| 	if (_photo) {
 | |
| 		return;
 | |
| 	}
 | |
| 	if (auto sticker = _document->sticker()) {
 | |
| 		if (auto list = Stickers::GetEmojiListFromSet(_document)) {
 | |
| 			_emojiList = std::move(*list);
 | |
| 			while (_emojiList.size() > kStickerPreviewEmojiLimit) {
 | |
| 				_emojiList.pop_back();
 | |
| 			}
 | |
| 		} else if (const auto emoji = Ui::Emoji::Find(sticker->alt)) {
 | |
| 			_emojiList.emplace_back(emoji);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::resetGifAndCache() {
 | |
| 	_lottie = nullptr;
 | |
| 	_gif.reset();
 | |
| 	_cacheStatus = CacheNotLoaded;
 | |
| 	_cachedSize = QSize();
 | |
| }
 | |
| 
 | |
| QSize MediaPreviewWidget::currentDimensions() const {
 | |
| 	if (!_cachedSize.isEmpty()) {
 | |
| 		return _cachedSize;
 | |
| 	}
 | |
| 	if (!_document && !_photo) {
 | |
| 		_cachedSize = QSize(_cache.width() / cIntRetinaFactor(), _cache.height() / cIntRetinaFactor());
 | |
| 		return _cachedSize;
 | |
| 	}
 | |
| 
 | |
| 	QSize result, box;
 | |
| 	if (_photo) {
 | |
| 		result = QSize(_photo->width(), _photo->height());
 | |
| 		box = QSize(width() - 2 * st::boxVerticalMargin, height() - 2 * st::boxVerticalMargin);
 | |
| 	} else {
 | |
| 		result = _document->dimensions;
 | |
| 		if (_gif && _gif->ready()) {
 | |
| 			result = QSize(_gif->width(), _gif->height());
 | |
| 		}
 | |
| 		if (_document->sticker()) {
 | |
| 			box = QSize(st::maxStickerSize, st::maxStickerSize);
 | |
| 		} else {
 | |
| 			box = QSize(2 * st::maxStickerSize, 2 * st::maxStickerSize);
 | |
| 		}
 | |
| 	}
 | |
| 	result = QSize(qMax(style::ConvertScale(result.width()), 1), qMax(style::ConvertScale(result.height()), 1));
 | |
| 	if (result.width() > box.width()) {
 | |
| 		result.setHeight(qMax((box.width() * result.height()) / result.width(), 1));
 | |
| 		result.setWidth(box.width());
 | |
| 	}
 | |
| 	if (result.height() > box.height()) {
 | |
| 		result.setWidth(qMax((box.height() * result.width()) / result.height(), 1));
 | |
| 		result.setHeight(box.height());
 | |
| 	}
 | |
| 	if (_photo) {
 | |
| 		_cachedSize = result;
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::setupLottie() {
 | |
| 	Expects(_document != nullptr);
 | |
| 
 | |
| 	_lottie = std::make_unique<Lottie::SinglePlayer>(
 | |
| 		Lottie::ReadContent(_document->data(), _document->filepath()),
 | |
| 		Lottie::FrameRequest{ currentDimensions() * cIntRetinaFactor() },
 | |
| 		Lottie::Quality::High);
 | |
| 
 | |
| 	_lottie->updates(
 | |
| 	) | rpl::start_with_next([=](Lottie::Update update) {
 | |
| 		update.data.match([&](const Lottie::Information &) {
 | |
| 			this->update();
 | |
| 		}, [&](const Lottie::DisplayFrameRequest &) {
 | |
| 			this->update(updateArea());
 | |
| 		});
 | |
| 	}, lifetime());
 | |
| }
 | |
| 
 | |
| QPixmap MediaPreviewWidget::currentImage() const {
 | |
| 	if (_document) {
 | |
| 		if (const auto sticker = _document->sticker()) {
 | |
| 			if (_cacheStatus != CacheLoaded) {
 | |
| 				if (sticker->animated && !_lottie && _document->loaded()) {
 | |
| 					const_cast<MediaPreviewWidget*>(this)->setupLottie();
 | |
| 				}
 | |
| 				if (_lottie && _lottie->ready()) {
 | |
| 					return QPixmap();
 | |
| 				} else if (const auto image = _document->getStickerLarge()) {
 | |
| 					QSize s = currentDimensions();
 | |
| 					_cache = image->pix(_origin, s.width(), s.height());
 | |
| 					_cacheStatus = CacheLoaded;
 | |
| 				} else if (_cacheStatus != CacheThumbLoaded
 | |
| 					&& _document->hasThumbnail()
 | |
| 					&& _document->thumbnail()->loaded()) {
 | |
| 					QSize s = currentDimensions();
 | |
| 					_cache = _document->thumbnail()->pixBlurred(_origin, s.width(), s.height());
 | |
| 					_cacheStatus = CacheThumbLoaded;
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			_document->automaticLoad(_origin, nullptr);
 | |
| 			if (_document->loaded()) {
 | |
| 				if (!_gif && !_gif.isBad()) {
 | |
| 					auto that = const_cast<MediaPreviewWidget*>(this);
 | |
| 					that->_gif = Media::Clip::MakeReader(_document, FullMsgId(), [=](Media::Clip::Notification notification) {
 | |
| 						that->clipCallback(notification);
 | |
| 					});
 | |
| 					if (_gif) _gif->setAutoplay();
 | |
| 				}
 | |
| 			}
 | |
| 			if (_gif && _gif->started()) {
 | |
| 				auto s = currentDimensions();
 | |
| 				auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::MediaPreview);
 | |
| 				return _gif->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
 | |
| 			}
 | |
| 			if (_cacheStatus != CacheThumbLoaded
 | |
| 				&& _document->hasThumbnail()) {
 | |
| 				QSize s = currentDimensions();
 | |
| 				if (_document->thumbnail()->loaded()) {
 | |
| 					_cache = _document->thumbnail()->pixBlurred(_origin, s.width(), s.height());
 | |
| 					_cacheStatus = CacheThumbLoaded;
 | |
| 				} else if (const auto blurred = _document->thumbnailInline()) {
 | |
| 					_cache = _document->thumbnail()->pixBlurred(_origin, s.width(), s.height());
 | |
| 					_cacheStatus = CacheThumbLoaded;
 | |
| 				} else {
 | |
| 					_document->thumbnail()->load(_origin);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} else if (_photo) {
 | |
| 		if (_cacheStatus != CacheLoaded) {
 | |
| 			if (_photo->loaded()) {
 | |
| 				QSize s = currentDimensions();
 | |
| 				_cache = _photo->large()->pix(_origin, s.width(), s.height());
 | |
| 				_cacheStatus = CacheLoaded;
 | |
| 			} else {
 | |
| 				_photo->load(_origin);
 | |
| 				if (_cacheStatus != CacheThumbLoaded) {
 | |
| 					QSize s = currentDimensions();
 | |
| 					if (_photo->thumbnail()->loaded()) {
 | |
| 						_cache = _photo->thumbnail()->pixBlurred(_origin, s.width(), s.height());
 | |
| 						_cacheStatus = CacheThumbLoaded;
 | |
| 					} else if (_photo->thumbnailSmall()->loaded()) {
 | |
| 						_cache = _photo->thumbnailSmall()->pixBlurred(_origin, s.width(), s.height());
 | |
| 						_cacheStatus = CacheThumbLoaded;
 | |
| 					} else if (const auto blurred = _photo->thumbnailInline()) {
 | |
| 						_cache = blurred->pixBlurred(_origin, s.width(), s.height());
 | |
| 						_cacheStatus = CacheThumbLoaded;
 | |
| 					} else {
 | |
| 						_photo->thumbnailSmall()->load(_origin);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 	return _cache;
 | |
| }
 | |
| 
 | |
| void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
 | |
| 	using namespace Media::Clip;
 | |
| 	switch (notification) {
 | |
| 	case NotificationReinit: {
 | |
| 		if (_gif && _gif->state() == State::Error) {
 | |
| 			_gif.setBad();
 | |
| 		}
 | |
| 
 | |
| 		if (_gif && _gif->ready() && !_gif->started()) {
 | |
| 			QSize s = currentDimensions();
 | |
| 			_gif->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
 | |
| 		}
 | |
| 
 | |
| 		update();
 | |
| 	} break;
 | |
| 
 | |
| 	case NotificationRepaint: {
 | |
| 		if (_gif && !_gif->currentDisplayed()) {
 | |
| 			update(updateArea());
 | |
| 		}
 | |
| 	} break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| MediaPreviewWidget::~MediaPreviewWidget() {
 | |
| }
 | |
| 
 | |
| } // namespace Window
 | 
