"Fossies" - the Fresh Open Source Software Archive

Member "tdesktop-2.6.1/Telegram/SourceFiles/history/view/media/history_view_photo.cpp" (24 Feb 2021, 25879 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "history_view_photo.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.5.9_vs_2.6.0.

    1 /*
    2 This file is part of Telegram Desktop,
    3 the official desktop application for the Telegram messaging service.
    4 
    5 For license and copyright information please follow this link:
    6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
    7 */
    8 #include "history/view/media/history_view_photo.h"
    9 
   10 #include "layout.h"
   11 #include "history/history_item_components.h"
   12 #include "history/history_item.h"
   13 #include "history/history.h"
   14 #include "history/view/history_view_element.h"
   15 #include "history/view/history_view_cursor_state.h"
   16 #include "history/view/media/history_view_media_common.h"
   17 #include "media/streaming/media_streaming_instance.h"
   18 #include "media/streaming/media_streaming_player.h"
   19 #include "media/streaming/media_streaming_document.h"
   20 #include "main/main_session.h"
   21 #include "main/main_session_settings.h"
   22 #include "ui/image/image.h"
   23 #include "ui/grouped_layout.h"
   24 #include "ui/cached_round_corners.h"
   25 #include "data/data_session.h"
   26 #include "data/data_streaming.h"
   27 #include "data/data_photo.h"
   28 #include "data/data_photo_media.h"
   29 #include "data/data_file_origin.h"
   30 #include "data/data_auto_download.h"
   31 #include "core/application.h"
   32 #include "styles/style_chat.h"
   33 
   34 namespace HistoryView {
   35 namespace {
   36 
   37 using Data::PhotoSize;
   38 
   39 } // namespace
   40 
   41 struct Photo::Streamed {
   42     explicit Streamed(std::shared_ptr<::Media::Streaming::Document> shared);
   43     ::Media::Streaming::Instance instance;
   44     QImage frozenFrame;
   45 };
   46 
   47 Photo::Streamed::Streamed(
   48     std::shared_ptr<::Media::Streaming::Document> shared)
   49 : instance(std::move(shared), nullptr) {
   50 }
   51 
   52 Photo::Photo(
   53     not_null<Element*> parent,
   54     not_null<HistoryItem*> realParent,
   55     not_null<PhotoData*> photo)
   56 : File(parent, realParent)
   57 , _data(photo)
   58 , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
   59     _caption = createCaption(realParent);
   60     create(realParent->fullId());
   61 }
   62 
   63 Photo::Photo(
   64     not_null<Element*> parent,
   65     not_null<PeerData*> chat,
   66     not_null<PhotoData*> photo,
   67     int width)
   68 : File(parent, parent->data())
   69 , _data(photo)
   70 , _serviceWidth(width) {
   71     create(parent->data()->fullId(), chat);
   72 }
   73 
   74 Photo::~Photo() {
   75     if (_streamed || _dataMedia) {
   76         if (_streamed) {
   77             _data->owner().streaming().keepAlive(_data);
   78             stopAnimation();
   79         }
   80         if (_dataMedia) {
   81             _data->owner().keepAlive(base::take(_dataMedia));
   82             _parent->checkHeavyPart();
   83         }
   84     }
   85 }
   86 
   87 void Photo::create(FullMsgId contextId, PeerData *chat) {
   88     setLinks(
   89         std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat),
   90         std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat),
   91         std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat));
   92     if ((_dataMedia = _data->activeMediaView())) {
   93         dataMediaCreated();
   94     } else if (_data->inlineThumbnailBytes().isEmpty()
   95         && (_data->hasExact(PhotoSize::Small)
   96             || _data->hasExact(PhotoSize::Thumbnail))) {
   97         _data->load(PhotoSize::Small, contextId);
   98     }
   99 }
  100 
  101 void Photo::ensureDataMediaCreated() const {
  102     if (_dataMedia) {
  103         return;
  104     }
  105     _dataMedia = _data->createMediaView();
  106     dataMediaCreated();
  107 }
  108 
  109 void Photo::dataMediaCreated() const {
  110     Expects(_dataMedia != nullptr);
  111 
  112     if (_data->inlineThumbnailBytes().isEmpty()
  113         && !_dataMedia->image(PhotoSize::Large)
  114         && !_dataMedia->image(PhotoSize::Thumbnail)) {
  115         _dataMedia->wanted(PhotoSize::Small, _realParent->fullId());
  116     }
  117     history()->owner().registerHeavyViewPart(_parent);
  118 }
  119 
  120 bool Photo::hasHeavyPart() const {
  121     return _streamed || _dataMedia;
  122 }
  123 
  124 void Photo::unloadHeavyPart() {
  125     stopAnimation();
  126     _dataMedia = nullptr;
  127 }
  128 
  129 QSize Photo::countOptimalSize() {
  130     if (_parent->media() != this) {
  131         _caption = Ui::Text::String();
  132     } else if (_caption.hasSkipBlock()) {
  133         _caption.updateSkipBlock(
  134             _parent->skipBlockWidth(),
  135             _parent->skipBlockHeight());
  136     }
  137 
  138     auto maxWidth = 0;
  139     auto minHeight = 0;
  140 
  141     auto tw = style::ConvertScale(_data->width());
  142     auto th = style::ConvertScale(_data->height());
  143     if (!tw || !th) {
  144         tw = th = 1;
  145     }
  146     if (tw > st::maxMediaSize) {
  147         th = (st::maxMediaSize * th) / tw;
  148         tw = st::maxMediaSize;
  149     }
  150     if (th > st::maxMediaSize) {
  151         tw = (st::maxMediaSize * tw) / th;
  152         th = st::maxMediaSize;
  153     }
  154 
  155     if (_serviceWidth > 0) {
  156         return { _serviceWidth, _serviceWidth };
  157     }
  158     const auto minWidth = qMax(
  159         (_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
  160         _parent->minWidthForMedia());
  161     const auto maxActualWidth = qMax(tw, minWidth);
  162     maxWidth = qMax(maxActualWidth, th);
  163     minHeight = qMax(th, st::minPhotoSize);
  164     if (_parent->hasBubble() && !_caption.isEmpty()) {
  165         auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right();
  166         minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
  167         if (isBubbleBottom()) {
  168             minHeight += st::msgPadding.bottom();
  169         }
  170     }
  171     return { maxWidth, minHeight };
  172 }
  173 
  174 QSize Photo::countCurrentSize(int newWidth) {
  175     auto tw = style::ConvertScale(_data->width());
  176     auto th = style::ConvertScale(_data->height());
  177     if (tw > st::maxMediaSize) {
  178         th = (st::maxMediaSize * th) / tw;
  179         tw = st::maxMediaSize;
  180     }
  181     if (th > st::maxMediaSize) {
  182         tw = (st::maxMediaSize * tw) / th;
  183         th = st::maxMediaSize;
  184     }
  185 
  186     _pixw = qMin(newWidth, maxWidth());
  187     _pixh = th;
  188     if (tw > _pixw) {
  189         _pixh = (_pixw * _pixh / tw);
  190     } else {
  191         _pixw = tw;
  192     }
  193     if (_pixh > newWidth) {
  194         _pixw = (_pixw * newWidth) / _pixh;
  195         _pixh = newWidth;
  196     }
  197     if (_pixw < 1) _pixw = 1;
  198     if (_pixh < 1) _pixh = 1;
  199 
  200     auto minWidth = qMax(
  201         (_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
  202         _parent->minWidthForMedia());
  203     newWidth = qMax(_pixw, minWidth);
  204     auto newHeight = qMax(_pixh, st::minPhotoSize);
  205     if (_parent->hasBubble() && !_caption.isEmpty()) {
  206         const auto captionw = newWidth
  207             - st::msgPadding.left()
  208             - st::msgPadding.right();
  209         newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
  210         if (isBubbleBottom()) {
  211             newHeight += st::msgPadding.bottom();
  212         }
  213     }
  214     return { newWidth, newHeight };
  215 }
  216 
  217 void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
  218     if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
  219 
  220     ensureDataMediaCreated();
  221     _dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
  222     auto selected = (selection == FullSelection);
  223     auto loaded = _dataMedia->loaded();
  224     auto displayLoading = _data->displayLoading();
  225 
  226     auto inWebPage = (_parent->media() != this);
  227     auto paintx = 0, painty = 0, paintw = width(), painth = height();
  228     auto bubble = _parent->hasBubble();
  229 
  230     auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
  231 
  232     if (displayLoading) {
  233         ensureAnimation();
  234         if (!_animation->radial.animating()) {
  235             _animation->radial.start(_dataMedia->progress());
  236         }
  237     }
  238     const auto radial = isRadialAnimation();
  239 
  240     auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
  241     if (_serviceWidth > 0) {
  242         paintUserpicFrame(p, rthumb.topLeft(), selected);
  243     } else {
  244         if (bubble) {
  245             if (!_caption.isEmpty()) {
  246                 painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
  247                 if (isBubbleBottom()) {
  248                     painth -= st::msgPadding.bottom();
  249                 }
  250                 rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
  251             }
  252         } else {
  253             Ui::FillRoundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? Ui::InSelectedShadowCorners : Ui::InShadowCorners);
  254         }
  255         auto inWebPage = (_parent->media() != this);
  256         auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
  257         auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
  258             | ((isRoundedInBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
  259         const auto pix = [&] {
  260             if (const auto large = _dataMedia->image(PhotoSize::Large)) {
  261                 return large->pixSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
  262             } else if (const auto thumbnail = _dataMedia->image(
  263                     PhotoSize::Thumbnail)) {
  264                 return thumbnail->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
  265             } else if (const auto small = _dataMedia->image(
  266                     PhotoSize::Small)) {
  267                 return small->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
  268             } else if (const auto blurred = _dataMedia->thumbnailInline()) {
  269                 return blurred->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
  270             } else {
  271                 return QPixmap();
  272             }
  273         }();
  274         p.drawPixmap(rthumb.topLeft(), pix);
  275         if (selected) {
  276             Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners);
  277         }
  278     }
  279     if (radial || (!loaded && !_data->loading())) {
  280         const auto radialOpacity = (radial && loaded && !_data->uploading())
  281             ? _animation->radial.opacity() :
  282             1.;
  283         const auto innerSize = st::msgFileLayout.thumbSize;
  284         QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
  285         p.setPen(Qt::NoPen);
  286         if (selected) {
  287             p.setBrush(st::msgDateImgBgSelected);
  288         } else if (isThumbAnimation()) {
  289             auto over = _animation->a_thumbOver.value(1.);
  290             p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
  291         } else {
  292             auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
  293             p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
  294         }
  295 
  296         p.setOpacity(radialOpacity * p.opacity());
  297 
  298         {
  299             PainterHighQualityEnabler hq(p);
  300             p.drawEllipse(inner);
  301         }
  302 
  303         p.setOpacity(radialOpacity);
  304         auto icon = [&]() -> const style::icon* {
  305             if (radial || _data->loading()) {
  306                 return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
  307             }
  308             return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
  309         }();
  310         if (icon) {
  311             icon->paintInCenter(p, inner);
  312         }
  313         p.setOpacity(1);
  314         if (radial) {
  315             QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
  316             _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
  317         }
  318     }
  319 
  320     // date
  321     if (!_caption.isEmpty()) {
  322         auto outbg = _parent->hasOutLayout();
  323         p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
  324         _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
  325     } else if (!inWebPage) {
  326         auto fullRight = paintx + paintw;
  327         auto fullBottom = painty + painth;
  328         if (needInfoDisplay()) {
  329             _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image);
  330         }
  331         if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
  332             auto fastShareLeft = (fullRight + st::historyFastShareLeft);
  333             auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
  334             _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
  335         }
  336     }
  337 }
  338 
  339 void Photo::paintUserpicFrame(
  340         Painter &p,
  341         QPoint photoPosition,
  342         bool selected) const {
  343     const auto autoplay = _data->videoCanBePlayed() && videoAutoplayEnabled();
  344     const auto startPlay = autoplay && !_streamed;
  345     if (startPlay) {
  346         const_cast<Photo*>(this)->playAnimation(true);
  347     } else {
  348         checkStreamedIsStarted();
  349     }
  350 
  351     const auto size = QSize{ _pixw, _pixh };
  352     const auto rect = QRect(photoPosition, size);
  353 
  354     if (_streamed
  355         && _streamed->instance.player().ready()
  356         && !_streamed->instance.player().videoSize().isEmpty()) {
  357         const auto paused = _parent->delegate()->elementIsGifPaused();
  358         auto request = ::Media::Streaming::FrameRequest();
  359         request.outer = size * cIntRetinaFactor();
  360         request.resize = size * cIntRetinaFactor();
  361         request.radius = ImageRoundRadius::Ellipse;
  362         if (_streamed->instance.playerLocked()) {
  363             if (_streamed->frozenFrame.isNull()) {
  364                 _streamed->frozenFrame = _streamed->instance.frame(request);
  365             }
  366             p.drawImage(rect, _streamed->frozenFrame);
  367         } else {
  368             _streamed->frozenFrame = QImage();
  369             p.drawImage(rect, _streamed->instance.frame(request));
  370             if (!paused) {
  371                 _streamed->instance.markFrameShown();
  372             }
  373         }
  374         return;
  375     }
  376     const auto pix = [&] {
  377         if (const auto large = _dataMedia->image(PhotoSize::Large)) {
  378             return large->pixCircled(_pixw, _pixh);
  379         } else if (const auto thumbnail = _dataMedia->image(
  380                 PhotoSize::Thumbnail)) {
  381             return thumbnail->pixBlurredCircled(_pixw, _pixh);
  382         } else if (const auto small = _dataMedia->image(
  383                 PhotoSize::Small)) {
  384             return small->pixBlurredCircled(_pixw, _pixh);
  385         } else if (const auto blurred = _dataMedia->thumbnailInline()) {
  386             return blurred->pixBlurredCircled(_pixw, _pixh);
  387         } else {
  388             return QPixmap();
  389         }
  390     }();
  391     p.drawPixmap(rect, pix);
  392 
  393     if (_data->videoCanBePlayed() && !_streamed) {
  394         const auto innerSize = st::msgFileLayout.thumbSize;
  395         auto inner = QRect(rect.x() + (rect.width() - innerSize) / 2, rect.y() + (rect.height() - innerSize) / 2, innerSize, innerSize);
  396         p.setPen(Qt::NoPen);
  397         if (selected) {
  398             p.setBrush(st::msgDateImgBgSelected);
  399         } else {
  400             const auto over = ClickHandler::showAsActive(_openl);
  401             p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
  402         }
  403         {
  404             PainterHighQualityEnabler hq(p);
  405             p.drawEllipse(inner);
  406         }
  407         const auto icon = [&]() -> const style::icon * {
  408             return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
  409         }();
  410         if (icon) {
  411             icon->paintInCenter(p, inner);
  412         }
  413     }
  414 }
  415 
  416 TextState Photo::textState(QPoint point, StateRequest request) const {
  417     auto result = TextState(_parent);
  418 
  419     if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
  420         return result;
  421     }
  422     auto paintx = 0, painty = 0, paintw = width(), painth = height();
  423     auto bubble = _parent->hasBubble();
  424 
  425     if (bubble && !_caption.isEmpty()) {
  426         const auto captionw = paintw
  427             - st::msgPadding.left()
  428             - st::msgPadding.right();
  429         painth -= _caption.countHeight(captionw);
  430         if (isBubbleBottom()) {
  431             painth -= st::msgPadding.bottom();
  432         }
  433         if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
  434             result = TextState(_parent, _caption.getState(
  435                 point - QPoint(st::msgPadding.left(), painth),
  436                 captionw,
  437                 request.forText()));
  438             return result;
  439         }
  440         painth -= st::mediaCaptionSkip;
  441     }
  442     if (QRect(paintx, painty, paintw, painth).contains(point)) {
  443         ensureDataMediaCreated();
  444         if (_data->uploading()) {
  445             result.link = _cancell;
  446         } else if (_dataMedia->loaded()) {
  447             result.link = _openl;
  448         } else if (_data->loading()) {
  449             result.link = _cancell;
  450         } else {
  451             result.link = _savel;
  452         }
  453     }
  454     if (_caption.isEmpty() && _parent->media() == this) {
  455         auto fullRight = paintx + paintw;
  456         auto fullBottom = painty + painth;
  457         if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
  458             result.cursor = CursorState::Date;
  459         }
  460         if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
  461             auto fastShareLeft = (fullRight + st::historyFastShareLeft);
  462             auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
  463             if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) {
  464                 result.link = _parent->rightActionLink();
  465             }
  466         }
  467     }
  468     return result;
  469 }
  470 
  471 QSize Photo::sizeForGroupingOptimal(int maxWidth) const {
  472     const auto width = _data->width();
  473     const auto height = _data->height();
  474     return { std::max(width, 1), std::max(height, 1) };
  475 }
  476 
  477 QSize Photo::sizeForGrouping(int width) const {
  478     return sizeForGroupingOptimal(width);
  479 }
  480 
  481 void Photo::drawGrouped(
  482         Painter &p,
  483         const QRect &clip,
  484         TextSelection selection,
  485         crl::time ms,
  486         const QRect &geometry,
  487         RectParts sides,
  488         RectParts corners,
  489         float64 highlightOpacity,
  490         not_null<uint64*> cacheKey,
  491         not_null<QPixmap*> cache) const {
  492     ensureDataMediaCreated();
  493     _dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
  494 
  495     validateGroupedCache(geometry, corners, cacheKey, cache);
  496 
  497     const auto selected = (selection == FullSelection);
  498     const auto loaded = _dataMedia->loaded();
  499     const auto displayLoading = _data->displayLoading();
  500     const auto bubble = _parent->hasBubble();
  501 
  502     if (displayLoading) {
  503         ensureAnimation();
  504         if (!_animation->radial.animating()) {
  505             _animation->radial.start(_dataMedia->progress());
  506         }
  507     }
  508     const auto radial = isRadialAnimation();
  509 
  510     if (!bubble) {
  511 //      App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
  512     }
  513     p.drawPixmap(geometry.topLeft(), *cache);
  514 
  515     const auto overlayOpacity = selected
  516         ? (1. - highlightOpacity)
  517         : highlightOpacity;
  518     if (overlayOpacity > 0.) {
  519         p.setOpacity(overlayOpacity);
  520         const auto roundRadius = ImageRoundRadius::Large;
  521         Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
  522         if (!selected) {
  523             Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
  524         }
  525         p.setOpacity(1.);
  526     }
  527 
  528     const auto displayState = radial
  529         || (!loaded && !_data->loading())
  530         || _data->waitingForAlbum();
  531     if (displayState) {
  532         const auto radialOpacity = radial
  533             ? _animation->radial.opacity()
  534             : 1.;
  535         const auto backOpacity = (loaded && !_data->uploading())
  536             ? radialOpacity
  537             : 1.;
  538         const auto radialSize = st::historyGroupRadialSize;
  539         const auto inner = QRect(
  540             geometry.x() + (geometry.width() - radialSize) / 2,
  541             geometry.y() + (geometry.height() - radialSize) / 2,
  542             radialSize,
  543             radialSize);
  544         p.setPen(Qt::NoPen);
  545         if (selected) {
  546             p.setBrush(st::msgDateImgBgSelected);
  547         } else if (isThumbAnimation()) {
  548             auto over = _animation->a_thumbOver.value(1.);
  549             p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
  550         } else {
  551             auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
  552             p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
  553         }
  554 
  555         p.setOpacity(backOpacity * p.opacity());
  556 
  557         {
  558             PainterHighQualityEnabler hq(p);
  559             p.drawEllipse(inner);
  560         }
  561 
  562         const auto icon = [&]() -> const style::icon* {
  563             if (_data->waitingForAlbum()) {
  564                 return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
  565             } else if (radial || _data->loading()) {
  566                 return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
  567             }
  568             return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
  569         }();
  570         const auto previous = [&]() -> const style::icon* {
  571             if (_data->waitingForAlbum()) {
  572                 return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
  573             }
  574             return nullptr;
  575         }();
  576         p.setOpacity(backOpacity);
  577         if (icon) {
  578             if (previous && radialOpacity > 0. && radialOpacity < 1.) {
  579                 PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner);
  580             } else {
  581                 icon->paintInCenter(p, inner);
  582             }
  583         }
  584         p.setOpacity(1);
  585         if (radial) {
  586             const auto line = st::historyGroupRadialLine;
  587             const auto rinner = inner.marginsRemoved({ line, line, line, line });
  588             const auto color = selected
  589                 ? st::historyFileThumbRadialFgSelected
  590                 : st::historyFileThumbRadialFg;
  591             _animation->radial.draw(p, rinner, line, color);
  592         }
  593     }
  594 }
  595 
  596 TextState Photo::getStateGrouped(
  597         const QRect &geometry,
  598         RectParts sides,
  599         QPoint point,
  600         StateRequest request) const {
  601     if (!geometry.contains(point)) {
  602         return {};
  603     }
  604     ensureDataMediaCreated();
  605     return TextState(_parent, _data->uploading()
  606         ? _cancell
  607         : _dataMedia->loaded()
  608         ? _openl
  609         : _data->loading()
  610         ? _cancell
  611         : _savel);
  612 }
  613 
  614 float64 Photo::dataProgress() const {
  615     ensureDataMediaCreated();
  616     return _dataMedia->progress();
  617 }
  618 
  619 bool Photo::dataFinished() const {
  620     return !_data->loading()
  621         && (!_data->uploading() || _data->waitingForAlbum());
  622 }
  623 
  624 bool Photo::dataLoaded() const {
  625     ensureDataMediaCreated();
  626     return _dataMedia->loaded();
  627 }
  628 
  629 bool Photo::needInfoDisplay() const {
  630     return (_parent->data()->id < 0
  631         || _parent->isUnderCursor()
  632         || _parent->isLastAndSelfMessage());
  633 }
  634 
  635 void Photo::validateGroupedCache(
  636         const QRect &geometry,
  637         RectParts corners,
  638         not_null<uint64*> cacheKey,
  639         not_null<QPixmap*> cache) const {
  640     using Option = Images::Option;
  641 
  642     ensureDataMediaCreated();
  643 
  644     const auto loaded = _dataMedia->loaded();
  645     const auto loadLevel = loaded
  646         ? 2
  647         : (_dataMedia->thumbnailInline()
  648             || _dataMedia->image(PhotoSize::Small)
  649             || _dataMedia->image(PhotoSize::Thumbnail))
  650         ? 1
  651         : 0;
  652     const auto width = geometry.width();
  653     const auto height = geometry.height();
  654     const auto options = Option::Smooth
  655         | Option::RoundedLarge
  656         | (loaded ? Option::None : Option::Blurred)
  657         | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None)
  658         | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None)
  659         | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None)
  660         | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None);
  661     const auto key = (uint64(width) << 48)
  662         | (uint64(height) << 32)
  663         | (uint64(options) << 16)
  664         | (uint64(loadLevel));
  665     if (*cacheKey == key) {
  666         return;
  667     }
  668 
  669     const auto originalWidth = style::ConvertScale(_data->width());
  670     const auto originalHeight = style::ConvertScale(_data->height());
  671     const auto pixSize = Ui::GetImageScaleSizeForGeometry(
  672         { originalWidth, originalHeight },
  673         { width, height });
  674     const auto pixWidth = pixSize.width() * cIntRetinaFactor();
  675     const auto pixHeight = pixSize.height() * cIntRetinaFactor();
  676     const auto image = _dataMedia->image(PhotoSize::Large)
  677         ? _dataMedia->image(PhotoSize::Large)
  678         : _dataMedia->image(PhotoSize::Thumbnail)
  679         ? _dataMedia->image(PhotoSize::Thumbnail)
  680         : _dataMedia->image(PhotoSize::Small)
  681         ? _dataMedia->image(PhotoSize::Small)
  682         : _dataMedia->thumbnailInline()
  683         ? _dataMedia->thumbnailInline()
  684         : Image::BlankMedia().get();
  685 
  686     *cacheKey = key;
  687     *cache = image->pixNoCache(pixWidth, pixHeight, options, width, height);
  688 }
  689 
  690 bool Photo::createStreamingObjects() {
  691     using namespace ::Media::Streaming;
  692 
  693     setStreamed(std::make_unique<Streamed>(
  694         history()->owner().streaming().sharedDocument(
  695             _data,
  696             _realParent->fullId())));
  697     _streamed->instance.player().updates(
  698     ) | rpl::start_with_next_error([=](Update &&update) {
  699         handleStreamingUpdate(std::move(update));
  700     }, [=](Error &&error) {
  701         handleStreamingError(std::move(error));
  702     }, _streamed->instance.lifetime());
  703     if (_streamed->instance.ready()) {
  704         streamingReady(base::duplicate(_streamed->instance.info()));
  705     }
  706     if (!_streamed->instance.valid()) {
  707         stopAnimation();
  708         return false;
  709     }
  710     checkStreamedIsStarted();
  711     return true;
  712 }
  713 
  714 void Photo::setStreamed(std::unique_ptr<Streamed> value) {
  715     const auto removed = (_streamed && !value);
  716     const auto set = (!_streamed && value);
  717     _streamed = std::move(value);
  718     if (set) {
  719         history()->owner().registerHeavyViewPart(_parent);
  720     } else if (removed) {
  721         _parent->checkHeavyPart();
  722     }
  723 }
  724 
  725 void Photo::handleStreamingUpdate(::Media::Streaming::Update &&update) {
  726     using namespace ::Media::Streaming;
  727 
  728     v::match(update.data, [&](Information &update) {
  729         streamingReady(std::move(update));
  730     }, [&](const PreloadedVideo &update) {
  731     }, [&](const UpdateVideo &update) {
  732         repaintStreamedContent();
  733     }, [&](const PreloadedAudio &update) {
  734     }, [&](const UpdateAudio &update) {
  735     }, [&](const WaitingForData &update) {
  736     }, [&](MutedByOther) {
  737     }, [&](Finished) {
  738     });
  739 }
  740 
  741 void Photo::handleStreamingError(::Media::Streaming::Error &&error) {
  742     _data->setVideoPlaybackFailed();
  743     stopAnimation();
  744 }
  745 
  746 void Photo::repaintStreamedContent() {
  747     if (_streamed && !_streamed->frozenFrame.isNull()) {
  748         return;
  749     } else if (_parent->delegate()->elementIsGifPaused()) {
  750         return;
  751     }
  752     history()->owner().requestViewRepaint(_parent);
  753 }
  754 
  755 void Photo::streamingReady(::Media::Streaming::Information &&info) {
  756     history()->owner().requestViewRepaint(_parent);
  757 }
  758 
  759 void Photo::checkAnimation() {
  760     if (_streamed && !videoAutoplayEnabled()) {
  761         stopAnimation();
  762     }
  763 }
  764 
  765 void Photo::stopAnimation() {
  766     setStreamed(nullptr);
  767 }
  768 
  769 void Photo::playAnimation(bool autoplay) {
  770     ensureDataMediaCreated();
  771     if (_streamed && autoplay) {
  772         return;
  773     } else if (_streamed && videoAutoplayEnabled()) {
  774         Core::App().showPhoto(_data, _parent->data());
  775         return;
  776     }
  777     if (_streamed) {
  778         stopAnimation();
  779     } else if (_data->videoCanBePlayed()) {
  780         if (!videoAutoplayEnabled()) {
  781             history()->owner().checkPlayingAnimations();
  782         }
  783         if (!createStreamingObjects()) {
  784             _data->setVideoPlaybackFailed();
  785             return;
  786         }
  787     }
  788 }
  789 
  790 void Photo::checkStreamedIsStarted() const {
  791     if (!_streamed) {
  792         return;
  793     } else if (_streamed->instance.paused()) {
  794         _streamed->instance.resume();
  795     }
  796     if (_streamed
  797         && !_streamed->instance.active()
  798         && !_streamed->instance.failed()) {
  799         const auto position = _data->videoStartPosition();
  800         auto options = ::Media::Streaming::PlaybackOptions();
  801         options.position = position;
  802         options.mode = ::Media::Streaming::Mode::Video;
  803         options.loop = true;
  804         _streamed->instance.play(options);
  805     }
  806 }
  807 
  808 bool Photo::videoAutoplayEnabled() const {
  809     return Data::AutoDownload::ShouldAutoPlay(
  810         _data->session().settings().autoDownload(),
  811         _realParent->history()->peer,
  812         _data);
  813 }
  814 
  815 TextForMimeData Photo::selectedText(TextSelection selection) const {
  816     return _caption.toTextForMimeData(selection);
  817 }
  818 
  819 bool Photo::needsBubble() const {
  820     if (!_caption.isEmpty()) {
  821         return true;
  822     }
  823     const auto item = _parent->data();
  824     if (item->toHistoryMessage()) {
  825         return item->repliesAreComments()
  826             || item->externalReply()
  827             || item->viaBot()
  828             || _parent->displayedReply()
  829             || _parent->displayForwardedFrom()
  830             || _parent->displayFromName();
  831     }
  832     return false;
  833 }
  834 
  835 bool Photo::isReadyForOpen() const {
  836     ensureDataMediaCreated();
  837     return _dataMedia->loaded();
  838 }
  839 
  840 void Photo::parentTextUpdated() {
  841     _caption = (_parent->media() == this)
  842         ? createCaption(_parent->data())
  843         : Ui::Text::String();
  844     history()->owner().requestViewResize(_parent);
  845 }
  846 
  847 } // namespace HistoryView