| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722 |
- /*
- 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 "inline_bots/inline_bot_layout_internal.h"
- #include "data/data_photo.h"
- #include "data/data_document.h"
- #include "data/data_session.h"
- #include "data/data_file_origin.h"
- #include "data/data_photo_media.h"
- #include "data/data_document_media.h"
- #include "data/stickers/data_stickers.h"
- #include "chat_helpers/gifs_list_widget.h" // ChatHelpers::AddGifAction.
- #include "chat_helpers/stickers_lottie.h"
- #include "inline_bots/inline_bot_result.h"
- #include "lottie/lottie_single_player.h"
- #include "media/audio/media_audio.h"
- #include "media/clip/media_clip_reader.h"
- #include "media/player/media_player_instance.h"
- #include "history/history_location_manager.h"
- #include "history/view/history_view_cursor_state.h"
- #include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
- #include "history/view/media/history_view_media_common.h"
- #include "ui/image/image.h"
- #include "ui/text/format_values.h"
- #include "ui/cached_round_corners.h"
- #include "ui/painter.h"
- #include "main/main_session.h"
- #include "lang/lang_keys.h"
- #include "styles/style_overview.h"
- #include "styles/style_chat.h"
- #include "styles/style_chat_helpers.h"
- #include "styles/style_widgets.h"
- namespace InlineBots {
- namespace Layout {
- namespace internal {
- using TextState = HistoryView::TextState;
- constexpr auto kMaxInlineArea = 1280 * 720;
- [[nodiscard]] bool CanPlayInline(not_null<DocumentData*> document) {
- const auto dimensions = document->dimensions;
- return dimensions.width() * dimensions.height() <= kMaxInlineArea;
- }
- FileBase::FileBase(not_null<Context*> context, std::shared_ptr<Result> result)
- : ItemBase(context, std::move(result)) {
- }
- FileBase::FileBase(
- not_null<Context*> context,
- not_null<DocumentData*> document)
- : ItemBase(context, document) {
- }
- DocumentData *FileBase::getShownDocument() const {
- if (const auto result = getDocument()) {
- return result;
- }
- return getResultDocument();
- }
- int FileBase::content_width() const {
- if (const auto document = getShownDocument()) {
- if (document->dimensions.width() > 0) {
- return document->dimensions.width();
- }
- return style::ConvertScale(document->thumbnailLocation().width());
- }
- return 0;
- }
- int FileBase::content_height() const {
- if (const auto document = getShownDocument()) {
- if (document->dimensions.height() > 0) {
- return document->dimensions.height();
- }
- return style::ConvertScale(document->thumbnailLocation().height());
- }
- return 0;
- }
- int FileBase::content_duration() const {
- if (const auto document = getShownDocument()) {
- if (document->hasDuration()) {
- return document->duration() / 1000;
- }
- }
- return getResultDuration();
- }
- Gif::Gif(not_null<Context*> context, std::shared_ptr<Result> result)
- : FileBase(context, std::move(result)) {
- Expects(getResultDocument() != nullptr);
- }
- Gif::Gif(
- not_null<Context*> context,
- not_null<DocumentData*> document,
- bool hasDeleteButton)
- : FileBase(context, document) {
- if (hasDeleteButton) {
- _delete = std::make_shared<DeleteSavedGifClickHandler>(document);
- }
- }
- void Gif::initDimensions() {
- int32 w = content_width(), h = content_height();
- if (w <= 0 || h <= 0) {
- _maxw = 0;
- } else {
- w = w * st::inlineMediaHeight / h;
- _maxw = qMax(w, int32(st::inlineResultsMinWidth));
- }
- _minh = st::inlineMediaHeight + st::inlineResultsSkip;
- }
- void Gif::setPosition(int32 position) {
- AbstractLayoutItem::setPosition(position);
- if (_position < 0) {
- _gif.reset();
- }
- }
- void DeleteSavedGifClickHandler::onClickImpl() const {
- ChatHelpers::AddGifAction(
- [](QString, Fn<void()> &&done, const style::icon*) { done(); },
- nullptr,
- _data);
- }
- int Gif::resizeGetHeight(int width) {
- _width = width;
- _height = _minh;
- return _height;
- }
- QRect Gif::innerContentRect() const {
- ensureDataMediaCreated(getShownDocument());
- const auto size = (!_thumb.isNull())
- ? (_thumb.size() / style::DevicePixelRatio())
- : countFrameSize();
- return QRect(QPoint(), size);
- }
- void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- const auto document = getShownDocument();
- ensureDataMediaCreated(document);
- const auto preview = Data::VideoPreviewState(_dataMedia.get());
- preview.automaticLoad(fileOrigin());
- const auto displayLoading = !preview.usingThumbnail()
- && document->displayLoading();
- const auto loaded = preview.loaded();
- const auto loading = preview.loading();
- if (loaded
- && !_gif
- && !_gif.isBad()
- && CanPlayInline(document)) {
- auto that = const_cast<Gif*>(this);
- that->_gif = preview.makeAnimation([=](
- Media::Clip::Notification notification) {
- that->clipCallback(notification);
- });
- }
- const auto animating = (_gif && _gif->started());
- if (displayLoading) {
- ensureAnimation();
- if (!_animation->radial.animating()) {
- _animation->radial.start(_dataMedia->progress());
- }
- }
- const auto radial = isRadialAnimation();
- const auto frame = countFrameSize();
- const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
- if (animating) {
- const auto pixmap = _gif->current({
- .frame = frame,
- .outer = r.size(),
- }, context->paused ? 0 : context->ms);
- if (_thumb.isNull()) {
- _thumb = pixmap;
- _thumbGood = true;
- }
- p.drawImage(r.topLeft(), pixmap);
- } else {
- prepareThumbnail(r.size(), frame);
- if (_thumb.isNull()) {
- p.fillRect(r, st::overviewPhotoBg);
- } else {
- p.drawImage(r.topLeft(), _thumb);
- }
- }
- if (radial
- || _gif.isBad()
- || (!_gif && !loaded && !loading && !preview.usingThumbnail())) {
- auto radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1.;
- if (_animation && _animation->_a_over.animating()) {
- auto over = _animation->_a_over.value(1.);
- p.fillRect(r, anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
- } else {
- auto over = (_state & StateFlag::Over);
- p.fillRect(r, over ? st::msgDateImgBgOver : st::msgDateImgBg);
- }
- p.setOpacity(radialOpacity * p.opacity());
- p.setOpacity(radialOpacity);
- auto icon = [&] {
- if (radial || loading) {
- return &st::historyFileInCancel;
- } else if (loaded) {
- return &st::historyFileInPlay;
- }
- return &st::historyFileInDownload;
- }();
- const auto size = st::inlineRadialSize;
- QRect inner(
- (r.width() - size) / 2,
- (r.height() - size) / 2,
- size,
- size);
- icon->paintInCenter(p, inner);
- if (radial) {
- p.setOpacity(1);
- QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
- _animation->radial.draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg);
- }
- }
- if (_delete && (_state & StateFlag::Over)) {
- auto deleteSelected = (_state & StateFlag::DeleteOver);
- auto deletePos = QPoint(_width - st::stickerPanDeleteIconBg.width(), 0);
- p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg);
- st::stickerPanDeleteIconBg.paint(p, deletePos, width());
- p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg);
- st::stickerPanDeleteIconFg.paint(p, deletePos, width());
- p.setOpacity(1.);
- }
- }
- TextState Gif::getState(
- QPoint point,
- StateRequest request) const {
- if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
- if (_delete && style::rtlpoint(point, _width).x() >= _width - st::stickerPanDeleteIconBg.width() && point.y() < st::stickerPanDeleteIconBg.height()) {
- return { nullptr, _delete };
- } else {
- return { nullptr, _send };
- }
- }
- return {};
- }
- void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
- if (!p) return;
- if (_delete && p == _delete) {
- bool wasactive = (_state & StateFlag::DeleteOver);
- if (active != wasactive) {
- auto from = active ? 0. : 1., to = active ? 1. : 0.;
- _a_deleteOver.start([this] { update(); }, from, to, st::stickersRowDuration);
- if (active) {
- _state |= StateFlag::DeleteOver;
- } else {
- _state &= ~StateFlag::DeleteOver;
- }
- }
- }
- if (p == _delete || p == _send) {
- bool wasactive = (_state & StateFlag::Over);
- if (active != wasactive) {
- ensureDataMediaCreated(getShownDocument());
- const auto preview = Data::VideoPreviewState(_dataMedia.get());
- if (!preview.usingThumbnail() && !preview.loaded()) {
- ensureAnimation();
- auto from = active ? 0. : 1., to = active ? 1. : 0.;
- _animation->_a_over.start([=] { update(); }, from, to, st::stickersRowDuration);
- }
- if (active) {
- _state |= StateFlag::Over;
- } else {
- _state &= ~StateFlag::Over;
- }
- }
- }
- ItemBase::clickHandlerActiveChanged(p, active);
- }
- QSize Gif::countFrameSize() const {
- bool animating = (_gif && _gif->ready());
- int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight;
- if (framew * height > frameh * _width) {
- if (framew < st::maxStickerSize || frameh > height) {
- if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) {
- framew = framew * height / frameh;
- frameh = height;
- } else {
- frameh = int32(frameh * st::maxStickerSize) / framew;
- framew = st::maxStickerSize;
- }
- }
- } else {
- if (frameh < st::maxStickerSize || framew > _width) {
- if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) {
- frameh = frameh * _width / framew;
- framew = _width;
- } else {
- framew = int32(framew * st::maxStickerSize) / frameh;
- frameh = st::maxStickerSize;
- }
- }
- }
- return QSize(framew, frameh);
- }
- void Gif::validateThumbnail(
- Image *image,
- QSize size,
- QSize frame,
- bool good) const {
- if (!image || (_thumbGood && !good)) {
- return;
- } else if ((_thumb.size() == size * style::DevicePixelRatio())
- && (_thumbGood || !good)) {
- return;
- }
- _thumbGood = good;
- _thumb = image->pixNoCache(
- frame * style::DevicePixelRatio(),
- {
- .options = (Images::Option::TransparentBackground
- | (good ? Images::Option() : Images::Option::Blur)),
- .outer = size,
- }).toImage();
- }
- void Gif::prepareThumbnail(QSize size, QSize frame) const {
- const auto document = getShownDocument();
- Assert(document != nullptr);
- ensureDataMediaCreated(document);
- validateThumbnail(_dataMedia->thumbnail(), size, frame, true);
- validateThumbnail(_dataMedia->thumbnailInline(), size, frame, false);
- }
- void Gif::ensureDataMediaCreated(not_null<DocumentData*> document) const {
- if (_dataMedia) {
- return;
- }
- _dataMedia = document->createMediaView();
- _dataMedia->thumbnailWanted(fileOrigin());
- _dataMedia->videoThumbnailWanted(fileOrigin());
- }
- void Gif::ensureAnimation() const {
- if (!_animation) {
- _animation = std::make_unique<AnimationData>([=](crl::time now) {
- radialAnimationCallback(now);
- });
- }
- }
- bool Gif::isRadialAnimation() const {
- if (_animation) {
- if (_animation->radial.animating()) {
- return true;
- } else {
- ensureDataMediaCreated(getShownDocument());
- const auto preview = Data::VideoPreviewState(_dataMedia.get());
- if (preview.usingThumbnail() || preview.loaded()) {
- _animation = nullptr;
- }
- }
- }
- return false;
- }
- void Gif::radialAnimationCallback(crl::time now) const {
- const auto document = getShownDocument();
- ensureDataMediaCreated(document);
- const auto updated = [&] {
- return _animation->radial.update(
- _dataMedia->progress(),
- !document->loading() || _dataMedia->loaded(),
- now);
- }();
- if (!anim::Disabled() || updated) {
- update();
- }
- if (!_animation->radial.animating() && _dataMedia->loaded()) {
- _animation = nullptr;
- }
- }
- void Gif::unloadHeavyPart() {
- _gif.reset();
- _dataMedia = nullptr;
- }
- void Gif::clipCallback(Media::Clip::Notification notification) {
- using namespace Media::Clip;
- switch (notification) {
- case Notification::Reinit: {
- if (_gif) {
- if (_gif->state() == State::Error) {
- _gif.setBad();
- } else if (_gif->ready() && !_gif->started()) {
- if (_gif->width() * _gif->height() > kMaxInlineArea) {
- getShownDocument()->dimensions = QSize(
- _gif->width(),
- _gif->height());
- _gif.reset();
- } else {
- _gif->start({
- .frame = countFrameSize(),
- .outer = { _width, st::inlineMediaHeight },
- });
- }
- } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
- unloadHeavyPart();
- }
- }
- update();
- } break;
- case Notification::Repaint: {
- if (_gif && !_gif->currentDisplayed()) {
- update();
- }
- } break;
- }
- }
- Sticker::Sticker(not_null<Context*> context, std::shared_ptr<Result> result)
- : FileBase(context, std::move(result)) {
- Expects(getResultDocument() != nullptr);
- }
- Sticker::~Sticker() = default;
- void Sticker::initDimensions() {
- _maxw = st::stickerPanSize.width();
- _minh = st::stickerPanSize.height();
- }
- void Sticker::preload() const {
- const auto document = getShownDocument();
- Assert(document != nullptr);
- ensureDataMediaCreated(document);
- _dataMedia->checkStickerSmall();
- }
- void Sticker::ensureDataMediaCreated(not_null<DocumentData*> document) const {
- if (_dataMedia) {
- return;
- }
- _dataMedia = document->createMediaView();
- }
- void Sticker::unloadHeavyPart() {
- _dataMedia = nullptr;
- _lifetime.destroy();
- _lottie = nullptr;
- _webm = nullptr;
- }
- QRect Sticker::innerContentRect() const {
- ensureDataMediaCreated(getShownDocument());
- const auto size = (_lottie && _lottie->ready())
- ? (_lottie->frame().size() / style::DevicePixelRatio())
- : (!_thumb.isNull())
- ? (_thumb.size() / style::DevicePixelRatio())
- : getThumbSize();
- const auto pos = QPoint(
- (st::stickerPanSize.width() - size.width()) / 2,
- (st::stickerPanSize.height() - size.height()) / 2);
- return QRect(pos, size);
- }
- void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- ensureDataMediaCreated(getShownDocument());
- auto over = _a_over.value(_active ? 1. : 0.);
- if (over > 0) {
- p.setOpacity(over);
- Ui::FillRoundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, Ui::StickerHoverCorners);
- p.setOpacity(1);
- }
- prepareThumbnail();
- if (_lottie && _lottie->ready()) {
- const auto frame = _lottie->frame();
- const auto size = frame.size() / style::DevicePixelRatio();
- const auto pos = QPoint(
- (st::stickerPanSize.width() - size.width()) / 2,
- (st::stickerPanSize.height() - size.height()) / 2);
- p.drawImage(
- QRect(pos, size),
- frame);
- if (!context->paused) {
- _lottie->markFrameShown();
- }
- } else if (_webm && _webm->started()) {
- const auto size = getThumbSize();
- const auto frame = _webm->current({
- .frame = size,
- .keepAlpha = true,
- }, context->paused ? 0 : context->ms);
- p.drawImage(
- (st::stickerPanSize.width() - size.width()) / 2,
- (st::stickerPanSize.height() - size.width()) / 2,
- frame);
- } else if (!_thumb.isNull()) {
- const auto w = _thumb.width() / style::DevicePixelRatio();
- const auto h = _thumb.height() / style::DevicePixelRatio();
- const auto pos = QPoint(
- (st::stickerPanSize.width() - w) / 2,
- (st::stickerPanSize.height() - h) / 2);
- p.drawPixmap(pos, _thumb);
- } else if (context->pathGradient) {
- const auto thumbSize = getThumbSize();
- const auto w = thumbSize.width();
- const auto h = thumbSize.height();
- ChatHelpers::PaintStickerThumbnailPath(
- p,
- _dataMedia.get(),
- QRect(
- (st::stickerPanSize.width() - w) / 2,
- (st::stickerPanSize.height() - h) / 2,
- w,
- h),
- context->pathGradient);
- }
- }
- TextState Sticker::getState(
- QPoint point,
- StateRequest request) const {
- if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
- return { nullptr, _send };
- }
- return {};
- }
- void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
- if (!p) return;
- if (p == _send) {
- if (active != _active) {
- _active = active;
- auto from = active ? 0. : 1., to = active ? 1. : 0.;
- _a_over.start([this] { update(); }, from, to, st::stickersRowDuration);
- }
- }
- ItemBase::clickHandlerActiveChanged(p, active);
- }
- QSize Sticker::boundingBox() const {
- const auto size = st::stickerPanSize.width() - st::roundRadiusSmall * 2;
- return { size, size };
- }
- QSize Sticker::getThumbSize() const {
- const auto width = qMax(content_width(), 1);
- const auto height = qMax(content_height(), 1);
- return HistoryView::DownscaledSize({ width, height }, boundingBox());
- }
- void Sticker::setupLottie() const {
- Expects(_dataMedia != nullptr);
- _lottie = ChatHelpers::LottiePlayerFromDocument(
- _dataMedia.get(),
- ChatHelpers::StickerLottieSize::InlineResults,
- boundingBox() * style::DevicePixelRatio());
- _lottie->updates(
- ) | rpl::start_with_next([=] {
- update();
- }, _lifetime);
- }
- void Sticker::setupWebm() const {
- Expects(_dataMedia != nullptr);
- const auto that = const_cast<Sticker*>(this);
- auto callback = [=](Media::Clip::Notification notification) {
- that->clipCallback(notification);
- };
- that->_webm = Media::Clip::MakeReader(
- _dataMedia->owner()->location(),
- _dataMedia->bytes(),
- std::move(callback));
- }
- void Sticker::prepareThumbnail() const {
- const auto document = getShownDocument();
- Assert(document != nullptr);
- ensureDataMediaCreated(document);
- const auto sticker = document->sticker();
- if (sticker && _dataMedia->loaded()) {
- if (!_lottie && sticker->isLottie()) {
- setupLottie();
- } else if (!_webm && sticker->isWebm()) {
- setupWebm();
- }
- }
- _dataMedia->checkStickerSmall();
- if (const auto image = _dataMedia->getStickerSmall()) {
- if (!_lottie && !_thumbLoaded) {
- const auto thumbSize = getThumbSize();
- _thumb = image->pix(thumbSize);
- _thumbLoaded = true;
- }
- }
- }
- void Sticker::clipCallback(Media::Clip::Notification notification) {
- using namespace Media::Clip;
- switch (notification) {
- case Notification::Reinit: {
- if (!_webm) {
- break;
- } else if (_webm->state() == State::Error) {
- _webm.setBad();
- } else if (_webm->ready() && !_webm->started()) {
- _webm->start({
- .frame = getThumbSize(),
- .keepAlpha = true,
- });
- } else if (_webm->autoPausedGif()
- && !context()->inlineItemVisible(this)) {
- unloadHeavyPart();
- }
- } break;
- case Notification::Repaint: break;
- }
- update();
- }
- Photo::Photo(not_null<Context*> context, std::shared_ptr<Result> result)
- : ItemBase(context, std::move(result)) {
- Expects(getShownPhoto() != nullptr);
- }
- void Photo::initDimensions() {
- const auto photo = getShownPhoto();
- int32 w = photo->width(), h = photo->height();
- if (w <= 0 || h <= 0) {
- _maxw = 0;
- } else {
- w = w * st::inlineMediaHeight / h;
- _maxw = qMax(w, int32(st::inlineResultsMinWidth));
- }
- _minh = st::inlineMediaHeight + st::inlineResultsSkip;
- }
- void Photo::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- int32 height = st::inlineMediaHeight;
- QSize frame = countFrameSize();
- QRect r(0, 0, _width, height);
- prepareThumbnail({ _width, height }, frame);
- if (_thumb.isNull()) {
- p.fillRect(r, st::overviewPhotoBg);
- } else {
- p.drawPixmap(r.topLeft(), _thumb);
- }
- }
- TextState Photo::getState(
- QPoint point,
- StateRequest request) const {
- if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
- return { nullptr, _send };
- }
- return {};
- }
- void Photo::unloadHeavyPart() {
- _photoMedia = nullptr;
- }
- PhotoData *Photo::getShownPhoto() const {
- if (const auto result = getPhoto()) {
- return result;
- }
- return getResultPhoto();
- }
- QSize Photo::countFrameSize() const {
- const auto photo = getShownPhoto();
- int32 framew = photo->width(), frameh = photo->height(), height = st::inlineMediaHeight;
- if (framew * height > frameh * _width) {
- if (framew < st::maxStickerSize || frameh > height) {
- if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) {
- framew = framew * height / frameh;
- frameh = height;
- } else {
- frameh = int32(frameh * st::maxStickerSize) / framew;
- framew = st::maxStickerSize;
- }
- }
- } else {
- if (frameh < st::maxStickerSize || framew > _width) {
- if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) {
- frameh = frameh * _width / framew;
- framew = _width;
- } else {
- framew = int32(framew * st::maxStickerSize) / frameh;
- frameh = st::maxStickerSize;
- }
- }
- }
- return QSize(framew, frameh);
- }
- void Photo::validateThumbnail(
- Image *image,
- QSize size,
- QSize frame,
- bool good) const {
- if (!image || (_thumbGood && !good)) {
- return;
- } else if ((_thumb.size() == size * style::DevicePixelRatio())
- && (_thumbGood || !good)) {
- return;
- }
- const auto origin = fileOrigin();
- _thumb = image->pixNoCache(
- frame * style::DevicePixelRatio(),
- {
- .options = (Images::Option::TransparentBackground
- | (good ? Images::Option() : Images::Option::Blur)),
- .outer = size,
- });
- _thumbGood = good;
- }
- void Photo::prepareThumbnail(QSize size, QSize frame) const {
- using PhotoSize = Data::PhotoSize;
- const auto photo = getShownPhoto();
- Assert(photo != nullptr);
- if (!_photoMedia) {
- _photoMedia = photo->createMediaView();
- _photoMedia->wanted(PhotoSize::Thumbnail, fileOrigin());
- }
- validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, frame, true);
- validateThumbnail(_photoMedia->image(PhotoSize::Small), size, frame, false);
- validateThumbnail(_photoMedia->thumbnailInline(), size, frame, false);
- }
- Video::Video(not_null<Context*> context, std::shared_ptr<Result> result)
- : FileBase(context, std::move(result))
- , _link(getResultPreviewHandler())
- , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
- , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
- if (int duration = content_duration()) {
- _duration = Ui::FormatDurationText(duration);
- _durationWidth = st::normalFont->width(_duration);
- }
- }
- bool Video::withThumbnail() const {
- if (const auto document = getShownDocument()) {
- if (document->hasThumbnail()) {
- return true;
- }
- }
- return hasResultThumb();
- }
- void Video::initDimensions() {
- const auto withThumb = withThumbnail();
- _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
- const auto textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
- TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
- auto title = TextUtilities::SingleLine(_result->getLayoutTitle());
- if (title.isEmpty()) {
- title = tr::lng_media_video(tr::now);
- }
- _title.setText(st::semiboldTextStyle, title, titleOpts);
- int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height);
- int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3;
- TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
- QString description = _result->getLayoutDescription();
- if (description.isEmpty()) {
- description = _duration;
- }
- _description.setText(st::defaultTextStyle, description, descriptionOpts);
- _minh = st::inlineThumbSize;
- _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
- }
- void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- int left = st::inlineThumbSize + st::inlineThumbSkip;
- const auto withThumb = withThumbnail();
- if (withThumb) {
- prepareThumbnail({ st::inlineThumbSize, st::inlineThumbSize });
- if (_thumb.isNull()) {
- p.fillRect(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewPhotoBg);
- } else {
- p.drawPixmapLeft(0, st::inlineRowMargin, _width, _thumb);
- }
- } else {
- p.fillRect(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewVideoBg);
- }
- if (!_duration.isEmpty()) {
- int durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin;
- int durationW = _durationWidth + 2 * st::msgDateImgPadding.x(), durationH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
- int durationX = (st::inlineThumbSize - durationW) / 2, durationY = st::inlineRowMargin + st::inlineThumbSize - durationH;
- Ui::FillRoundRect(p, durationX, durationY - st::msgDateImgPadding.y(), durationW, durationH, st::msgDateImgBg, Ui::DateCorners);
- p.setPen(st::msgDateImgFg);
- p.setFont(st::normalFont);
- p.drawText(durationX + st::msgDateImgPadding.x(), durationTop + st::normalFont->ascent, _duration);
- }
- p.setPen(st::inlineTitleFg);
- _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
- int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
- p.setPen(st::inlineDescriptionFg);
- int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3;
- _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
- if (!context->lastRow) {
- p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
- }
- }
- void Video::unloadHeavyPart() {
- _documentMedia = nullptr;
- ItemBase::unloadHeavyPart();
- }
- TextState Video::getState(
- QPoint point,
- StateRequest request) const {
- if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
- return { nullptr, _link };
- }
- if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) {
- return { nullptr, _send };
- }
- return {};
- }
- void Video::prepareThumbnail(QSize size) const {
- if (const auto document = getShownDocument()) {
- if (document->hasThumbnail()) {
- if (!_documentMedia) {
- _documentMedia = document->createMediaView();
- _documentMedia->thumbnailWanted(fileOrigin());
- }
- if (!_documentMedia->thumbnail()) {
- return;
- }
- }
- }
- auto resultThumbnailImage = _documentMedia
- ? nullptr
- : getResultThumb(fileOrigin());
- const auto resultThumbnail = Image(resultThumbnailImage
- ? base::duplicate(*resultThumbnailImage)
- : QImage());
- const auto thumb = _documentMedia
- ? _documentMedia->thumbnail()
- : resultThumbnailImage
- ? &resultThumbnail
- : nullptr;
- if (!thumb) {
- return;
- }
- if (_thumb.size() != size * style::DevicePixelRatio()) {
- const auto width = size.width();
- const auto height = size.height();
- auto w = qMax(style::ConvertScale(thumb->width()), 1);
- auto h = qMax(style::ConvertScale(thumb->height()), 1);
- if (w * height > h * width) {
- if (height < h) {
- w = w * height / h;
- h = height;
- }
- } else {
- if (width < w) {
- h = h * width / w;
- w = width;
- }
- }
- _thumb = thumb->pixNoCache(
- QSize(w, h) * style::DevicePixelRatio(),
- {
- .options = Images::Option::TransparentBackground,
- .outer = size,
- });
- }
- }
- void CancelFileClickHandler::onClickImpl() const {
- _result->cancelFile();
- }
- File::File(not_null<Context*> context, std::shared_ptr<Result> result)
- : FileBase(context, std::move(result))
- , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineFileSize - st::inlineThumbSkip)
- , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineFileSize - st::inlineThumbSkip)
- , _cancel(std::make_shared<CancelFileClickHandler>(_result.get()))
- , _document(getShownDocument()) {
- Expects(getResultDocument() != nullptr);
- updateStatusText();
- // We have to save document, not read it from Result every time.
- // Because we first delete the Result and then delete this File.
- // So in destructor we have to remember _document, we can't read it.
- regDocumentItem(_document, this);
- }
- void File::initDimensions() {
- _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
- TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto };
- _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
- TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
- _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
- _minh = st::inlineFileSize;
- _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
- }
- void File::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- const auto left = st::inlineFileSize + st::inlineThumbSkip;
- ensureDataMediaCreated();
- const auto displayLoading = _document->displayLoading();
- if (displayLoading) {
- ensureAnimation();
- if (!_animation->radial.animating()) {
- _animation->radial.start(_documentMedia->progress());
- }
- }
- const auto showPause = updateStatusText();
- const auto radial = isRadialAnimation();
- auto inner = style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width);
- p.setPen(Qt::NoPen);
- const auto coverDrawn = _document->isSongWithCover()
- && HistoryView::DrawThumbnailAsSongCover(
- p,
- st::songCoverOverlayFg,
- _documentMedia,
- inner);
- if (!coverDrawn) {
- PainterHighQualityEnabler hq(p);
- if (isThumbAnimation()) {
- const auto over = _animation->a_thumbOver.value(1.);
- p.setBrush(
- anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
- } else {
- const auto over = ClickHandler::showAsActive(_document->loading()
- ? _cancel
- : _open);
- p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
- }
- p.drawEllipse(inner);
- }
- if (radial) {
- auto radialCircle = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine));
- _animation->radial.draw(p, radialCircle, st::msgFileRadialLine, st::historyFileInRadialFg);
- }
- const auto icon = [&] {
- if (radial || _document->loading()) {
- return &st::historyFileInCancel;
- } else if (showPause) {
- return &st::historyFileInPause;
- } else if (_document->isImage()) {
- return &st::historyFileInImage;
- } else if (_document->isSongWithCover()) {
- return &st::historyFileThumbPlay;
- } else if (_document->isVoiceMessage()
- || _document->isAudioFile()) {
- return &st::historyFileInPlay;
- }
- return &st::historyFileInDocument;
- }();
- icon->paintInCenter(p, inner);
- int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop;
- int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop;
- p.setPen(st::inlineTitleFg);
- _title.drawLeftElided(p, left, titleTop, _width - left, _width);
- p.setPen(st::inlineDescriptionFg);
- bool drawStatusSize = true;
- if (_statusSize == Ui::FileStatusSizeReady
- || _statusSize == Ui::FileStatusSizeLoaded
- || _statusSize == Ui::FileStatusSizeFailed) {
- if (!_description.isEmpty()) {
- _description.drawLeftElided(p, left, descriptionTop, _width - left, _width);
- drawStatusSize = false;
- }
- }
- if (drawStatusSize) {
- p.setFont(st::normalFont);
- p.drawTextLeft(left, descriptionTop, _width, _statusText);
- }
- if (!context->lastRow) {
- p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
- }
- }
- TextState File::getState(
- QPoint point,
- StateRequest request) const {
- if (QRect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize).contains(point)) {
- return { nullptr, _document->loading() ? _cancel : _open };
- } else {
- auto left = st::inlineFileSize + st::inlineThumbSkip;
- if (QRect(left, 0, _width - left, _height).contains(point)) {
- return { nullptr, _send };
- }
- }
- return {};
- }
- void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
- if (p == _open || p == _cancel) {
- ensureAnimation();
- _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, active ? 0. : 1., active ? 1. : 0., st::msgFileOverDuration);
- }
- }
- void File::unloadHeavyPart() {
- _documentMedia = nullptr;
- }
- File::~File() {
- unregDocumentItem(_document, this);
- }
- void File::thumbAnimationCallback() {
- update();
- }
- void File::radialAnimationCallback(crl::time now) const {
- ensureDataMediaCreated();
- const auto updated = [&] {
- return _animation->radial.update(
- _documentMedia->progress(),
- !_document->loading() || _documentMedia->loaded(),
- now);
- }();
- if (!anim::Disabled() || updated) {
- update();
- }
- if (!_animation->radial.animating()) {
- checkAnimationFinished();
- }
- }
- void File::ensureAnimation() const {
- if (!_animation) {
- _animation = std::make_unique<AnimationData>([=](crl::time now) {
- return radialAnimationCallback(now);
- });
- }
- }
- void File::ensureDataMediaCreated() const {
- if (_documentMedia) {
- return;
- }
- _documentMedia = _document->createMediaView();
- }
- void File::checkAnimationFinished() const {
- if (_animation
- && !_animation->a_thumbOver.animating()
- && !_animation->radial.animating()) {
- ensureDataMediaCreated();
- if (_documentMedia->loaded()) {
- _animation.reset();
- }
- }
- }
- bool File::updateStatusText() const {
- ensureDataMediaCreated();
- auto showPause = false;
- auto statusSize = int64();
- auto realDuration = TimeId();
- if (_document->status == FileDownloadFailed || _document->status == FileUploadFailed) {
- statusSize = Ui::FileStatusSizeFailed;
- } else if (_document->uploading()) {
- statusSize = _document->uploadingData->offset;
- } else if (_document->loading()) {
- statusSize = _document->loadOffset();
- } else if (_documentMedia->loaded()) {
- statusSize = Ui::FileStatusSizeLoaded;
- } else {
- statusSize = Ui::FileStatusSizeReady;
- }
- if (_document->isVoiceMessage() || _document->isAudioFile()) {
- const auto type = _document->isVoiceMessage() ? AudioMsgId::Type::Voice : AudioMsgId::Type::Song;
- const auto state = Media::Player::instance()->getState(type);
- if (state.id == AudioMsgId(_document, FullMsgId(), state.id.externalPlayId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
- statusSize = -1 - (state.position / state.frequency);
- realDuration = (state.length / state.frequency);
- showPause = Media::Player::ShowPauseIcon(state.state);
- }
- if (!showPause && (state.id == AudioMsgId(_document, FullMsgId(), state.id.externalPlayId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
- showPause = true;
- }
- }
- if (statusSize != _statusSize) {
- const auto duration = _document->isSong()
- ? _document->duration()
- : (_document->isVoiceMessage() ? _document->duration() : -1);
- setStatusSize(statusSize, _document->size, (duration >= 0) ? (duration / 1000) : -1, realDuration);
- }
- return showPause;
- }
- void File::setStatusSize(
- int64 newSize,
- int64 fullSize,
- TimeId duration,
- TimeId realDuration) const {
- _statusSize = newSize;
- if (_statusSize == Ui::FileStatusSizeReady) {
- _statusText = (duration >= 0) ? Ui::FormatDurationAndSizeText(duration, fullSize) : (duration < -1 ? Ui::FormatGifAndSizeText(fullSize) : Ui::FormatSizeText(fullSize));
- } else if (_statusSize == Ui::FileStatusSizeLoaded) {
- _statusText = (duration >= 0) ? Ui::FormatDurationText(duration) : (duration < -1 ? u"GIF"_q : Ui::FormatSizeText(fullSize));
- } else if (_statusSize == Ui::FileStatusSizeFailed) {
- _statusText = tr::lng_attach_failed(tr::now);
- } else if (_statusSize >= 0) {
- _statusText = Ui::FormatDownloadText(_statusSize, fullSize);
- } else {
- _statusText = Ui::FormatPlayedText(-_statusSize - 1, realDuration);
- }
- }
- Contact::Contact(not_null<Context*> context, std::shared_ptr<Result> result)
- : ItemBase(context, std::move(result))
- , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
- , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
- }
- void Contact::initDimensions() {
- _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
- int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
- TextParseOptions titleOpts = { 0, textWidth, st::semiboldFont->height, Qt::LayoutDirectionAuto };
- _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
- TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, st::normalFont->height, Qt::LayoutDirectionAuto };
- _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
- _minh = st::inlineFileSize;
- _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
- }
- void Contact::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- int32 left = st::defaultEmojiPan.headerLeft - st::inlineResultsLeft;
- left = st::inlineFileSize + st::inlineThumbSkip;
- prepareThumbnail(st::inlineFileSize, st::inlineFileSize);
- QRect rthumb(style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width));
- p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
- int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop;
- int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop;
- p.setPen(st::inlineTitleFg);
- _title.drawLeftElided(p, left, titleTop, _width - left, _width);
- p.setPen(st::inlineDescriptionFg);
- _description.drawLeftElided(p, left, descriptionTop, _width - left, _width);
- if (!context->lastRow) {
- p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
- }
- }
- TextState Contact::getState(
- QPoint point,
- StateRequest request) const {
- if (!QRect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineThumbSize).contains(point)) {
- auto left = (st::inlineFileSize + st::inlineThumbSkip);
- if (QRect(left, 0, _width - left, _height).contains(point)) {
- return { nullptr, _send };
- }
- }
- return {};
- }
- void Contact::prepareThumbnail(int width, int height) const {
- if (!hasResultThumb()) {
- if ((_thumb.width() != width * style::DevicePixelRatio())
- || (_thumb.height() != height * style::DevicePixelRatio())) {
- _thumb = getResultContactAvatar(width, height);
- }
- return;
- }
- const auto origin = fileOrigin();
- const auto thumb = getResultThumb(origin);
- if (!thumb
- || ((_thumb.width() == width * style::DevicePixelRatio())
- && (_thumb.height() == height * style::DevicePixelRatio()))) {
- return;
- }
- auto w = qMax(style::ConvertScale(thumb->width()), 1);
- auto h = qMax(style::ConvertScale(thumb->height()), 1);
- if (w * height > h * width) {
- if (height < h) {
- w = w * height / h;
- h = height;
- }
- } else {
- if (width < w) {
- h = h * width / w;
- w = width;
- }
- }
- _thumb = Image(base::duplicate(*thumb)).pixNoCache(
- QSize(w, h) * style::DevicePixelRatio(),
- {
- .options = Images::Option::TransparentBackground,
- .outer = { width, height },
- });
- }
- Article::Article(
- not_null<Context*> context,
- std::shared_ptr<Result> result,
- bool withThumb)
- : ItemBase(context, std::move(result))
- , _url(getResultUrlHandler())
- , _link(getResultPreviewHandler())
- , _withThumb(withThumb)
- , _title(st::emojiPanWidth / 2)
- , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
- if (!_link) {
- if (const auto point = _result->getLocationPoint()) {
- _link = std::make_shared<LocationClickHandler>(*point);
- }
- }
- _thumbLetter = getResultThumbLetter();
- }
- void Article::initDimensions() {
- _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
- int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : (st::defaultEmojiPan.headerLeft - st::inlineResultsLeft));
- TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
- _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
- int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height);
- int32 descriptionLines = (_withThumb || _url) ? 2 : 3;
- QString description = _result->getLayoutDescription();
- TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
- _description.setText(st::defaultTextStyle, description, descriptionOpts);
- int32 descriptionHeight = qMin(_description.countHeight(textWidth), descriptionLines * st::normalFont->height);
- _minh = titleHeight + descriptionHeight;
- if (_url) _minh += st::normalFont->height;
- if (_withThumb) _minh = qMax(_minh, int32(st::inlineThumbSize));
- _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
- }
- int Article::resizeGetHeight(int width) {
- _width = qMin(width, _maxw);
- if (_url) {
- _urlText = getResultUrl();
- _urlWidth = st::normalFont->width(_urlText);
- int32 textWidth = _width - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : (st::defaultEmojiPan.headerLeft - st::inlineResultsLeft));
- if (_urlWidth > textWidth) {
- _urlText = st::normalFont->elided(_urlText, textWidth);
- _urlWidth = st::normalFont->width(_urlText);
- }
- }
- _height = _minh;
- return _height;
- }
- void Article::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- int32 left = st::defaultEmojiPan.headerLeft - st::inlineResultsLeft;
- if (_withThumb) {
- left = st::inlineThumbSize + st::inlineThumbSkip;
- prepareThumbnail(st::inlineThumbSize, st::inlineThumbSize);
- QRect rthumb(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width));
- if (_thumb.isNull()) {
- if (!hasResultThumb() && !_thumbLetter.isEmpty()) {
- int32 index = (_thumbLetter.at(0).unicode() % 4);
- style::color colors[] = {
- st::msgFile3Bg,
- st::msgFile4Bg,
- st::msgFile2Bg,
- st::msgFile1Bg
- };
- p.fillRect(rthumb, colors[index]);
- if (!_thumbLetter.isEmpty()) {
- p.setFont(st::linksLetterFont);
- p.setPen(st::linksLetterFg);
- p.drawText(rthumb, _thumbLetter, style::al_center);
- }
- } else {
- p.fillRect(rthumb, st::overviewPhotoBg);
- }
- } else {
- p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
- }
- }
- p.setPen(st::inlineTitleFg);
- _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
- int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
- p.setPen(st::inlineDescriptionFg);
- int32 descriptionLines = (_withThumb || _url) ? 2 : 3;
- _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
- if (_url) {
- int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
- p.drawTextLeft(left, st::inlineRowMargin + titleHeight + descriptionHeight, _width, _urlText, _urlWidth);
- }
- if (!context->lastRow) {
- p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
- }
- }
- TextState Article::getState(
- QPoint point,
- StateRequest request) const {
- if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
- return { nullptr, _link };
- }
- auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0;
- if (QRect(left, 0, _width - left, _height).contains(point)) {
- if (_url) {
- auto left = st::inlineThumbSize + st::inlineThumbSkip;
- auto titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
- auto descriptionLines = 2;
- auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
- if (style::rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) {
- return { nullptr, _url };
- }
- }
- return { nullptr, _send };
- }
- return {};
- }
- void Article::prepareThumbnail(int width, int height) const {
- if (!hasResultThumb()) {
- if ((_thumb.width() != width * style::DevicePixelRatio())
- || (_thumb.height() != height * style::DevicePixelRatio())) {
- _thumb = getResultContactAvatar(width, height);
- }
- return;
- }
- const auto origin = fileOrigin();
- const auto thumb = getResultThumb(origin);
- if (!thumb
- || ((_thumb.width() == width * style::DevicePixelRatio())
- && (_thumb.height() == height * style::DevicePixelRatio()))) {
- return;
- }
- auto w = qMax(style::ConvertScale(thumb->width()), 1);
- auto h = qMax(style::ConvertScale(thumb->height()), 1);
- if (w * height > h * width) {
- if (height < h) {
- w = w * height / h;
- h = height;
- }
- } else {
- if (width < w) {
- h = h * width / w;
- w = width;
- }
- }
- _thumb = Image(base::duplicate(*thumb)).pixNoCache(
- QSize(w, h) * style::DevicePixelRatio(),
- {
- .options = Images::Option::TransparentBackground,
- .outer = { width, height },
- });
- }
- Game::Game(not_null<Context*> context, std::shared_ptr<Result> result)
- : ItemBase(context, std::move(result))
- , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
- , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
- countFrameSize();
- }
- void Game::countFrameSize() {
- if (auto document = getResultDocument()) {
- if (document->isAnimation()) {
- auto documentSize = document->dimensions;
- if (documentSize.isEmpty()) {
- documentSize = QSize(st::inlineThumbSize, st::inlineThumbSize);
- }
- auto resizeByHeight1 = (documentSize.width() > documentSize.height()) && (documentSize.height() >= st::inlineThumbSize);
- auto resizeByHeight2 = (documentSize.height() >= documentSize.width()) && (documentSize.width() < st::inlineThumbSize);
- if (resizeByHeight1 || resizeByHeight2) {
- if (documentSize.height() > st::inlineThumbSize) {
- _frameSize = QSize((documentSize.width() * st::inlineThumbSize) / documentSize.height(), st::inlineThumbSize);
- }
- } else {
- if (documentSize.width() > st::inlineThumbSize) {
- _frameSize = QSize(st::inlineThumbSize, (documentSize.height() * st::inlineThumbSize) / documentSize.width());
- }
- }
- if (!_frameSize.width()) {
- _frameSize.setWidth(1);
- }
- if (!_frameSize.height()) {
- _frameSize.setHeight(1);
- }
- }
- }
- }
- void Game::initDimensions() {
- _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
- TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
- _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
- int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
- int32 descriptionLines = 2;
- QString description = _result->getLayoutDescription();
- TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
- _description.setText(st::defaultTextStyle, description, descriptionOpts);
- int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height);
- _minh = titleHeight + descriptionHeight;
- accumulate_max(_minh, st::inlineThumbSize);
- _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
- }
- void Game::setPosition(int32 position) {
- AbstractLayoutItem::setPosition(position);
- if (_position < 0) {
- _gif.reset();
- }
- }
- void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
- int32 left = st::defaultEmojiPan.headerLeft - st::inlineResultsLeft;
- left = st::inlineThumbSize + st::inlineThumbSkip;
- auto rthumb = style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width);
- // Gif thumb
- auto thumbDisplayed = false, radial = false;
- const auto photo = getResultPhoto();
- const auto document = getResultDocument();
- if (document) {
- ensureDataMediaCreated(document);
- } else if (photo) {
- ensureDataMediaCreated(photo);
- }
- auto animatedThumb = document && document->isAnimation();
- if (animatedThumb) {
- _documentMedia->automaticLoad(fileOrigin(), nullptr);
- bool loaded = _documentMedia->loaded(), displayLoading = document->displayLoading();
- if (loaded && !_gif && !_gif.isBad()) {
- auto that = const_cast<Game*>(this);
- that->_gif = Media::Clip::MakeReader(
- _documentMedia->owner()->location(),
- _documentMedia->bytes(),
- [=](Media::Clip::Notification notification) { that->clipCallback(notification); });
- }
- bool animating = (_gif && _gif->started());
- if (displayLoading) {
- if (!_radial) {
- _radial = std::make_unique<Ui::RadialAnimation>([=](crl::time now) {
- return radialAnimationCallback(now);
- });
- }
- if (!_radial->animating()) {
- _radial->start(_documentMedia->progress());
- }
- }
- radial = isRadialAnimation();
- if (animating) {
- const auto pixmap = _gif->current({
- .frame = _frameSize,
- .outer = { st::inlineThumbSize, st::inlineThumbSize },
- }, context->paused ? 0 : context->ms);
- if (_thumb.isNull()) {
- _thumb = pixmap;
- _thumbGood = true;
- }
- p.drawImage(rthumb.topLeft(), pixmap);
- thumbDisplayed = true;
- }
- }
- if (!thumbDisplayed) {
- prepareThumbnail({ st::inlineThumbSize, st::inlineThumbSize });
- if (_thumb.isNull()) {
- p.fillRect(rthumb, st::overviewPhotoBg);
- } else {
- p.drawImage(rthumb.topLeft(), _thumb);
- }
- }
- if (radial) {
- p.fillRect(rthumb, st::msgDateImgBg);
- QRect inner((st::inlineThumbSize - st::inlineRadialSize) / 2, (st::inlineThumbSize - st::inlineRadialSize) / 2, st::inlineRadialSize, st::inlineRadialSize);
- if (radial) {
- p.setOpacity(1);
- QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
- _radial->draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg);
- }
- }
- p.setPen(st::inlineTitleFg);
- _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
- int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
- p.setPen(st::inlineDescriptionFg);
- int32 descriptionLines = 2;
- _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
- if (!context->lastRow) {
- p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
- }
- }
- TextState Game::getState(
- QPoint point,
- StateRequest request) const {
- int left = st::inlineThumbSize + st::inlineThumbSkip;
- if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
- return { nullptr, _send };
- }
- if (QRect(left, 0, _width - left, _height).contains(point)) {
- return { nullptr, _send };
- }
- return {};
- }
- void Game::prepareThumbnail(QSize size) const {
- if (const auto document = getResultDocument()) {
- Assert(_documentMedia != nullptr);
- validateThumbnail(_documentMedia->thumbnail(), size, true);
- validateThumbnail(_documentMedia->thumbnailInline(), size, false);
- } else if (const auto photo = getResultPhoto()) {
- using Data::PhotoSize;
- Assert(_photoMedia != nullptr);
- validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, true);
- validateThumbnail(_photoMedia->image(PhotoSize::Small), size, false);
- validateThumbnail(_photoMedia->thumbnailInline(), size, false);
- }
- }
- void Game::ensureDataMediaCreated(not_null<DocumentData*> document) const {
- if (_documentMedia) {
- return;
- }
- _documentMedia = document->createMediaView();
- _documentMedia->thumbnailWanted(fileOrigin());
- }
- void Game::ensureDataMediaCreated(not_null<PhotoData*> photo) const {
- if (_photoMedia) {
- return;
- }
- _photoMedia = photo->createMediaView();
- _photoMedia->wanted(Data::PhotoSize::Thumbnail, fileOrigin());
- }
- void Game::validateThumbnail(Image *image, QSize size, bool good) const {
- if (!image || (_thumbGood && !good)) {
- return;
- } else if ((_thumb.size() == size * style::DevicePixelRatio())
- && (_thumbGood || !good)) {
- return;
- }
- const auto width = size.width();
- const auto height = size.height();
- auto w = qMax(style::ConvertScale(image->width()), 1);
- auto h = qMax(style::ConvertScale(image->height()), 1);
- auto resizeByHeight1 = (w * height > h * width) && (h >= height);
- auto resizeByHeight2 = (h * width >= w * height) && (w < width);
- if (resizeByHeight1 || resizeByHeight2) {
- if (h > height) {
- w = w * height / h;
- h = height;
- }
- } else {
- if (w > width) {
- h = h * width / w;
- w = width;
- }
- }
- _thumbGood = good;
- _thumb = image->pixNoCache(
- QSize(w, h) * style::DevicePixelRatio(),
- {
- .options = (Images::Option::TransparentBackground
- | (good ? Images::Option() : Images::Option::Blur)),
- .outer = size,
- }).toImage();
- }
- bool Game::isRadialAnimation() const {
- if (_radial) {
- if (_radial->animating()) {
- return true;
- } else {
- ensureDataMediaCreated(getResultDocument());
- if (_documentMedia->loaded()) {
- _radial = nullptr;
- }
- }
- }
- return false;
- }
- void Game::radialAnimationCallback(crl::time now) const {
- const auto document = getResultDocument();
- ensureDataMediaCreated(document);
- const auto updated = [&] {
- return _radial->update(
- _documentMedia->progress(),
- !document->loading() || _documentMedia->loaded(),
- now);
- }();
- if (!anim::Disabled() || updated) {
- update();
- }
- if (!_radial->animating() && _documentMedia->loaded()) {
- _radial = nullptr;
- }
- }
- void Game::unloadHeavyPart() {
- _gif.reset();
- _documentMedia = nullptr;
- _photoMedia = nullptr;
- }
- void Game::clipCallback(Media::Clip::Notification notification) {
- using namespace Media::Clip;
- switch (notification) {
- case Notification::Reinit: {
- if (_gif) {
- if (_gif->state() == State::Error) {
- _gif.setBad();
- } else if (_gif->ready() && !_gif->started()) {
- if (_gif->width() * _gif->height() > kMaxInlineArea) {
- getResultDocument()->dimensions = QSize(
- _gif->width(),
- _gif->height());
- _gif.reset();
- } else {
- _gif->start({
- .frame = _frameSize,
- .outer = { st::inlineThumbSize, st::inlineThumbSize },
- });
- }
- } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
- unloadHeavyPart();
- }
- }
- update();
- } break;
- case Notification::Repaint: {
- if (_gif && !_gif->currentDisplayed()) {
- update();
- }
- } break;
- }
- }
- } // namespace internal
- } // namespace Layout
- } // namespace InlineBots
|