media_view_group_thumbs.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  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 "media/view/media_view_group_thumbs.h"
  8. #include "data/data_shared_media.h"
  9. #include "data/data_user_photos.h"
  10. #include "data/data_photo.h"
  11. #include "data/data_photo_media.h"
  12. #include "data/data_document.h"
  13. #include "data/data_document_media.h"
  14. #include "data/data_media_types.h"
  15. #include "data/data_session.h"
  16. #include "data/data_web_page.h"
  17. #include "data/data_file_origin.h"
  18. #include "history/history.h"
  19. #include "history/view/media/history_view_media.h"
  20. #include "ui/image/image.h"
  21. #include "ui/ui_utility.h"
  22. #include "main/main_session.h"
  23. #include "core/crash_reports.h"
  24. #include "styles/style_media_view.h"
  25. namespace Media {
  26. namespace View {
  27. namespace {
  28. constexpr auto kThumbDuration = crl::time(150);
  29. int Round(float64 value) {
  30. return int(base::SafeRound(value));
  31. }
  32. using Context = GroupThumbs::Context;
  33. using Key = GroupThumbs::Key;
  34. #if 0
  35. [[nodiscard]] QString DebugSerializeMsgId(FullMsgId itemId) {
  36. return QString("msg%1_%2").arg(itemId.channel.bare).arg(itemId.msg);
  37. }
  38. [[nodiscard]] QString DebugSerializePeer(PeerId peerId) {
  39. return peerIsUser(peerId)
  40. ? QString("user%1").arg(peerToUser(peerId).bare)
  41. : peerIsChat(peerId)
  42. ? QString("chat%1").arg(peerToChat(peerId).bare)
  43. : QString("channel%1").arg(peerToChannel(peerId).bare);
  44. }
  45. [[nodiscard]] QString DebugSerializeKey(const Key &key) {
  46. return v::match(key, [&](PhotoId photoId) {
  47. return QString("photo%1").arg(photoId);
  48. }, [](FullMsgId itemId) {
  49. return DebugSerializeMsgId(itemId);
  50. }, [&](GroupThumbs::CollageKey key) {
  51. return QString("collage%1").arg(key.index);
  52. });
  53. }
  54. [[nodiscard]] QString DebugSerializeContext(const Context &context) {
  55. return v::match(context, [](PeerId peerId) {
  56. return DebugSerializePeer(peerId);
  57. }, [](MessageGroupId groupId) {
  58. return QString("group_%1_%2"
  59. ).arg(DebugSerializePeer(groupId.peer)
  60. ).arg(groupId.value);
  61. }, [](FullMsgId item) {
  62. return DebugSerializeMsgId(item);
  63. }, [](v::null_t) -> QString {
  64. return "null";
  65. });
  66. }
  67. #endif
  68. Data::FileOrigin ComputeFileOrigin(const Key &key, const Context &context) {
  69. return v::match(key, [&](PhotoId photoId) {
  70. return v::match(context, [&](PeerId peerId) {
  71. return peerIsUser(peerId)
  72. ? Data::FileOriginUserPhoto(peerToUser(peerId), photoId)
  73. : Data::FileOrigin(Data::FileOriginPeerPhoto(peerId));
  74. }, [](auto&&) {
  75. return Data::FileOrigin();
  76. });
  77. }, [](FullMsgId itemId) {
  78. return Data::FileOrigin(itemId);
  79. }, [&](GroupThumbs::CollageKey) {
  80. return v::match(context, [](const GroupThumbs::CollageSlice &slice) {
  81. return Data::FileOrigin(slice.context);
  82. }, [](auto&&) {
  83. return Data::FileOrigin();
  84. });
  85. });
  86. }
  87. Context ComputeContext(
  88. not_null<Main::Session*> session,
  89. const SharedMediaWithLastSlice &slice,
  90. int index) {
  91. Expects(index >= 0 && index < slice.size());
  92. const auto value = slice[index];
  93. if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
  94. if (const auto peer = (*photo)->peer) {
  95. return peer->id;
  96. }
  97. return v::null;
  98. } else if (const auto msgId = std::get_if<FullMsgId>(&value)) {
  99. if (const auto item = session->data().message(*msgId)) {
  100. if (item->isService()) {
  101. return item->history()->peer->id;
  102. } else if (const auto groupId = item->groupId()) {
  103. return groupId;
  104. }
  105. }
  106. return v::null;
  107. }
  108. Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
  109. }
  110. Context ComputeContext(
  111. not_null<Main::Session*> session,
  112. const UserPhotosSlice &slice,
  113. int index) {
  114. return peerFromUser(slice.key().userId);
  115. }
  116. Context ComputeContext(
  117. not_null<Main::Session*> session,
  118. const GroupThumbs::CollageSlice &slice,
  119. int index) {
  120. return slice.context;
  121. }
  122. Key ComputeKey(const SharedMediaWithLastSlice &slice, int index) {
  123. Expects(index >= 0 && index < slice.size());
  124. const auto value = slice[index];
  125. if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
  126. return (*photo)->id;
  127. } else if (const auto msgId = std::get_if<FullMsgId>(&value)) {
  128. return *msgId;
  129. }
  130. Unexpected("Variant in ComputeKey(SharedMediaWithLastSlice::Value)");
  131. }
  132. Key ComputeKey(const UserPhotosSlice &slice, int index) {
  133. return slice[index];
  134. }
  135. Key ComputeKey(const GroupThumbs::CollageSlice &slice, int index) {
  136. return GroupThumbs::CollageKey{ index };
  137. }
  138. int ComputeThumbsLimit(int availableWidth) {
  139. const auto singleWidth = st::mediaviewGroupWidth
  140. + 2 * st::mediaviewGroupSkip;
  141. const auto currentWidth = st::mediaviewGroupWidthMax
  142. + 2 * st::mediaviewGroupSkipCurrent;
  143. const auto skipForAnimation = 2 * singleWidth;
  144. const auto leftWidth = availableWidth
  145. - currentWidth
  146. - skipForAnimation;
  147. return std::max(leftWidth / (2 * singleWidth), 1);
  148. }
  149. } // namespace
  150. class GroupThumbs::Thumb {
  151. public:
  152. enum class State {
  153. Unknown,
  154. Current,
  155. Alive,
  156. Dying,
  157. };
  158. Thumb(Key key, Fn<void()> handler);
  159. Thumb(
  160. Key key,
  161. not_null<PhotoData*> photo,
  162. Data::FileOrigin origin,
  163. Fn<void()> handler);
  164. Thumb(
  165. Key key,
  166. not_null<DocumentData*> document,
  167. Data::FileOrigin origin,
  168. Fn<void()> handler);
  169. [[nodiscard]] int leftToUpdate() const;
  170. [[nodiscard]] int rightToUpdate() const;
  171. void animateToLeft(not_null<Thumb*> next);
  172. void animateToRight(not_null<Thumb*> prev);
  173. void setState(State state);
  174. [[nodiscard]] State state() const;
  175. [[nodiscard]] bool inited() const;
  176. [[nodiscard]] bool removed() const;
  177. void paint(QPainter &p, int x, int y, int outerWidth, float64 progress);
  178. [[nodiscard]] ClickHandlerPtr getState(QPoint point) const;
  179. private:
  180. QSize wantedPixSize() const;
  181. void validateImage();
  182. int currentLeft() const;
  183. int currentWidth() const;
  184. int finalLeft() const;
  185. int finalWidth() const;
  186. void animateTo(int left, int width);
  187. ClickHandlerPtr _link;
  188. const Key _key;
  189. std::shared_ptr<Data::DocumentMedia> _documentMedia;
  190. std::shared_ptr<Data::PhotoMedia> _photoMedia;
  191. Image *_image = nullptr;
  192. Data::FileOrigin _origin;
  193. State _state = State::Alive;
  194. QPixmap _full;
  195. int _fullWidth = 0;
  196. bool _hiding = false;
  197. anim::value _left = { 0. };
  198. anim::value _width = { 0. };
  199. anim::value _opacity = { 0., 1. };
  200. };
  201. GroupThumbs::Thumb::Thumb(Key key, Fn<void()> handler)
  202. : _key(key) {
  203. _link = std::make_shared<LambdaClickHandler>(std::move(handler));
  204. validateImage();
  205. }
  206. GroupThumbs::Thumb::Thumb(
  207. Key key,
  208. not_null<PhotoData*> photo,
  209. Data::FileOrigin origin,
  210. Fn<void()> handler)
  211. : _key(key)
  212. , _photoMedia(photo->createMediaView())
  213. , _origin(origin) {
  214. _link = std::make_shared<LambdaClickHandler>(std::move(handler));
  215. _photoMedia->wanted(Data::PhotoSize::Thumbnail, origin);
  216. validateImage();
  217. }
  218. GroupThumbs::Thumb::Thumb(
  219. Key key,
  220. not_null<DocumentData*> document,
  221. Data::FileOrigin origin,
  222. Fn<void()> handler)
  223. : _key(key)
  224. , _documentMedia(document->createMediaView())
  225. , _origin(origin) {
  226. _link = std::make_shared<LambdaClickHandler>(std::move(handler));
  227. _documentMedia->thumbnailWanted(origin);
  228. validateImage();
  229. }
  230. QSize GroupThumbs::Thumb::wantedPixSize() const {
  231. const auto originalWidth = _image ? std::max(_image->width(), 1) : 1;
  232. const auto originalHeight = _image ? std::max(_image->height(), 1) : 1;
  233. const auto pixHeight = st::mediaviewGroupHeight;
  234. const auto pixWidth = originalWidth * pixHeight / originalHeight;
  235. return { pixWidth, pixHeight };
  236. }
  237. void GroupThumbs::Thumb::validateImage() {
  238. if (!_image) {
  239. if (_photoMedia) {
  240. _image = _photoMedia->image(Data::PhotoSize::Thumbnail);
  241. } else if (_documentMedia) {
  242. _image = _documentMedia->thumbnail();
  243. }
  244. }
  245. if (!_full.isNull() || !_image) {
  246. return;
  247. }
  248. const auto pixSize = wantedPixSize();
  249. if (pixSize.width() > st::mediaviewGroupWidthMax) {
  250. const auto originalWidth = _image->width();
  251. const auto originalHeight = _image->height();
  252. const auto takeWidth = originalWidth * st::mediaviewGroupWidthMax
  253. / pixSize.width();
  254. auto original = _image->original();
  255. original.setDevicePixelRatio(style::DevicePixelRatio());
  256. _full = Ui::PixmapFromImage(original.copy(
  257. (originalWidth - takeWidth) / 2,
  258. 0,
  259. takeWidth,
  260. originalHeight
  261. ).scaled(
  262. st::mediaviewGroupWidthMax * style::DevicePixelRatio(),
  263. pixSize.height() * style::DevicePixelRatio(),
  264. Qt::IgnoreAspectRatio,
  265. Qt::SmoothTransformation));
  266. } else {
  267. _full = _image->pixNoCache(pixSize * style::DevicePixelRatio());
  268. }
  269. _fullWidth = std::min(
  270. wantedPixSize().width(),
  271. st::mediaviewGroupWidthMax);
  272. }
  273. int GroupThumbs::Thumb::leftToUpdate() const {
  274. return Round(std::min(_left.from(), _left.to()));
  275. }
  276. int GroupThumbs::Thumb::rightToUpdate() const {
  277. return Round(std::max(
  278. _left.from() + _width.from(),
  279. _left.to() + _width.to()));
  280. }
  281. int GroupThumbs::Thumb::currentLeft() const {
  282. return Round(_left.current());
  283. }
  284. int GroupThumbs::Thumb::currentWidth() const {
  285. return Round(_width.current());
  286. }
  287. int GroupThumbs::Thumb::finalLeft() const {
  288. return Round(_left.to());
  289. }
  290. int GroupThumbs::Thumb::finalWidth() const {
  291. return Round(_width.to());
  292. }
  293. void GroupThumbs::Thumb::setState(State state) {
  294. const auto isNewThumb = (_state == State::Alive);
  295. _state = state;
  296. if (_state == State::Current) {
  297. if (isNewThumb) {
  298. _opacity = anim::value(1.);
  299. _left = anim::value(-_fullWidth / 2);
  300. _width = anim::value(_fullWidth);
  301. } else {
  302. _opacity.start(1.);
  303. }
  304. _hiding = false;
  305. animateTo(-_fullWidth / 2, _fullWidth);
  306. } else if (_state == State::Alive) {
  307. _opacity.start(0.7);
  308. _hiding = false;
  309. } else if (_state == State::Dying) {
  310. _opacity.start(0.);
  311. _hiding = true;
  312. _left.restart();
  313. _width.restart();
  314. }
  315. }
  316. void GroupThumbs::Thumb::animateTo(int left, int width) {
  317. _left.start(left);
  318. _width.start(width);
  319. }
  320. void GroupThumbs::Thumb::animateToLeft(not_null<Thumb*> next) {
  321. const auto width = st::mediaviewGroupWidth;
  322. if (_state == State::Alive) {
  323. // New item animation, start exactly from the next, move only.
  324. _left = anim::value(next->currentLeft() - width);
  325. _width = anim::value(width);
  326. } else if (_state == State::Unknown) {
  327. // Existing item animation.
  328. setState(State::Alive);
  329. }
  330. const auto skip1 = st::mediaviewGroupSkip;
  331. const auto skip2 = (next->state() == State::Current)
  332. ? st::mediaviewGroupSkipCurrent
  333. : st::mediaviewGroupSkip;
  334. animateTo(next->finalLeft() - width - skip1 - skip2, width);
  335. }
  336. void GroupThumbs::Thumb::animateToRight(not_null<Thumb*> prev) {
  337. const auto width = st::mediaviewGroupWidth;
  338. if (_state == State::Alive) {
  339. // New item animation, start exactly from the next, move only.
  340. _left = anim::value(prev->currentLeft() + prev->currentWidth());
  341. _width = anim::value(width);
  342. } else if (_state == State::Unknown) {
  343. // Existing item animation.
  344. setState(State::Alive);
  345. }
  346. const auto skip1 = st::mediaviewGroupSkip;
  347. const auto skip2 = (prev->state() == State::Current)
  348. ? st::mediaviewGroupSkipCurrent
  349. : st::mediaviewGroupSkip;
  350. animateTo(prev->finalLeft() + prev->finalWidth() + skip1 + skip2, width);
  351. }
  352. auto GroupThumbs::Thumb::state() const -> State {
  353. return _state;
  354. }
  355. bool GroupThumbs::Thumb::inited() const {
  356. return _fullWidth != 0;
  357. }
  358. bool GroupThumbs::Thumb::removed() const {
  359. return (_state == State::Dying) && _hiding && !_opacity.current();
  360. }
  361. void GroupThumbs::Thumb::paint(
  362. QPainter &p,
  363. int x,
  364. int y,
  365. int outerWidth,
  366. float64 progress) {
  367. validateImage();
  368. _opacity.update(progress, anim::linear);
  369. _left.update(progress, anim::linear);
  370. _width.update(progress, anim::linear);
  371. const auto left = x + currentLeft();
  372. const auto width = currentWidth();
  373. const auto opacity = p.opacity();
  374. p.setOpacity(_opacity.current() * opacity);
  375. if (width == _fullWidth) {
  376. p.drawPixmap(left, y, _full);
  377. } else {
  378. const auto takeWidth = width * style::DevicePixelRatio();
  379. const auto from = QRect(
  380. (_full.width() - takeWidth) / 2,
  381. 0,
  382. takeWidth,
  383. _full.height());
  384. const auto to = QRect(left, y, width, st::mediaviewGroupHeight);
  385. p.drawPixmap(to, _full, from);
  386. }
  387. p.setOpacity(opacity);
  388. }
  389. ClickHandlerPtr GroupThumbs::Thumb::getState(QPoint point) const {
  390. if (_state != State::Alive) {
  391. return nullptr;
  392. }
  393. const auto left = finalLeft();
  394. const auto width = finalWidth();
  395. return QRect(left, 0, width, st::mediaviewGroupHeight).contains(point)
  396. ? _link
  397. : nullptr;
  398. }
  399. int GroupThumbs::CollageSlice::size() const {
  400. return data->items.size();
  401. }
  402. GroupThumbs::GroupThumbs(not_null<Main::Session*> session, Context context)
  403. : _session(session)
  404. , _context(context) {
  405. }
  406. void GroupThumbs::updateContext(Context context) {
  407. if (_context != context) {
  408. clear();
  409. _context = context;
  410. }
  411. }
  412. template <typename Slice>
  413. void GroupThumbs::RefreshFromSlice(
  414. not_null<Main::Session*> session,
  415. std::unique_ptr<GroupThumbs> &instance,
  416. const Slice &slice,
  417. int index,
  418. int availableWidth) {
  419. const auto context = ComputeContext(session, slice, index);
  420. if (instance) {
  421. instance->updateContext(context);
  422. }
  423. if (v::is_null(context)) {
  424. if (instance) {
  425. instance->resizeToWidth(availableWidth);
  426. }
  427. return;
  428. }
  429. const auto limit = ComputeThumbsLimit(availableWidth);
  430. const auto from = [&] {
  431. const auto edge = std::max(index - limit, 0);
  432. for (auto result = index; result != edge; --result) {
  433. if (ComputeContext(session, slice, result - 1) != context) {
  434. return result;
  435. }
  436. }
  437. return edge;
  438. }();
  439. const auto till = [&] {
  440. const auto edge = std::min(index + limit + 1, slice.size());
  441. for (auto result = index + 1; result != edge; ++result) {
  442. if (ComputeContext(session, slice, result) != context) {
  443. return result;
  444. }
  445. }
  446. return edge;
  447. }();
  448. if (from + 1 < till) {
  449. if (!instance) {
  450. instance = std::make_unique<GroupThumbs>(session, context);
  451. }
  452. instance->fillItems(slice, from, index, till);
  453. instance->resizeToWidth(availableWidth);
  454. } else if (instance) {
  455. instance->clear();
  456. instance->resizeToWidth(availableWidth);
  457. }
  458. }
  459. #if 0
  460. template <typename Slice>
  461. void ValidateSlice(
  462. const Slice &slice,
  463. const Context &context,
  464. int from,
  465. int index,
  466. int till) {
  467. auto keys = base::flat_set<Key>();
  468. for (auto i = from; i != till; ++i) {
  469. const auto key = ComputeKey(slice, i);
  470. if (keys.contains(key)) {
  471. // All items should be unique!
  472. auto strings = QStringList();
  473. strings.reserve(till - from);
  474. for (auto i = from; i != till; ++i) {
  475. strings.push_back(DebugSerializeKey(ComputeKey(slice, i)));
  476. }
  477. CrashReports::SetAnnotation(
  478. "keys",
  479. QString("%1:%2-(%3)-%4:"
  480. ).arg(DebugSerializeContext(context)
  481. ).arg(from
  482. ).arg(index
  483. ).arg(till) + strings.join(","));
  484. if (Logs::DebugEnabled()) {
  485. Unexpected("Bad slice in GroupThumbs.");
  486. }
  487. break;
  488. } else {
  489. keys.emplace(key);
  490. }
  491. }
  492. }
  493. #endif
  494. template <typename Slice>
  495. void GroupThumbs::fillItems(
  496. const Slice &slice,
  497. int from,
  498. int index,
  499. int till) {
  500. Expects(from <= index);
  501. Expects(index < till);
  502. Expects(from + 1 < till);
  503. const auto current = (index - from);
  504. const auto old = base::take(_items);
  505. //ValidateSlice(slice, _context, from, index, till);
  506. markCacheStale();
  507. _items.reserve(till - from);
  508. for (auto i = from; i != till; ++i) {
  509. _items.push_back(validateCacheEntry(ComputeKey(slice, i)));
  510. }
  511. animateAliveItems(current);
  512. fillDyingItems(old);
  513. startDelayedAnimation();
  514. }
  515. void GroupThumbs::animateAliveItems(int current) {
  516. Expects(current >= 0 && current < _items.size());
  517. _items[current]->setState(Thumb::State::Current);
  518. for (auto i = current; i != 0;) {
  519. const auto prev = _items[i];
  520. const auto item = _items[--i];
  521. item->animateToLeft(prev);
  522. }
  523. for (auto i = current + 1; i != _items.size(); ++i) {
  524. const auto prev = _items[i - 1];
  525. const auto item = _items[i];
  526. item->animateToRight(prev);
  527. }
  528. }
  529. void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
  530. //Expects(_cache.size() >= _items.size());
  531. if (_cache.size() >= _items.size()) {
  532. _dying.reserve(_cache.size() - _items.size());
  533. }
  534. animatePreviouslyAlive(old);
  535. markRestAsDying();
  536. }
  537. void GroupThumbs::markRestAsDying() {
  538. //Expects(_cache.size() >= _items.size());
  539. if (_cache.size() >= _items.size()) {
  540. _dying.reserve(_cache.size() - _items.size());
  541. }
  542. for (const auto &cacheItem : _cache) {
  543. const auto &thumb = cacheItem.second;
  544. const auto state = thumb->state();
  545. if (state == Thumb::State::Unknown) {
  546. markAsDying(thumb.get());
  547. }
  548. }
  549. }
  550. void GroupThumbs::markAsDying(not_null<Thumb*> thumb) {
  551. thumb->setState(Thumb::State::Dying);
  552. _dying.push_back(thumb.get());
  553. }
  554. void GroupThumbs::animatePreviouslyAlive(
  555. const std::vector<not_null<Thumb*>> &old) {
  556. auto toRight = false;
  557. for (auto i = 0; i != old.size(); ++i) {
  558. const auto item = old[i];
  559. if (item->state() == Thumb::State::Unknown) {
  560. if (toRight) {
  561. markAsDying(item);
  562. item->animateToRight(old[i - 1]);
  563. }
  564. } else if (!toRight) {
  565. for (auto j = i; j != 0;) {
  566. const auto next = old[j];
  567. const auto prev = old[--j];
  568. markAsDying(prev);
  569. prev->animateToLeft(next);
  570. }
  571. toRight = true;
  572. }
  573. }
  574. }
  575. auto GroupThumbs::createThumb(Key key)
  576. -> std::unique_ptr<Thumb> {
  577. if (const auto photoId = std::get_if<PhotoId>(&key)) {
  578. const auto photo = _session->data().photo(*photoId);
  579. return createThumb(key, photo);
  580. } else if (const auto msgId = std::get_if<FullMsgId>(&key)) {
  581. if (const auto item = _session->data().message(*msgId)) {
  582. if (const auto media = item->media()) {
  583. if (const auto photo = media->photo()) {
  584. return createThumb(key, photo);
  585. } else if (const auto document = media->document()) {
  586. return createThumb(key, document);
  587. }
  588. }
  589. }
  590. return createThumb(key, nullptr);
  591. } else if (const auto collageKey = std::get_if<CollageKey>(&key)) {
  592. if (const auto itemId = std::get_if<FullMsgId>(&_context)) {
  593. if (const auto item = _session->data().message(*itemId)) {
  594. if (const auto media = item->media()) {
  595. if (const auto page = media->webpage()) {
  596. return createThumb(
  597. key,
  598. page->collage,
  599. collageKey->index);
  600. } else if (const auto invoice = media->invoice()) {
  601. return createThumb(
  602. key,
  603. *invoice,
  604. collageKey->index);
  605. }
  606. }
  607. }
  608. }
  609. return createThumb(key, nullptr);
  610. }
  611. Unexpected("Value of Key in GroupThumbs::createThumb()");
  612. }
  613. auto GroupThumbs::createThumb(
  614. Key key,
  615. const WebPageCollage &collage,
  616. int index)
  617. -> std::unique_ptr<Thumb> {
  618. if (index < 0 || index >= collage.items.size()) {
  619. return createThumb(key, nullptr);
  620. }
  621. const auto &item = collage.items[index];
  622. if (const auto photo = std::get_if<PhotoData*>(&item)) {
  623. return createThumb(key, (*photo));
  624. } else if (const auto document = std::get_if<DocumentData*>(&item)) {
  625. return createThumb(key, (*document));
  626. }
  627. return createThumb(key, nullptr);
  628. }
  629. auto GroupThumbs::createThumb(
  630. Key key,
  631. const Data::Invoice &invoice,
  632. int index)
  633. -> std::unique_ptr<Thumb> {
  634. if (index < 0 || index >= invoice.extendedMedia.size()) {
  635. return createThumb(key, nullptr);
  636. }
  637. const auto &media = invoice.extendedMedia[index];
  638. if (const auto photo = media->photo()) {
  639. return createThumb(key, photo);
  640. } else if (const auto document = media->document()) {
  641. return createThumb(key, document);
  642. }
  643. return createThumb(key, nullptr);
  644. }
  645. auto GroupThumbs::createThumb(Key key, std::nullptr_t)
  646. -> std::unique_ptr<Thumb> {
  647. const auto weak = base::make_weak(this);
  648. const auto origin = ComputeFileOrigin(key, _context);
  649. return std::make_unique<Thumb>(key, [=] {
  650. if (const auto strong = weak.get()) {
  651. strong->_activateStream.fire_copy(key);
  652. }
  653. });
  654. }
  655. auto GroupThumbs::createThumb(Key key, not_null<PhotoData*> photo)
  656. -> std::unique_ptr<Thumb> {
  657. const auto weak = base::make_weak(this);
  658. const auto origin = ComputeFileOrigin(key, _context);
  659. return std::make_unique<Thumb>(key, photo, origin, [=] {
  660. if (const auto strong = weak.get()) {
  661. strong->_activateStream.fire_copy(key);
  662. }
  663. });
  664. }
  665. auto GroupThumbs::createThumb(Key key, not_null<DocumentData*> document)
  666. -> std::unique_ptr<Thumb> {
  667. const auto weak = base::make_weak(this);
  668. const auto origin = ComputeFileOrigin(key, _context);
  669. return std::make_unique<Thumb>(key, document, origin, [=] {
  670. if (const auto strong = weak.get()) {
  671. strong->_activateStream.fire_copy(key);
  672. }
  673. });
  674. }
  675. auto GroupThumbs::validateCacheEntry(Key key) -> not_null<Thumb*> {
  676. const auto i = _cache.find(key);
  677. return (i != _cache.end())
  678. ? i->second.get()
  679. : _cache.emplace(key, createThumb(key)).first->second.get();
  680. }
  681. void GroupThumbs::markCacheStale() {
  682. _dying.clear();
  683. for (const auto &cacheItem : _cache) {
  684. const auto &thumb = cacheItem.second;
  685. thumb->setState(Thumb::State::Unknown);
  686. }
  687. }
  688. void GroupThumbs::Refresh(
  689. not_null<Main::Session*> session,
  690. std::unique_ptr<GroupThumbs> &instance,
  691. const SharedMediaWithLastSlice &slice,
  692. int index,
  693. int availableWidth) {
  694. RefreshFromSlice(session, instance, slice, index, availableWidth);
  695. }
  696. void GroupThumbs::Refresh(
  697. not_null<Main::Session*> session,
  698. std::unique_ptr<GroupThumbs> &instance,
  699. const UserPhotosSlice &slice,
  700. int index,
  701. int availableWidth) {
  702. RefreshFromSlice(session, instance, slice, index, availableWidth);
  703. }
  704. void GroupThumbs::Refresh(
  705. not_null<Main::Session*> session,
  706. std::unique_ptr<GroupThumbs> &instance,
  707. const CollageSlice &slice,
  708. int index,
  709. int availableWidth) {
  710. RefreshFromSlice(session, instance, slice, index, availableWidth);
  711. }
  712. void GroupThumbs::clear() {
  713. if (_items.empty()) {
  714. return;
  715. }
  716. base::take(_items);
  717. markCacheStale();
  718. markRestAsDying();
  719. startDelayedAnimation();
  720. }
  721. void GroupThumbs::startDelayedAnimation() {
  722. _animation.stop();
  723. _waitingForAnimationStart = true;
  724. countUpdatedRect();
  725. }
  726. void GroupThumbs::resizeToWidth(int newWidth) {
  727. _width = newWidth;
  728. }
  729. int GroupThumbs::height() const {
  730. return st::mediaviewGroupPadding.top()
  731. + st::mediaviewGroupHeight
  732. + st::mediaviewGroupPadding.bottom();
  733. }
  734. bool GroupThumbs::hiding() const {
  735. return _items.empty();
  736. }
  737. bool GroupThumbs::hidden() const {
  738. return hiding() && !_waitingForAnimationStart && !_animation.animating();
  739. }
  740. void GroupThumbs::checkForAnimationStart() {
  741. if (_waitingForAnimationStart) {
  742. _waitingForAnimationStart = false;
  743. _animation.start([=] { update(); }, 0., 1., kThumbDuration);
  744. }
  745. }
  746. void GroupThumbs::update() {
  747. if (_cache.empty()) {
  748. return;
  749. }
  750. _updateRequests.fire_copy(_updatedRect);
  751. }
  752. void GroupThumbs::paint(QPainter &p, int x, int y, int outerWidth) {
  753. const auto progress = _waitingForAnimationStart
  754. ? 0.
  755. : _animation.value(1.);
  756. x += (_width / 2);
  757. y += st::mediaviewGroupPadding.top();
  758. auto initedCurrentIndex = int(_items.size());
  759. for (auto i = _cache.begin(); i != _cache.end();) {
  760. const auto thumb = not_null{ i->second.get() };
  761. const auto inited = thumb->inited();
  762. thumb->paint(p, x, y, outerWidth, progress);
  763. if (thumb->removed()) {
  764. _dying.erase(ranges::remove(_dying, thumb), _dying.end());
  765. i = _cache.erase(i);
  766. } else {
  767. if (!inited
  768. && thumb->inited()
  769. && thumb->state() == Thumb::State::Current) {
  770. initedCurrentIndex = ranges::find(_items, thumb)
  771. - begin(_items);
  772. }
  773. ++i;
  774. }
  775. }
  776. if (initedCurrentIndex < _items.size()) {
  777. animateAliveItems(initedCurrentIndex);
  778. }
  779. }
  780. ClickHandlerPtr GroupThumbs::getState(QPoint point) const {
  781. point -= QPoint((_width / 2), st::mediaviewGroupPadding.top());
  782. for (const auto &cacheItem : _cache) {
  783. const auto &thumb = cacheItem.second;
  784. if (auto link = thumb->getState(point)) {
  785. return link;
  786. }
  787. }
  788. return nullptr;
  789. }
  790. void GroupThumbs::countUpdatedRect() {
  791. if (_cache.empty()) {
  792. return;
  793. }
  794. auto min = _width;
  795. auto max = 0;
  796. const auto left = [](const auto &cacheItem) {
  797. const auto &[key, thumb] = cacheItem;
  798. return thumb->leftToUpdate();
  799. };
  800. const auto right = [](const auto &cacheItem) {
  801. const auto &[key, thumb] = cacheItem;
  802. return thumb->rightToUpdate();
  803. };
  804. accumulate_min(min, left(*ranges::max_element(
  805. _cache,
  806. std::greater<>(),
  807. left)));
  808. accumulate_max(max, right(*ranges::max_element(
  809. _cache,
  810. std::less<>(),
  811. right)));
  812. _updatedRect = QRect(
  813. min,
  814. st::mediaviewGroupPadding.top(),
  815. max - min,
  816. st::mediaviewGroupHeight);
  817. }
  818. GroupThumbs::~GroupThumbs() = default;
  819. } // namespace View
  820. } // namespace Media