inline_bot_layout_internal.cpp 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "inline_bots/inline_bot_layout_internal.h"
  8. #include "data/data_photo.h"
  9. #include "data/data_document.h"
  10. #include "data/data_session.h"
  11. #include "data/data_file_origin.h"
  12. #include "data/data_photo_media.h"
  13. #include "data/data_document_media.h"
  14. #include "data/stickers/data_stickers.h"
  15. #include "chat_helpers/gifs_list_widget.h" // ChatHelpers::AddGifAction.
  16. #include "chat_helpers/stickers_lottie.h"
  17. #include "inline_bots/inline_bot_result.h"
  18. #include "lottie/lottie_single_player.h"
  19. #include "media/audio/media_audio.h"
  20. #include "media/clip/media_clip_reader.h"
  21. #include "media/player/media_player_instance.h"
  22. #include "history/history_location_manager.h"
  23. #include "history/view/history_view_cursor_state.h"
  24. #include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
  25. #include "history/view/media/history_view_media_common.h"
  26. #include "ui/image/image.h"
  27. #include "ui/text/format_values.h"
  28. #include "ui/cached_round_corners.h"
  29. #include "ui/painter.h"
  30. #include "main/main_session.h"
  31. #include "lang/lang_keys.h"
  32. #include "styles/style_overview.h"
  33. #include "styles/style_chat.h"
  34. #include "styles/style_chat_helpers.h"
  35. #include "styles/style_widgets.h"
  36. namespace InlineBots {
  37. namespace Layout {
  38. namespace internal {
  39. using TextState = HistoryView::TextState;
  40. constexpr auto kMaxInlineArea = 1280 * 720;
  41. [[nodiscard]] bool CanPlayInline(not_null<DocumentData*> document) {
  42. const auto dimensions = document->dimensions;
  43. return dimensions.width() * dimensions.height() <= kMaxInlineArea;
  44. }
  45. FileBase::FileBase(not_null<Context*> context, std::shared_ptr<Result> result)
  46. : ItemBase(context, std::move(result)) {
  47. }
  48. FileBase::FileBase(
  49. not_null<Context*> context,
  50. not_null<DocumentData*> document)
  51. : ItemBase(context, document) {
  52. }
  53. DocumentData *FileBase::getShownDocument() const {
  54. if (const auto result = getDocument()) {
  55. return result;
  56. }
  57. return getResultDocument();
  58. }
  59. int FileBase::content_width() const {
  60. if (const auto document = getShownDocument()) {
  61. if (document->dimensions.width() > 0) {
  62. return document->dimensions.width();
  63. }
  64. return style::ConvertScale(document->thumbnailLocation().width());
  65. }
  66. return 0;
  67. }
  68. int FileBase::content_height() const {
  69. if (const auto document = getShownDocument()) {
  70. if (document->dimensions.height() > 0) {
  71. return document->dimensions.height();
  72. }
  73. return style::ConvertScale(document->thumbnailLocation().height());
  74. }
  75. return 0;
  76. }
  77. int FileBase::content_duration() const {
  78. if (const auto document = getShownDocument()) {
  79. if (document->hasDuration()) {
  80. return document->duration() / 1000;
  81. }
  82. }
  83. return getResultDuration();
  84. }
  85. Gif::Gif(not_null<Context*> context, std::shared_ptr<Result> result)
  86. : FileBase(context, std::move(result)) {
  87. Expects(getResultDocument() != nullptr);
  88. }
  89. Gif::Gif(
  90. not_null<Context*> context,
  91. not_null<DocumentData*> document,
  92. bool hasDeleteButton)
  93. : FileBase(context, document) {
  94. if (hasDeleteButton) {
  95. _delete = std::make_shared<DeleteSavedGifClickHandler>(document);
  96. }
  97. }
  98. void Gif::initDimensions() {
  99. int32 w = content_width(), h = content_height();
  100. if (w <= 0 || h <= 0) {
  101. _maxw = 0;
  102. } else {
  103. w = w * st::inlineMediaHeight / h;
  104. _maxw = qMax(w, int32(st::inlineResultsMinWidth));
  105. }
  106. _minh = st::inlineMediaHeight + st::inlineResultsSkip;
  107. }
  108. void Gif::setPosition(int32 position) {
  109. AbstractLayoutItem::setPosition(position);
  110. if (_position < 0) {
  111. _gif.reset();
  112. }
  113. }
  114. void DeleteSavedGifClickHandler::onClickImpl() const {
  115. ChatHelpers::AddGifAction(
  116. [](QString, Fn<void()> &&done, const style::icon*) { done(); },
  117. nullptr,
  118. _data);
  119. }
  120. int Gif::resizeGetHeight(int width) {
  121. _width = width;
  122. _height = _minh;
  123. return _height;
  124. }
  125. QRect Gif::innerContentRect() const {
  126. ensureDataMediaCreated(getShownDocument());
  127. const auto size = (!_thumb.isNull())
  128. ? (_thumb.size() / style::DevicePixelRatio())
  129. : countFrameSize();
  130. return QRect(QPoint(), size);
  131. }
  132. void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  133. const auto document = getShownDocument();
  134. ensureDataMediaCreated(document);
  135. const auto preview = Data::VideoPreviewState(_dataMedia.get());
  136. preview.automaticLoad(fileOrigin());
  137. const auto displayLoading = !preview.usingThumbnail()
  138. && document->displayLoading();
  139. const auto loaded = preview.loaded();
  140. const auto loading = preview.loading();
  141. if (loaded
  142. && !_gif
  143. && !_gif.isBad()
  144. && CanPlayInline(document)) {
  145. auto that = const_cast<Gif*>(this);
  146. that->_gif = preview.makeAnimation([=](
  147. Media::Clip::Notification notification) {
  148. that->clipCallback(notification);
  149. });
  150. }
  151. const auto animating = (_gif && _gif->started());
  152. if (displayLoading) {
  153. ensureAnimation();
  154. if (!_animation->radial.animating()) {
  155. _animation->radial.start(_dataMedia->progress());
  156. }
  157. }
  158. const auto radial = isRadialAnimation();
  159. const auto frame = countFrameSize();
  160. const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
  161. if (animating) {
  162. const auto pixmap = _gif->current({
  163. .frame = frame,
  164. .outer = r.size(),
  165. }, context->paused ? 0 : context->ms);
  166. if (_thumb.isNull()) {
  167. _thumb = pixmap;
  168. _thumbGood = true;
  169. }
  170. p.drawImage(r.topLeft(), pixmap);
  171. } else {
  172. prepareThumbnail(r.size(), frame);
  173. if (_thumb.isNull()) {
  174. p.fillRect(r, st::overviewPhotoBg);
  175. } else {
  176. p.drawImage(r.topLeft(), _thumb);
  177. }
  178. }
  179. if (radial
  180. || _gif.isBad()
  181. || (!_gif && !loaded && !loading && !preview.usingThumbnail())) {
  182. auto radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1.;
  183. if (_animation && _animation->_a_over.animating()) {
  184. auto over = _animation->_a_over.value(1.);
  185. p.fillRect(r, anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
  186. } else {
  187. auto over = (_state & StateFlag::Over);
  188. p.fillRect(r, over ? st::msgDateImgBgOver : st::msgDateImgBg);
  189. }
  190. p.setOpacity(radialOpacity * p.opacity());
  191. p.setOpacity(radialOpacity);
  192. auto icon = [&] {
  193. if (radial || loading) {
  194. return &st::historyFileInCancel;
  195. } else if (loaded) {
  196. return &st::historyFileInPlay;
  197. }
  198. return &st::historyFileInDownload;
  199. }();
  200. const auto size = st::inlineRadialSize;
  201. QRect inner(
  202. (r.width() - size) / 2,
  203. (r.height() - size) / 2,
  204. size,
  205. size);
  206. icon->paintInCenter(p, inner);
  207. if (radial) {
  208. p.setOpacity(1);
  209. QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
  210. _animation->radial.draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg);
  211. }
  212. }
  213. if (_delete && (_state & StateFlag::Over)) {
  214. auto deleteSelected = (_state & StateFlag::DeleteOver);
  215. auto deletePos = QPoint(_width - st::stickerPanDeleteIconBg.width(), 0);
  216. p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg);
  217. st::stickerPanDeleteIconBg.paint(p, deletePos, width());
  218. p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg);
  219. st::stickerPanDeleteIconFg.paint(p, deletePos, width());
  220. p.setOpacity(1.);
  221. }
  222. }
  223. TextState Gif::getState(
  224. QPoint point,
  225. StateRequest request) const {
  226. if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
  227. if (_delete && style::rtlpoint(point, _width).x() >= _width - st::stickerPanDeleteIconBg.width() && point.y() < st::stickerPanDeleteIconBg.height()) {
  228. return { nullptr, _delete };
  229. } else {
  230. return { nullptr, _send };
  231. }
  232. }
  233. return {};
  234. }
  235. void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
  236. if (!p) return;
  237. if (_delete && p == _delete) {
  238. bool wasactive = (_state & StateFlag::DeleteOver);
  239. if (active != wasactive) {
  240. auto from = active ? 0. : 1., to = active ? 1. : 0.;
  241. _a_deleteOver.start([this] { update(); }, from, to, st::stickersRowDuration);
  242. if (active) {
  243. _state |= StateFlag::DeleteOver;
  244. } else {
  245. _state &= ~StateFlag::DeleteOver;
  246. }
  247. }
  248. }
  249. if (p == _delete || p == _send) {
  250. bool wasactive = (_state & StateFlag::Over);
  251. if (active != wasactive) {
  252. ensureDataMediaCreated(getShownDocument());
  253. const auto preview = Data::VideoPreviewState(_dataMedia.get());
  254. if (!preview.usingThumbnail() && !preview.loaded()) {
  255. ensureAnimation();
  256. auto from = active ? 0. : 1., to = active ? 1. : 0.;
  257. _animation->_a_over.start([=] { update(); }, from, to, st::stickersRowDuration);
  258. }
  259. if (active) {
  260. _state |= StateFlag::Over;
  261. } else {
  262. _state &= ~StateFlag::Over;
  263. }
  264. }
  265. }
  266. ItemBase::clickHandlerActiveChanged(p, active);
  267. }
  268. QSize Gif::countFrameSize() const {
  269. bool animating = (_gif && _gif->ready());
  270. int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight;
  271. if (framew * height > frameh * _width) {
  272. if (framew < st::maxStickerSize || frameh > height) {
  273. if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) {
  274. framew = framew * height / frameh;
  275. frameh = height;
  276. } else {
  277. frameh = int32(frameh * st::maxStickerSize) / framew;
  278. framew = st::maxStickerSize;
  279. }
  280. }
  281. } else {
  282. if (frameh < st::maxStickerSize || framew > _width) {
  283. if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) {
  284. frameh = frameh * _width / framew;
  285. framew = _width;
  286. } else {
  287. framew = int32(framew * st::maxStickerSize) / frameh;
  288. frameh = st::maxStickerSize;
  289. }
  290. }
  291. }
  292. return QSize(framew, frameh);
  293. }
  294. void Gif::validateThumbnail(
  295. Image *image,
  296. QSize size,
  297. QSize frame,
  298. bool good) const {
  299. if (!image || (_thumbGood && !good)) {
  300. return;
  301. } else if ((_thumb.size() == size * style::DevicePixelRatio())
  302. && (_thumbGood || !good)) {
  303. return;
  304. }
  305. _thumbGood = good;
  306. _thumb = image->pixNoCache(
  307. frame * style::DevicePixelRatio(),
  308. {
  309. .options = (Images::Option::TransparentBackground
  310. | (good ? Images::Option() : Images::Option::Blur)),
  311. .outer = size,
  312. }).toImage();
  313. }
  314. void Gif::prepareThumbnail(QSize size, QSize frame) const {
  315. const auto document = getShownDocument();
  316. Assert(document != nullptr);
  317. ensureDataMediaCreated(document);
  318. validateThumbnail(_dataMedia->thumbnail(), size, frame, true);
  319. validateThumbnail(_dataMedia->thumbnailInline(), size, frame, false);
  320. }
  321. void Gif::ensureDataMediaCreated(not_null<DocumentData*> document) const {
  322. if (_dataMedia) {
  323. return;
  324. }
  325. _dataMedia = document->createMediaView();
  326. _dataMedia->thumbnailWanted(fileOrigin());
  327. _dataMedia->videoThumbnailWanted(fileOrigin());
  328. }
  329. void Gif::ensureAnimation() const {
  330. if (!_animation) {
  331. _animation = std::make_unique<AnimationData>([=](crl::time now) {
  332. radialAnimationCallback(now);
  333. });
  334. }
  335. }
  336. bool Gif::isRadialAnimation() const {
  337. if (_animation) {
  338. if (_animation->radial.animating()) {
  339. return true;
  340. } else {
  341. ensureDataMediaCreated(getShownDocument());
  342. const auto preview = Data::VideoPreviewState(_dataMedia.get());
  343. if (preview.usingThumbnail() || preview.loaded()) {
  344. _animation = nullptr;
  345. }
  346. }
  347. }
  348. return false;
  349. }
  350. void Gif::radialAnimationCallback(crl::time now) const {
  351. const auto document = getShownDocument();
  352. ensureDataMediaCreated(document);
  353. const auto updated = [&] {
  354. return _animation->radial.update(
  355. _dataMedia->progress(),
  356. !document->loading() || _dataMedia->loaded(),
  357. now);
  358. }();
  359. if (!anim::Disabled() || updated) {
  360. update();
  361. }
  362. if (!_animation->radial.animating() && _dataMedia->loaded()) {
  363. _animation = nullptr;
  364. }
  365. }
  366. void Gif::unloadHeavyPart() {
  367. _gif.reset();
  368. _dataMedia = nullptr;
  369. }
  370. void Gif::clipCallback(Media::Clip::Notification notification) {
  371. using namespace Media::Clip;
  372. switch (notification) {
  373. case Notification::Reinit: {
  374. if (_gif) {
  375. if (_gif->state() == State::Error) {
  376. _gif.setBad();
  377. } else if (_gif->ready() && !_gif->started()) {
  378. if (_gif->width() * _gif->height() > kMaxInlineArea) {
  379. getShownDocument()->dimensions = QSize(
  380. _gif->width(),
  381. _gif->height());
  382. _gif.reset();
  383. } else {
  384. _gif->start({
  385. .frame = countFrameSize(),
  386. .outer = { _width, st::inlineMediaHeight },
  387. });
  388. }
  389. } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
  390. unloadHeavyPart();
  391. }
  392. }
  393. update();
  394. } break;
  395. case Notification::Repaint: {
  396. if (_gif && !_gif->currentDisplayed()) {
  397. update();
  398. }
  399. } break;
  400. }
  401. }
  402. Sticker::Sticker(not_null<Context*> context, std::shared_ptr<Result> result)
  403. : FileBase(context, std::move(result)) {
  404. Expects(getResultDocument() != nullptr);
  405. }
  406. Sticker::~Sticker() = default;
  407. void Sticker::initDimensions() {
  408. _maxw = st::stickerPanSize.width();
  409. _minh = st::stickerPanSize.height();
  410. }
  411. void Sticker::preload() const {
  412. const auto document = getShownDocument();
  413. Assert(document != nullptr);
  414. ensureDataMediaCreated(document);
  415. _dataMedia->checkStickerSmall();
  416. }
  417. void Sticker::ensureDataMediaCreated(not_null<DocumentData*> document) const {
  418. if (_dataMedia) {
  419. return;
  420. }
  421. _dataMedia = document->createMediaView();
  422. }
  423. void Sticker::unloadHeavyPart() {
  424. _dataMedia = nullptr;
  425. _lifetime.destroy();
  426. _lottie = nullptr;
  427. _webm = nullptr;
  428. }
  429. QRect Sticker::innerContentRect() const {
  430. ensureDataMediaCreated(getShownDocument());
  431. const auto size = (_lottie && _lottie->ready())
  432. ? (_lottie->frame().size() / style::DevicePixelRatio())
  433. : (!_thumb.isNull())
  434. ? (_thumb.size() / style::DevicePixelRatio())
  435. : getThumbSize();
  436. const auto pos = QPoint(
  437. (st::stickerPanSize.width() - size.width()) / 2,
  438. (st::stickerPanSize.height() - size.height()) / 2);
  439. return QRect(pos, size);
  440. }
  441. void Sticker::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  442. ensureDataMediaCreated(getShownDocument());
  443. auto over = _a_over.value(_active ? 1. : 0.);
  444. if (over > 0) {
  445. p.setOpacity(over);
  446. Ui::FillRoundRect(p, QRect(QPoint(0, 0), st::stickerPanSize), st::emojiPanHover, Ui::StickerHoverCorners);
  447. p.setOpacity(1);
  448. }
  449. prepareThumbnail();
  450. if (_lottie && _lottie->ready()) {
  451. const auto frame = _lottie->frame();
  452. const auto size = frame.size() / style::DevicePixelRatio();
  453. const auto pos = QPoint(
  454. (st::stickerPanSize.width() - size.width()) / 2,
  455. (st::stickerPanSize.height() - size.height()) / 2);
  456. p.drawImage(
  457. QRect(pos, size),
  458. frame);
  459. if (!context->paused) {
  460. _lottie->markFrameShown();
  461. }
  462. } else if (_webm && _webm->started()) {
  463. const auto size = getThumbSize();
  464. const auto frame = _webm->current({
  465. .frame = size,
  466. .keepAlpha = true,
  467. }, context->paused ? 0 : context->ms);
  468. p.drawImage(
  469. (st::stickerPanSize.width() - size.width()) / 2,
  470. (st::stickerPanSize.height() - size.width()) / 2,
  471. frame);
  472. } else if (!_thumb.isNull()) {
  473. const auto w = _thumb.width() / style::DevicePixelRatio();
  474. const auto h = _thumb.height() / style::DevicePixelRatio();
  475. const auto pos = QPoint(
  476. (st::stickerPanSize.width() - w) / 2,
  477. (st::stickerPanSize.height() - h) / 2);
  478. p.drawPixmap(pos, _thumb);
  479. } else if (context->pathGradient) {
  480. const auto thumbSize = getThumbSize();
  481. const auto w = thumbSize.width();
  482. const auto h = thumbSize.height();
  483. ChatHelpers::PaintStickerThumbnailPath(
  484. p,
  485. _dataMedia.get(),
  486. QRect(
  487. (st::stickerPanSize.width() - w) / 2,
  488. (st::stickerPanSize.height() - h) / 2,
  489. w,
  490. h),
  491. context->pathGradient);
  492. }
  493. }
  494. TextState Sticker::getState(
  495. QPoint point,
  496. StateRequest request) const {
  497. if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
  498. return { nullptr, _send };
  499. }
  500. return {};
  501. }
  502. void Sticker::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
  503. if (!p) return;
  504. if (p == _send) {
  505. if (active != _active) {
  506. _active = active;
  507. auto from = active ? 0. : 1., to = active ? 1. : 0.;
  508. _a_over.start([this] { update(); }, from, to, st::stickersRowDuration);
  509. }
  510. }
  511. ItemBase::clickHandlerActiveChanged(p, active);
  512. }
  513. QSize Sticker::boundingBox() const {
  514. const auto size = st::stickerPanSize.width() - st::roundRadiusSmall * 2;
  515. return { size, size };
  516. }
  517. QSize Sticker::getThumbSize() const {
  518. const auto width = qMax(content_width(), 1);
  519. const auto height = qMax(content_height(), 1);
  520. return HistoryView::DownscaledSize({ width, height }, boundingBox());
  521. }
  522. void Sticker::setupLottie() const {
  523. Expects(_dataMedia != nullptr);
  524. _lottie = ChatHelpers::LottiePlayerFromDocument(
  525. _dataMedia.get(),
  526. ChatHelpers::StickerLottieSize::InlineResults,
  527. boundingBox() * style::DevicePixelRatio());
  528. _lottie->updates(
  529. ) | rpl::start_with_next([=] {
  530. update();
  531. }, _lifetime);
  532. }
  533. void Sticker::setupWebm() const {
  534. Expects(_dataMedia != nullptr);
  535. const auto that = const_cast<Sticker*>(this);
  536. auto callback = [=](Media::Clip::Notification notification) {
  537. that->clipCallback(notification);
  538. };
  539. that->_webm = Media::Clip::MakeReader(
  540. _dataMedia->owner()->location(),
  541. _dataMedia->bytes(),
  542. std::move(callback));
  543. }
  544. void Sticker::prepareThumbnail() const {
  545. const auto document = getShownDocument();
  546. Assert(document != nullptr);
  547. ensureDataMediaCreated(document);
  548. const auto sticker = document->sticker();
  549. if (sticker && _dataMedia->loaded()) {
  550. if (!_lottie && sticker->isLottie()) {
  551. setupLottie();
  552. } else if (!_webm && sticker->isWebm()) {
  553. setupWebm();
  554. }
  555. }
  556. _dataMedia->checkStickerSmall();
  557. if (const auto image = _dataMedia->getStickerSmall()) {
  558. if (!_lottie && !_thumbLoaded) {
  559. const auto thumbSize = getThumbSize();
  560. _thumb = image->pix(thumbSize);
  561. _thumbLoaded = true;
  562. }
  563. }
  564. }
  565. void Sticker::clipCallback(Media::Clip::Notification notification) {
  566. using namespace Media::Clip;
  567. switch (notification) {
  568. case Notification::Reinit: {
  569. if (!_webm) {
  570. break;
  571. } else if (_webm->state() == State::Error) {
  572. _webm.setBad();
  573. } else if (_webm->ready() && !_webm->started()) {
  574. _webm->start({
  575. .frame = getThumbSize(),
  576. .keepAlpha = true,
  577. });
  578. } else if (_webm->autoPausedGif()
  579. && !context()->inlineItemVisible(this)) {
  580. unloadHeavyPart();
  581. }
  582. } break;
  583. case Notification::Repaint: break;
  584. }
  585. update();
  586. }
  587. Photo::Photo(not_null<Context*> context, std::shared_ptr<Result> result)
  588. : ItemBase(context, std::move(result)) {
  589. Expects(getShownPhoto() != nullptr);
  590. }
  591. void Photo::initDimensions() {
  592. const auto photo = getShownPhoto();
  593. int32 w = photo->width(), h = photo->height();
  594. if (w <= 0 || h <= 0) {
  595. _maxw = 0;
  596. } else {
  597. w = w * st::inlineMediaHeight / h;
  598. _maxw = qMax(w, int32(st::inlineResultsMinWidth));
  599. }
  600. _minh = st::inlineMediaHeight + st::inlineResultsSkip;
  601. }
  602. void Photo::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  603. int32 height = st::inlineMediaHeight;
  604. QSize frame = countFrameSize();
  605. QRect r(0, 0, _width, height);
  606. prepareThumbnail({ _width, height }, frame);
  607. if (_thumb.isNull()) {
  608. p.fillRect(r, st::overviewPhotoBg);
  609. } else {
  610. p.drawPixmap(r.topLeft(), _thumb);
  611. }
  612. }
  613. TextState Photo::getState(
  614. QPoint point,
  615. StateRequest request) const {
  616. if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
  617. return { nullptr, _send };
  618. }
  619. return {};
  620. }
  621. void Photo::unloadHeavyPart() {
  622. _photoMedia = nullptr;
  623. }
  624. PhotoData *Photo::getShownPhoto() const {
  625. if (const auto result = getPhoto()) {
  626. return result;
  627. }
  628. return getResultPhoto();
  629. }
  630. QSize Photo::countFrameSize() const {
  631. const auto photo = getShownPhoto();
  632. int32 framew = photo->width(), frameh = photo->height(), height = st::inlineMediaHeight;
  633. if (framew * height > frameh * _width) {
  634. if (framew < st::maxStickerSize || frameh > height) {
  635. if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) {
  636. framew = framew * height / frameh;
  637. frameh = height;
  638. } else {
  639. frameh = int32(frameh * st::maxStickerSize) / framew;
  640. framew = st::maxStickerSize;
  641. }
  642. }
  643. } else {
  644. if (frameh < st::maxStickerSize || framew > _width) {
  645. if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) {
  646. frameh = frameh * _width / framew;
  647. framew = _width;
  648. } else {
  649. framew = int32(framew * st::maxStickerSize) / frameh;
  650. frameh = st::maxStickerSize;
  651. }
  652. }
  653. }
  654. return QSize(framew, frameh);
  655. }
  656. void Photo::validateThumbnail(
  657. Image *image,
  658. QSize size,
  659. QSize frame,
  660. bool good) const {
  661. if (!image || (_thumbGood && !good)) {
  662. return;
  663. } else if ((_thumb.size() == size * style::DevicePixelRatio())
  664. && (_thumbGood || !good)) {
  665. return;
  666. }
  667. const auto origin = fileOrigin();
  668. _thumb = image->pixNoCache(
  669. frame * style::DevicePixelRatio(),
  670. {
  671. .options = (Images::Option::TransparentBackground
  672. | (good ? Images::Option() : Images::Option::Blur)),
  673. .outer = size,
  674. });
  675. _thumbGood = good;
  676. }
  677. void Photo::prepareThumbnail(QSize size, QSize frame) const {
  678. using PhotoSize = Data::PhotoSize;
  679. const auto photo = getShownPhoto();
  680. Assert(photo != nullptr);
  681. if (!_photoMedia) {
  682. _photoMedia = photo->createMediaView();
  683. _photoMedia->wanted(PhotoSize::Thumbnail, fileOrigin());
  684. }
  685. validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, frame, true);
  686. validateThumbnail(_photoMedia->image(PhotoSize::Small), size, frame, false);
  687. validateThumbnail(_photoMedia->thumbnailInline(), size, frame, false);
  688. }
  689. Video::Video(not_null<Context*> context, std::shared_ptr<Result> result)
  690. : FileBase(context, std::move(result))
  691. , _link(getResultPreviewHandler())
  692. , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
  693. , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
  694. if (int duration = content_duration()) {
  695. _duration = Ui::FormatDurationText(duration);
  696. _durationWidth = st::normalFont->width(_duration);
  697. }
  698. }
  699. bool Video::withThumbnail() const {
  700. if (const auto document = getShownDocument()) {
  701. if (document->hasThumbnail()) {
  702. return true;
  703. }
  704. }
  705. return hasResultThumb();
  706. }
  707. void Video::initDimensions() {
  708. const auto withThumb = withThumbnail();
  709. _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
  710. const auto textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
  711. TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
  712. auto title = TextUtilities::SingleLine(_result->getLayoutTitle());
  713. if (title.isEmpty()) {
  714. title = tr::lng_media_video(tr::now);
  715. }
  716. _title.setText(st::semiboldTextStyle, title, titleOpts);
  717. int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height);
  718. int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3;
  719. TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
  720. QString description = _result->getLayoutDescription();
  721. if (description.isEmpty()) {
  722. description = _duration;
  723. }
  724. _description.setText(st::defaultTextStyle, description, descriptionOpts);
  725. _minh = st::inlineThumbSize;
  726. _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
  727. }
  728. void Video::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  729. int left = st::inlineThumbSize + st::inlineThumbSkip;
  730. const auto withThumb = withThumbnail();
  731. if (withThumb) {
  732. prepareThumbnail({ st::inlineThumbSize, st::inlineThumbSize });
  733. if (_thumb.isNull()) {
  734. p.fillRect(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewPhotoBg);
  735. } else {
  736. p.drawPixmapLeft(0, st::inlineRowMargin, _width, _thumb);
  737. }
  738. } else {
  739. p.fillRect(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width), st::overviewVideoBg);
  740. }
  741. if (!_duration.isEmpty()) {
  742. int durationTop = st::inlineRowMargin + st::inlineThumbSize - st::normalFont->height - st::inlineDurationMargin;
  743. int durationW = _durationWidth + 2 * st::msgDateImgPadding.x(), durationH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
  744. int durationX = (st::inlineThumbSize - durationW) / 2, durationY = st::inlineRowMargin + st::inlineThumbSize - durationH;
  745. Ui::FillRoundRect(p, durationX, durationY - st::msgDateImgPadding.y(), durationW, durationH, st::msgDateImgBg, Ui::DateCorners);
  746. p.setPen(st::msgDateImgFg);
  747. p.setFont(st::normalFont);
  748. p.drawText(durationX + st::msgDateImgPadding.x(), durationTop + st::normalFont->ascent, _duration);
  749. }
  750. p.setPen(st::inlineTitleFg);
  751. _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
  752. int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
  753. p.setPen(st::inlineDescriptionFg);
  754. int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3;
  755. _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
  756. if (!context->lastRow) {
  757. p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
  758. }
  759. }
  760. void Video::unloadHeavyPart() {
  761. _documentMedia = nullptr;
  762. ItemBase::unloadHeavyPart();
  763. }
  764. TextState Video::getState(
  765. QPoint point,
  766. StateRequest request) const {
  767. if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
  768. return { nullptr, _link };
  769. }
  770. if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) {
  771. return { nullptr, _send };
  772. }
  773. return {};
  774. }
  775. void Video::prepareThumbnail(QSize size) const {
  776. if (const auto document = getShownDocument()) {
  777. if (document->hasThumbnail()) {
  778. if (!_documentMedia) {
  779. _documentMedia = document->createMediaView();
  780. _documentMedia->thumbnailWanted(fileOrigin());
  781. }
  782. if (!_documentMedia->thumbnail()) {
  783. return;
  784. }
  785. }
  786. }
  787. auto resultThumbnailImage = _documentMedia
  788. ? nullptr
  789. : getResultThumb(fileOrigin());
  790. const auto resultThumbnail = Image(resultThumbnailImage
  791. ? base::duplicate(*resultThumbnailImage)
  792. : QImage());
  793. const auto thumb = _documentMedia
  794. ? _documentMedia->thumbnail()
  795. : resultThumbnailImage
  796. ? &resultThumbnail
  797. : nullptr;
  798. if (!thumb) {
  799. return;
  800. }
  801. if (_thumb.size() != size * style::DevicePixelRatio()) {
  802. const auto width = size.width();
  803. const auto height = size.height();
  804. auto w = qMax(style::ConvertScale(thumb->width()), 1);
  805. auto h = qMax(style::ConvertScale(thumb->height()), 1);
  806. if (w * height > h * width) {
  807. if (height < h) {
  808. w = w * height / h;
  809. h = height;
  810. }
  811. } else {
  812. if (width < w) {
  813. h = h * width / w;
  814. w = width;
  815. }
  816. }
  817. _thumb = thumb->pixNoCache(
  818. QSize(w, h) * style::DevicePixelRatio(),
  819. {
  820. .options = Images::Option::TransparentBackground,
  821. .outer = size,
  822. });
  823. }
  824. }
  825. void CancelFileClickHandler::onClickImpl() const {
  826. _result->cancelFile();
  827. }
  828. File::File(not_null<Context*> context, std::shared_ptr<Result> result)
  829. : FileBase(context, std::move(result))
  830. , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineFileSize - st::inlineThumbSkip)
  831. , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineFileSize - st::inlineThumbSkip)
  832. , _cancel(std::make_shared<CancelFileClickHandler>(_result.get()))
  833. , _document(getShownDocument()) {
  834. Expects(getResultDocument() != nullptr);
  835. updateStatusText();
  836. // We have to save document, not read it from Result every time.
  837. // Because we first delete the Result and then delete this File.
  838. // So in destructor we have to remember _document, we can't read it.
  839. regDocumentItem(_document, this);
  840. }
  841. void File::initDimensions() {
  842. _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
  843. TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto };
  844. _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
  845. TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto };
  846. _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
  847. _minh = st::inlineFileSize;
  848. _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
  849. }
  850. void File::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  851. const auto left = st::inlineFileSize + st::inlineThumbSkip;
  852. ensureDataMediaCreated();
  853. const auto displayLoading = _document->displayLoading();
  854. if (displayLoading) {
  855. ensureAnimation();
  856. if (!_animation->radial.animating()) {
  857. _animation->radial.start(_documentMedia->progress());
  858. }
  859. }
  860. const auto showPause = updateStatusText();
  861. const auto radial = isRadialAnimation();
  862. auto inner = style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width);
  863. p.setPen(Qt::NoPen);
  864. const auto coverDrawn = _document->isSongWithCover()
  865. && HistoryView::DrawThumbnailAsSongCover(
  866. p,
  867. st::songCoverOverlayFg,
  868. _documentMedia,
  869. inner);
  870. if (!coverDrawn) {
  871. PainterHighQualityEnabler hq(p);
  872. if (isThumbAnimation()) {
  873. const auto over = _animation->a_thumbOver.value(1.);
  874. p.setBrush(
  875. anim::brush(st::msgFileInBg, st::msgFileInBgOver, over));
  876. } else {
  877. const auto over = ClickHandler::showAsActive(_document->loading()
  878. ? _cancel
  879. : _open);
  880. p.setBrush(over ? st::msgFileInBgOver : st::msgFileInBg);
  881. }
  882. p.drawEllipse(inner);
  883. }
  884. if (radial) {
  885. auto radialCircle = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine));
  886. _animation->radial.draw(p, radialCircle, st::msgFileRadialLine, st::historyFileInRadialFg);
  887. }
  888. const auto icon = [&] {
  889. if (radial || _document->loading()) {
  890. return &st::historyFileInCancel;
  891. } else if (showPause) {
  892. return &st::historyFileInPause;
  893. } else if (_document->isImage()) {
  894. return &st::historyFileInImage;
  895. } else if (_document->isSongWithCover()) {
  896. return &st::historyFileThumbPlay;
  897. } else if (_document->isVoiceMessage()
  898. || _document->isAudioFile()) {
  899. return &st::historyFileInPlay;
  900. }
  901. return &st::historyFileInDocument;
  902. }();
  903. icon->paintInCenter(p, inner);
  904. int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop;
  905. int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop;
  906. p.setPen(st::inlineTitleFg);
  907. _title.drawLeftElided(p, left, titleTop, _width - left, _width);
  908. p.setPen(st::inlineDescriptionFg);
  909. bool drawStatusSize = true;
  910. if (_statusSize == Ui::FileStatusSizeReady
  911. || _statusSize == Ui::FileStatusSizeLoaded
  912. || _statusSize == Ui::FileStatusSizeFailed) {
  913. if (!_description.isEmpty()) {
  914. _description.drawLeftElided(p, left, descriptionTop, _width - left, _width);
  915. drawStatusSize = false;
  916. }
  917. }
  918. if (drawStatusSize) {
  919. p.setFont(st::normalFont);
  920. p.drawTextLeft(left, descriptionTop, _width, _statusText);
  921. }
  922. if (!context->lastRow) {
  923. p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
  924. }
  925. }
  926. TextState File::getState(
  927. QPoint point,
  928. StateRequest request) const {
  929. if (QRect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize).contains(point)) {
  930. return { nullptr, _document->loading() ? _cancel : _open };
  931. } else {
  932. auto left = st::inlineFileSize + st::inlineThumbSkip;
  933. if (QRect(left, 0, _width - left, _height).contains(point)) {
  934. return { nullptr, _send };
  935. }
  936. }
  937. return {};
  938. }
  939. void File::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
  940. if (p == _open || p == _cancel) {
  941. ensureAnimation();
  942. _animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, active ? 0. : 1., active ? 1. : 0., st::msgFileOverDuration);
  943. }
  944. }
  945. void File::unloadHeavyPart() {
  946. _documentMedia = nullptr;
  947. }
  948. File::~File() {
  949. unregDocumentItem(_document, this);
  950. }
  951. void File::thumbAnimationCallback() {
  952. update();
  953. }
  954. void File::radialAnimationCallback(crl::time now) const {
  955. ensureDataMediaCreated();
  956. const auto updated = [&] {
  957. return _animation->radial.update(
  958. _documentMedia->progress(),
  959. !_document->loading() || _documentMedia->loaded(),
  960. now);
  961. }();
  962. if (!anim::Disabled() || updated) {
  963. update();
  964. }
  965. if (!_animation->radial.animating()) {
  966. checkAnimationFinished();
  967. }
  968. }
  969. void File::ensureAnimation() const {
  970. if (!_animation) {
  971. _animation = std::make_unique<AnimationData>([=](crl::time now) {
  972. return radialAnimationCallback(now);
  973. });
  974. }
  975. }
  976. void File::ensureDataMediaCreated() const {
  977. if (_documentMedia) {
  978. return;
  979. }
  980. _documentMedia = _document->createMediaView();
  981. }
  982. void File::checkAnimationFinished() const {
  983. if (_animation
  984. && !_animation->a_thumbOver.animating()
  985. && !_animation->radial.animating()) {
  986. ensureDataMediaCreated();
  987. if (_documentMedia->loaded()) {
  988. _animation.reset();
  989. }
  990. }
  991. }
  992. bool File::updateStatusText() const {
  993. ensureDataMediaCreated();
  994. auto showPause = false;
  995. auto statusSize = int64();
  996. auto realDuration = TimeId();
  997. if (_document->status == FileDownloadFailed || _document->status == FileUploadFailed) {
  998. statusSize = Ui::FileStatusSizeFailed;
  999. } else if (_document->uploading()) {
  1000. statusSize = _document->uploadingData->offset;
  1001. } else if (_document->loading()) {
  1002. statusSize = _document->loadOffset();
  1003. } else if (_documentMedia->loaded()) {
  1004. statusSize = Ui::FileStatusSizeLoaded;
  1005. } else {
  1006. statusSize = Ui::FileStatusSizeReady;
  1007. }
  1008. if (_document->isVoiceMessage() || _document->isAudioFile()) {
  1009. const auto type = _document->isVoiceMessage() ? AudioMsgId::Type::Voice : AudioMsgId::Type::Song;
  1010. const auto state = Media::Player::instance()->getState(type);
  1011. if (state.id == AudioMsgId(_document, FullMsgId(), state.id.externalPlayId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
  1012. statusSize = -1 - (state.position / state.frequency);
  1013. realDuration = (state.length / state.frequency);
  1014. showPause = Media::Player::ShowPauseIcon(state.state);
  1015. }
  1016. if (!showPause && (state.id == AudioMsgId(_document, FullMsgId(), state.id.externalPlayId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
  1017. showPause = true;
  1018. }
  1019. }
  1020. if (statusSize != _statusSize) {
  1021. const auto duration = _document->isSong()
  1022. ? _document->duration()
  1023. : (_document->isVoiceMessage() ? _document->duration() : -1);
  1024. setStatusSize(statusSize, _document->size, (duration >= 0) ? (duration / 1000) : -1, realDuration);
  1025. }
  1026. return showPause;
  1027. }
  1028. void File::setStatusSize(
  1029. int64 newSize,
  1030. int64 fullSize,
  1031. TimeId duration,
  1032. TimeId realDuration) const {
  1033. _statusSize = newSize;
  1034. if (_statusSize == Ui::FileStatusSizeReady) {
  1035. _statusText = (duration >= 0) ? Ui::FormatDurationAndSizeText(duration, fullSize) : (duration < -1 ? Ui::FormatGifAndSizeText(fullSize) : Ui::FormatSizeText(fullSize));
  1036. } else if (_statusSize == Ui::FileStatusSizeLoaded) {
  1037. _statusText = (duration >= 0) ? Ui::FormatDurationText(duration) : (duration < -1 ? u"GIF"_q : Ui::FormatSizeText(fullSize));
  1038. } else if (_statusSize == Ui::FileStatusSizeFailed) {
  1039. _statusText = tr::lng_attach_failed(tr::now);
  1040. } else if (_statusSize >= 0) {
  1041. _statusText = Ui::FormatDownloadText(_statusSize, fullSize);
  1042. } else {
  1043. _statusText = Ui::FormatPlayedText(-_statusSize - 1, realDuration);
  1044. }
  1045. }
  1046. Contact::Contact(not_null<Context*> context, std::shared_ptr<Result> result)
  1047. : ItemBase(context, std::move(result))
  1048. , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
  1049. , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
  1050. }
  1051. void Contact::initDimensions() {
  1052. _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
  1053. int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
  1054. TextParseOptions titleOpts = { 0, textWidth, st::semiboldFont->height, Qt::LayoutDirectionAuto };
  1055. _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
  1056. TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, st::normalFont->height, Qt::LayoutDirectionAuto };
  1057. _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
  1058. _minh = st::inlineFileSize;
  1059. _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
  1060. }
  1061. void Contact::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  1062. int32 left = st::defaultEmojiPan.headerLeft - st::inlineResultsLeft;
  1063. left = st::inlineFileSize + st::inlineThumbSkip;
  1064. prepareThumbnail(st::inlineFileSize, st::inlineFileSize);
  1065. QRect rthumb(style::rtlrect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineFileSize, _width));
  1066. p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
  1067. int titleTop = st::inlineRowMargin + st::inlineRowFileNameTop;
  1068. int descriptionTop = st::inlineRowMargin + st::inlineRowFileDescriptionTop;
  1069. p.setPen(st::inlineTitleFg);
  1070. _title.drawLeftElided(p, left, titleTop, _width - left, _width);
  1071. p.setPen(st::inlineDescriptionFg);
  1072. _description.drawLeftElided(p, left, descriptionTop, _width - left, _width);
  1073. if (!context->lastRow) {
  1074. p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
  1075. }
  1076. }
  1077. TextState Contact::getState(
  1078. QPoint point,
  1079. StateRequest request) const {
  1080. if (!QRect(0, st::inlineRowMargin, st::inlineFileSize, st::inlineThumbSize).contains(point)) {
  1081. auto left = (st::inlineFileSize + st::inlineThumbSkip);
  1082. if (QRect(left, 0, _width - left, _height).contains(point)) {
  1083. return { nullptr, _send };
  1084. }
  1085. }
  1086. return {};
  1087. }
  1088. void Contact::prepareThumbnail(int width, int height) const {
  1089. if (!hasResultThumb()) {
  1090. if ((_thumb.width() != width * style::DevicePixelRatio())
  1091. || (_thumb.height() != height * style::DevicePixelRatio())) {
  1092. _thumb = getResultContactAvatar(width, height);
  1093. }
  1094. return;
  1095. }
  1096. const auto origin = fileOrigin();
  1097. const auto thumb = getResultThumb(origin);
  1098. if (!thumb
  1099. || ((_thumb.width() == width * style::DevicePixelRatio())
  1100. && (_thumb.height() == height * style::DevicePixelRatio()))) {
  1101. return;
  1102. }
  1103. auto w = qMax(style::ConvertScale(thumb->width()), 1);
  1104. auto h = qMax(style::ConvertScale(thumb->height()), 1);
  1105. if (w * height > h * width) {
  1106. if (height < h) {
  1107. w = w * height / h;
  1108. h = height;
  1109. }
  1110. } else {
  1111. if (width < w) {
  1112. h = h * width / w;
  1113. w = width;
  1114. }
  1115. }
  1116. _thumb = Image(base::duplicate(*thumb)).pixNoCache(
  1117. QSize(w, h) * style::DevicePixelRatio(),
  1118. {
  1119. .options = Images::Option::TransparentBackground,
  1120. .outer = { width, height },
  1121. });
  1122. }
  1123. Article::Article(
  1124. not_null<Context*> context,
  1125. std::shared_ptr<Result> result,
  1126. bool withThumb)
  1127. : ItemBase(context, std::move(result))
  1128. , _url(getResultUrlHandler())
  1129. , _link(getResultPreviewHandler())
  1130. , _withThumb(withThumb)
  1131. , _title(st::emojiPanWidth / 2)
  1132. , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
  1133. if (!_link) {
  1134. if (const auto point = _result->getLocationPoint()) {
  1135. _link = std::make_shared<LocationClickHandler>(*point);
  1136. }
  1137. }
  1138. _thumbLetter = getResultThumbLetter();
  1139. }
  1140. void Article::initDimensions() {
  1141. _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
  1142. int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : (st::defaultEmojiPan.headerLeft - st::inlineResultsLeft));
  1143. TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
  1144. _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
  1145. int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height);
  1146. int32 descriptionLines = (_withThumb || _url) ? 2 : 3;
  1147. QString description = _result->getLayoutDescription();
  1148. TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
  1149. _description.setText(st::defaultTextStyle, description, descriptionOpts);
  1150. int32 descriptionHeight = qMin(_description.countHeight(textWidth), descriptionLines * st::normalFont->height);
  1151. _minh = titleHeight + descriptionHeight;
  1152. if (_url) _minh += st::normalFont->height;
  1153. if (_withThumb) _minh = qMax(_minh, int32(st::inlineThumbSize));
  1154. _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
  1155. }
  1156. int Article::resizeGetHeight(int width) {
  1157. _width = qMin(width, _maxw);
  1158. if (_url) {
  1159. _urlText = getResultUrl();
  1160. _urlWidth = st::normalFont->width(_urlText);
  1161. int32 textWidth = _width - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : (st::defaultEmojiPan.headerLeft - st::inlineResultsLeft));
  1162. if (_urlWidth > textWidth) {
  1163. _urlText = st::normalFont->elided(_urlText, textWidth);
  1164. _urlWidth = st::normalFont->width(_urlText);
  1165. }
  1166. }
  1167. _height = _minh;
  1168. return _height;
  1169. }
  1170. void Article::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  1171. int32 left = st::defaultEmojiPan.headerLeft - st::inlineResultsLeft;
  1172. if (_withThumb) {
  1173. left = st::inlineThumbSize + st::inlineThumbSkip;
  1174. prepareThumbnail(st::inlineThumbSize, st::inlineThumbSize);
  1175. QRect rthumb(style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width));
  1176. if (_thumb.isNull()) {
  1177. if (!hasResultThumb() && !_thumbLetter.isEmpty()) {
  1178. int32 index = (_thumbLetter.at(0).unicode() % 4);
  1179. style::color colors[] = {
  1180. st::msgFile3Bg,
  1181. st::msgFile4Bg,
  1182. st::msgFile2Bg,
  1183. st::msgFile1Bg
  1184. };
  1185. p.fillRect(rthumb, colors[index]);
  1186. if (!_thumbLetter.isEmpty()) {
  1187. p.setFont(st::linksLetterFont);
  1188. p.setPen(st::linksLetterFg);
  1189. p.drawText(rthumb, _thumbLetter, style::al_center);
  1190. }
  1191. } else {
  1192. p.fillRect(rthumb, st::overviewPhotoBg);
  1193. }
  1194. } else {
  1195. p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
  1196. }
  1197. }
  1198. p.setPen(st::inlineTitleFg);
  1199. _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
  1200. int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
  1201. p.setPen(st::inlineDescriptionFg);
  1202. int32 descriptionLines = (_withThumb || _url) ? 2 : 3;
  1203. _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
  1204. if (_url) {
  1205. int32 descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
  1206. p.drawTextLeft(left, st::inlineRowMargin + titleHeight + descriptionHeight, _width, _urlText, _urlWidth);
  1207. }
  1208. if (!context->lastRow) {
  1209. p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
  1210. }
  1211. }
  1212. TextState Article::getState(
  1213. QPoint point,
  1214. StateRequest request) const {
  1215. if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
  1216. return { nullptr, _link };
  1217. }
  1218. auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0;
  1219. if (QRect(left, 0, _width - left, _height).contains(point)) {
  1220. if (_url) {
  1221. auto left = st::inlineThumbSize + st::inlineThumbSkip;
  1222. auto titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
  1223. auto descriptionLines = 2;
  1224. auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
  1225. if (style::rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) {
  1226. return { nullptr, _url };
  1227. }
  1228. }
  1229. return { nullptr, _send };
  1230. }
  1231. return {};
  1232. }
  1233. void Article::prepareThumbnail(int width, int height) const {
  1234. if (!hasResultThumb()) {
  1235. if ((_thumb.width() != width * style::DevicePixelRatio())
  1236. || (_thumb.height() != height * style::DevicePixelRatio())) {
  1237. _thumb = getResultContactAvatar(width, height);
  1238. }
  1239. return;
  1240. }
  1241. const auto origin = fileOrigin();
  1242. const auto thumb = getResultThumb(origin);
  1243. if (!thumb
  1244. || ((_thumb.width() == width * style::DevicePixelRatio())
  1245. && (_thumb.height() == height * style::DevicePixelRatio()))) {
  1246. return;
  1247. }
  1248. auto w = qMax(style::ConvertScale(thumb->width()), 1);
  1249. auto h = qMax(style::ConvertScale(thumb->height()), 1);
  1250. if (w * height > h * width) {
  1251. if (height < h) {
  1252. w = w * height / h;
  1253. h = height;
  1254. }
  1255. } else {
  1256. if (width < w) {
  1257. h = h * width / w;
  1258. w = width;
  1259. }
  1260. }
  1261. _thumb = Image(base::duplicate(*thumb)).pixNoCache(
  1262. QSize(w, h) * style::DevicePixelRatio(),
  1263. {
  1264. .options = Images::Option::TransparentBackground,
  1265. .outer = { width, height },
  1266. });
  1267. }
  1268. Game::Game(not_null<Context*> context, std::shared_ptr<Result> result)
  1269. : ItemBase(context, std::move(result))
  1270. , _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
  1271. , _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
  1272. countFrameSize();
  1273. }
  1274. void Game::countFrameSize() {
  1275. if (auto document = getResultDocument()) {
  1276. if (document->isAnimation()) {
  1277. auto documentSize = document->dimensions;
  1278. if (documentSize.isEmpty()) {
  1279. documentSize = QSize(st::inlineThumbSize, st::inlineThumbSize);
  1280. }
  1281. auto resizeByHeight1 = (documentSize.width() > documentSize.height()) && (documentSize.height() >= st::inlineThumbSize);
  1282. auto resizeByHeight2 = (documentSize.height() >= documentSize.width()) && (documentSize.width() < st::inlineThumbSize);
  1283. if (resizeByHeight1 || resizeByHeight2) {
  1284. if (documentSize.height() > st::inlineThumbSize) {
  1285. _frameSize = QSize((documentSize.width() * st::inlineThumbSize) / documentSize.height(), st::inlineThumbSize);
  1286. }
  1287. } else {
  1288. if (documentSize.width() > st::inlineThumbSize) {
  1289. _frameSize = QSize(st::inlineThumbSize, (documentSize.height() * st::inlineThumbSize) / documentSize.width());
  1290. }
  1291. }
  1292. if (!_frameSize.width()) {
  1293. _frameSize.setWidth(1);
  1294. }
  1295. if (!_frameSize.height()) {
  1296. _frameSize.setHeight(1);
  1297. }
  1298. }
  1299. }
  1300. }
  1301. void Game::initDimensions() {
  1302. _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
  1303. TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
  1304. _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
  1305. int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
  1306. int32 descriptionLines = 2;
  1307. QString description = _result->getLayoutDescription();
  1308. TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
  1309. _description.setText(st::defaultTextStyle, description, descriptionOpts);
  1310. int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height);
  1311. _minh = titleHeight + descriptionHeight;
  1312. accumulate_max(_minh, st::inlineThumbSize);
  1313. _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
  1314. }
  1315. void Game::setPosition(int32 position) {
  1316. AbstractLayoutItem::setPosition(position);
  1317. if (_position < 0) {
  1318. _gif.reset();
  1319. }
  1320. }
  1321. void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
  1322. int32 left = st::defaultEmojiPan.headerLeft - st::inlineResultsLeft;
  1323. left = st::inlineThumbSize + st::inlineThumbSkip;
  1324. auto rthumb = style::rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width);
  1325. // Gif thumb
  1326. auto thumbDisplayed = false, radial = false;
  1327. const auto photo = getResultPhoto();
  1328. const auto document = getResultDocument();
  1329. if (document) {
  1330. ensureDataMediaCreated(document);
  1331. } else if (photo) {
  1332. ensureDataMediaCreated(photo);
  1333. }
  1334. auto animatedThumb = document && document->isAnimation();
  1335. if (animatedThumb) {
  1336. _documentMedia->automaticLoad(fileOrigin(), nullptr);
  1337. bool loaded = _documentMedia->loaded(), displayLoading = document->displayLoading();
  1338. if (loaded && !_gif && !_gif.isBad()) {
  1339. auto that = const_cast<Game*>(this);
  1340. that->_gif = Media::Clip::MakeReader(
  1341. _documentMedia->owner()->location(),
  1342. _documentMedia->bytes(),
  1343. [=](Media::Clip::Notification notification) { that->clipCallback(notification); });
  1344. }
  1345. bool animating = (_gif && _gif->started());
  1346. if (displayLoading) {
  1347. if (!_radial) {
  1348. _radial = std::make_unique<Ui::RadialAnimation>([=](crl::time now) {
  1349. return radialAnimationCallback(now);
  1350. });
  1351. }
  1352. if (!_radial->animating()) {
  1353. _radial->start(_documentMedia->progress());
  1354. }
  1355. }
  1356. radial = isRadialAnimation();
  1357. if (animating) {
  1358. const auto pixmap = _gif->current({
  1359. .frame = _frameSize,
  1360. .outer = { st::inlineThumbSize, st::inlineThumbSize },
  1361. }, context->paused ? 0 : context->ms);
  1362. if (_thumb.isNull()) {
  1363. _thumb = pixmap;
  1364. _thumbGood = true;
  1365. }
  1366. p.drawImage(rthumb.topLeft(), pixmap);
  1367. thumbDisplayed = true;
  1368. }
  1369. }
  1370. if (!thumbDisplayed) {
  1371. prepareThumbnail({ st::inlineThumbSize, st::inlineThumbSize });
  1372. if (_thumb.isNull()) {
  1373. p.fillRect(rthumb, st::overviewPhotoBg);
  1374. } else {
  1375. p.drawImage(rthumb.topLeft(), _thumb);
  1376. }
  1377. }
  1378. if (radial) {
  1379. p.fillRect(rthumb, st::msgDateImgBg);
  1380. QRect inner((st::inlineThumbSize - st::inlineRadialSize) / 2, (st::inlineThumbSize - st::inlineRadialSize) / 2, st::inlineRadialSize, st::inlineRadialSize);
  1381. if (radial) {
  1382. p.setOpacity(1);
  1383. QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
  1384. _radial->draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg);
  1385. }
  1386. }
  1387. p.setPen(st::inlineTitleFg);
  1388. _title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
  1389. int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
  1390. p.setPen(st::inlineDescriptionFg);
  1391. int32 descriptionLines = 2;
  1392. _description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
  1393. if (!context->lastRow) {
  1394. p.fillRect(style::rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
  1395. }
  1396. }
  1397. TextState Game::getState(
  1398. QPoint point,
  1399. StateRequest request) const {
  1400. int left = st::inlineThumbSize + st::inlineThumbSkip;
  1401. if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
  1402. return { nullptr, _send };
  1403. }
  1404. if (QRect(left, 0, _width - left, _height).contains(point)) {
  1405. return { nullptr, _send };
  1406. }
  1407. return {};
  1408. }
  1409. void Game::prepareThumbnail(QSize size) const {
  1410. if (const auto document = getResultDocument()) {
  1411. Assert(_documentMedia != nullptr);
  1412. validateThumbnail(_documentMedia->thumbnail(), size, true);
  1413. validateThumbnail(_documentMedia->thumbnailInline(), size, false);
  1414. } else if (const auto photo = getResultPhoto()) {
  1415. using Data::PhotoSize;
  1416. Assert(_photoMedia != nullptr);
  1417. validateThumbnail(_photoMedia->image(PhotoSize::Thumbnail), size, true);
  1418. validateThumbnail(_photoMedia->image(PhotoSize::Small), size, false);
  1419. validateThumbnail(_photoMedia->thumbnailInline(), size, false);
  1420. }
  1421. }
  1422. void Game::ensureDataMediaCreated(not_null<DocumentData*> document) const {
  1423. if (_documentMedia) {
  1424. return;
  1425. }
  1426. _documentMedia = document->createMediaView();
  1427. _documentMedia->thumbnailWanted(fileOrigin());
  1428. }
  1429. void Game::ensureDataMediaCreated(not_null<PhotoData*> photo) const {
  1430. if (_photoMedia) {
  1431. return;
  1432. }
  1433. _photoMedia = photo->createMediaView();
  1434. _photoMedia->wanted(Data::PhotoSize::Thumbnail, fileOrigin());
  1435. }
  1436. void Game::validateThumbnail(Image *image, QSize size, bool good) const {
  1437. if (!image || (_thumbGood && !good)) {
  1438. return;
  1439. } else if ((_thumb.size() == size * style::DevicePixelRatio())
  1440. && (_thumbGood || !good)) {
  1441. return;
  1442. }
  1443. const auto width = size.width();
  1444. const auto height = size.height();
  1445. auto w = qMax(style::ConvertScale(image->width()), 1);
  1446. auto h = qMax(style::ConvertScale(image->height()), 1);
  1447. auto resizeByHeight1 = (w * height > h * width) && (h >= height);
  1448. auto resizeByHeight2 = (h * width >= w * height) && (w < width);
  1449. if (resizeByHeight1 || resizeByHeight2) {
  1450. if (h > height) {
  1451. w = w * height / h;
  1452. h = height;
  1453. }
  1454. } else {
  1455. if (w > width) {
  1456. h = h * width / w;
  1457. w = width;
  1458. }
  1459. }
  1460. _thumbGood = good;
  1461. _thumb = image->pixNoCache(
  1462. QSize(w, h) * style::DevicePixelRatio(),
  1463. {
  1464. .options = (Images::Option::TransparentBackground
  1465. | (good ? Images::Option() : Images::Option::Blur)),
  1466. .outer = size,
  1467. }).toImage();
  1468. }
  1469. bool Game::isRadialAnimation() const {
  1470. if (_radial) {
  1471. if (_radial->animating()) {
  1472. return true;
  1473. } else {
  1474. ensureDataMediaCreated(getResultDocument());
  1475. if (_documentMedia->loaded()) {
  1476. _radial = nullptr;
  1477. }
  1478. }
  1479. }
  1480. return false;
  1481. }
  1482. void Game::radialAnimationCallback(crl::time now) const {
  1483. const auto document = getResultDocument();
  1484. ensureDataMediaCreated(document);
  1485. const auto updated = [&] {
  1486. return _radial->update(
  1487. _documentMedia->progress(),
  1488. !document->loading() || _documentMedia->loaded(),
  1489. now);
  1490. }();
  1491. if (!anim::Disabled() || updated) {
  1492. update();
  1493. }
  1494. if (!_radial->animating() && _documentMedia->loaded()) {
  1495. _radial = nullptr;
  1496. }
  1497. }
  1498. void Game::unloadHeavyPart() {
  1499. _gif.reset();
  1500. _documentMedia = nullptr;
  1501. _photoMedia = nullptr;
  1502. }
  1503. void Game::clipCallback(Media::Clip::Notification notification) {
  1504. using namespace Media::Clip;
  1505. switch (notification) {
  1506. case Notification::Reinit: {
  1507. if (_gif) {
  1508. if (_gif->state() == State::Error) {
  1509. _gif.setBad();
  1510. } else if (_gif->ready() && !_gif->started()) {
  1511. if (_gif->width() * _gif->height() > kMaxInlineArea) {
  1512. getResultDocument()->dimensions = QSize(
  1513. _gif->width(),
  1514. _gif->height());
  1515. _gif.reset();
  1516. } else {
  1517. _gif->start({
  1518. .frame = _frameSize,
  1519. .outer = { st::inlineThumbSize, st::inlineThumbSize },
  1520. });
  1521. }
  1522. } else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
  1523. unloadHeavyPart();
  1524. }
  1525. }
  1526. update();
  1527. } break;
  1528. case Notification::Repaint: {
  1529. if (_gif && !_gif->currentDisplayed()) {
  1530. update();
  1531. }
  1532. } break;
  1533. }
  1534. }
  1535. } // namespace internal
  1536. } // namespace Layout
  1537. } // namespace InlineBots