message_sending_animation_controller.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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 "ui/effects/message_sending_animation_controller.h"
  8. #include "data/data_document.h"
  9. #include "data/data_session.h"
  10. #include "history/history_item.h"
  11. #include "history/view/history_view_element.h"
  12. #include "history/view/history_view_list_widget.h" // kItemRevealDuration
  13. #include "history/view/media/history_view_media.h"
  14. #include "main/main_session.h"
  15. #include "mainwidget.h"
  16. #include "ui/chat/chat_style.h"
  17. #include "ui/chat/chat_theme.h"
  18. #include "ui/effects/animation_value.h"
  19. #include "ui/effects/animation_value_f.h"
  20. #include "ui/effects/animations.h"
  21. #include "ui/painter.h"
  22. #include "ui/rp_widget.h"
  23. #include "window/window_session_controller.h"
  24. #include "styles/style_chat.h"
  25. namespace Ui {
  26. namespace {
  27. constexpr auto kSurroundingProgress = 0.5;
  28. inline float64 OffsetMid(int value, float64 min, float64 max = 1.) {
  29. return ((value * max) - (value * min)) / 2.;
  30. }
  31. class Content final : public RpWidget {
  32. public:
  33. Content(
  34. not_null<RpWidget*> parent,
  35. not_null<Window::SessionController*> controller,
  36. MessageSendingAnimationFrom &&fromInfo,
  37. MessageSendingAnimationController::SendingInfoTo &&to);
  38. [[nodiscard]] rpl::producer<> destroyRequests() const;
  39. protected:
  40. void paintEvent(QPaintEvent *e) override;
  41. private:
  42. using Context = Ui::ChatPaintContext;
  43. void createSurrounding();
  44. void createBubble();
  45. HistoryView::Element *maybeView() const;
  46. bool checkView(HistoryView::Element *currentView) const;
  47. void drawContent(Painter &p, float64 progress) const;
  48. const not_null<Window::SessionController*> _controller;
  49. const bool _crop;
  50. MessageSendingAnimationController::SendingInfoTo _toInfo;
  51. QRect _from;
  52. QPoint _to;
  53. QRect _innerContentRect;
  54. Animations::Simple _animation;
  55. float64 _minScale = 0;
  56. struct {
  57. base::unique_qptr<Ui::RpWidget> widget;
  58. QPoint offsetFromContent;
  59. } _bubble;
  60. base::unique_qptr<Ui::RpWidget> _surrounding;
  61. rpl::event_stream<> _destroyRequests;
  62. };
  63. Content::Content(
  64. not_null<RpWidget*> parent,
  65. not_null<Window::SessionController*> controller,
  66. MessageSendingAnimationFrom &&fromInfo,
  67. MessageSendingAnimationController::SendingInfoTo &&to)
  68. : RpWidget(parent)
  69. , _controller(controller)
  70. , _crop(fromInfo.crop)
  71. , _toInfo(std::move(to))
  72. , _from(parent->mapFromGlobal(fromInfo.globalStartGeometry))
  73. , _innerContentRect(maybeView()->media()->contentRectForReactions())
  74. , _minScale(float64(_from.height()) / _innerContentRect.height()) {
  75. Expects(_toInfo.view != nullptr);
  76. Expects(_toInfo.paintContext != nullptr);
  77. show();
  78. setAttribute(Qt::WA_TransparentForMouseEvents);
  79. raise();
  80. base::take(
  81. _toInfo.globalEndTopLeft
  82. ) | rpl::distinct_until_changed(
  83. ) | rpl::start_with_next([=](const std::optional<QPoint> &p) {
  84. if (p) {
  85. _to = parent->mapFromGlobal(*p);
  86. } else {
  87. _destroyRequests.fire({});
  88. }
  89. }, lifetime());
  90. _controller->session().downloaderTaskFinished(
  91. ) | rpl::start_with_next([=] {
  92. update();
  93. }, lifetime());
  94. resize(_innerContentRect.size());
  95. const auto innerGeometry = maybeView()->innerGeometry();
  96. auto animationCallback = [=](float64 value) {
  97. auto resultFrom = rect();
  98. resultFrom.moveCenter(_from.center());
  99. const auto resultTo = _to
  100. + innerGeometry.topLeft()
  101. + _innerContentRect.topLeft();
  102. const auto x = anim::interpolate(resultFrom.x(), resultTo.x(), value);
  103. const auto y = anim::interpolate(resultFrom.y(), resultTo.y(), value);
  104. moveToLeft(x, y);
  105. update();
  106. if ((value > kSurroundingProgress)
  107. && !_surrounding
  108. && !_bubble.widget) {
  109. const auto currentView = maybeView();
  110. if (!checkView(currentView)) {
  111. return;
  112. }
  113. if (currentView->hasBubble()) {
  114. createBubble();
  115. } else {
  116. createSurrounding();
  117. }
  118. }
  119. if (_surrounding) {
  120. _surrounding->moveToLeft(
  121. x - _innerContentRect.x(),
  122. y - _innerContentRect.y());
  123. }
  124. if (_bubble.widget) {
  125. _bubble.widget->moveToLeft(
  126. x - _bubble.offsetFromContent.x(),
  127. y - _bubble.offsetFromContent.y());
  128. }
  129. if (value == 1.) {
  130. const auto currentView = maybeView();
  131. if (!checkView(currentView)) {
  132. return;
  133. }
  134. const auto controller = _controller;
  135. _destroyRequests.fire({});
  136. controller->session().data().requestViewRepaint(currentView);
  137. }
  138. };
  139. animationCallback(0.);
  140. _animation.start(
  141. std::move(animationCallback),
  142. 0.,
  143. 1.,
  144. HistoryView::ListWidget::kItemRevealDuration);
  145. }
  146. HistoryView::Element *Content::maybeView() const {
  147. return _toInfo.view();
  148. }
  149. bool Content::checkView(HistoryView::Element *currentView) const {
  150. if (!currentView) {
  151. _destroyRequests.fire({});
  152. return false;
  153. }
  154. return true;
  155. }
  156. void Content::paintEvent(QPaintEvent *e) {
  157. const auto progress = _animation.value(_animation.animating() ? 0. : 1.);
  158. if (!_crop) {
  159. Painter p(this);
  160. p.fillRect(e->rect(), Qt::transparent);
  161. drawContent(p, progress);
  162. } else {
  163. // Use QImage to make CompositionMode_Clear work.
  164. auto image = QImage(
  165. size() * style::DevicePixelRatio(),
  166. QImage::Format_ARGB32_Premultiplied);
  167. image.setDevicePixelRatio(style::DevicePixelRatio());
  168. image.fill(Qt::transparent);
  169. const auto scaledFromSize = _from.size().scaled(
  170. _innerContentRect.size(),
  171. Qt::KeepAspectRatio);
  172. const auto cropW = std::ceil(
  173. (_innerContentRect.width() - scaledFromSize.width())
  174. / 2.
  175. * (1. - std::clamp(progress / kSurroundingProgress, 0., 1.)));
  176. {
  177. Painter p(&image);
  178. drawContent(p, progress);
  179. p.setCompositionMode(QPainter::CompositionMode_Clear);
  180. p.fillRect(
  181. QRect(0, 0, cropW, _innerContentRect.height()),
  182. Qt::black);
  183. p.fillRect(
  184. QRect(
  185. _innerContentRect.width() - cropW,
  186. 0,
  187. cropW,
  188. _innerContentRect.height()),
  189. Qt::black);
  190. }
  191. auto p = QPainter(this);
  192. p.drawImage(QPoint(), std::move(image));
  193. }
  194. }
  195. void Content::drawContent(Painter &p, float64 progress) const {
  196. const auto scale = anim::interpolateF(_minScale, 1., progress);
  197. p.translate(
  198. (1 - progress) * OffsetMid(width(), _minScale),
  199. (1 - progress) * OffsetMid(height(), _minScale));
  200. p.scale(scale, scale);
  201. const auto currentView = maybeView();
  202. if (!checkView(currentView)) {
  203. return;
  204. }
  205. auto context = _toInfo.paintContext();
  206. context.skipDrawingParts = Context::SkipDrawingParts::Surrounding;
  207. context.outbg = currentView->hasOutLayout();
  208. p.translate(-_innerContentRect.topLeft());
  209. currentView->media()->draw(p, context);
  210. }
  211. rpl::producer<> Content::destroyRequests() const {
  212. return _destroyRequests.events();
  213. }
  214. void Content::createSurrounding() {
  215. _surrounding = base::make_unique_q<Ui::RpWidget>(parentWidget());
  216. _surrounding->setAttribute(Qt::WA_TransparentForMouseEvents);
  217. const auto currentView = maybeView();
  218. if (!checkView(currentView)) {
  219. return;
  220. }
  221. const auto surroundingSize = currentView->innerGeometry().size();
  222. const auto offset = _innerContentRect.topLeft();
  223. _surrounding->resize(surroundingSize);
  224. _surrounding->show();
  225. // Do not raise.
  226. _surrounding->stackUnder(this);
  227. stackUnder(_surrounding.get());
  228. _surrounding->paintRequest(
  229. ) | rpl::start_with_next([=, size = surroundingSize](const QRect &r) {
  230. Painter p(_surrounding);
  231. p.fillRect(r, Qt::transparent);
  232. const auto progress = _animation.value(0.);
  233. const auto revProgress = 1. - progress;
  234. const auto divider = 1. - kSurroundingProgress;
  235. const auto alpha = (divider - revProgress) / divider;
  236. p.setOpacity(alpha);
  237. const auto scale = anim::interpolateF(_minScale, 1., progress);
  238. p.translate(
  239. revProgress * OffsetMid(size.width() + offset.x(), _minScale),
  240. revProgress * OffsetMid(size.height() + offset.y(), _minScale));
  241. p.scale(scale, scale);
  242. const auto currentView = maybeView();
  243. if (!checkView(currentView)) {
  244. return;
  245. }
  246. auto context = _toInfo.paintContext();
  247. context.skipDrawingParts = Context::SkipDrawingParts::Content;
  248. context.outbg = currentView->hasOutLayout();
  249. currentView->media()->draw(p, context);
  250. }, _surrounding->lifetime());
  251. }
  252. void Content::createBubble() {
  253. _bubble.widget = base::make_unique_q<Ui::RpWidget>(parentWidget());
  254. _bubble.widget->setAttribute(Qt::WA_TransparentForMouseEvents);
  255. const auto currentView = maybeView();
  256. if (!checkView(currentView)) {
  257. return;
  258. }
  259. const auto innerGeometry = currentView->innerGeometry();
  260. const auto tailWidth = st::historyBubbleTailOutLeft.width();
  261. _bubble.offsetFromContent = QPoint(
  262. currentView->hasOutLayout() ? 0 : tailWidth,
  263. innerGeometry.y());
  264. const auto scaleOffset = QPoint(0, innerGeometry.y());
  265. const auto paintOffsetLeft = innerGeometry.x()
  266. - _bubble.offsetFromContent.x();
  267. const auto hasCommentsButton = currentView->data()->repliesAreComments()
  268. || currentView->data()->externalReply();
  269. _bubble.widget->resize(innerGeometry.size()
  270. + QSize(
  271. currentView->hasOutLayout() ? tailWidth : 0,
  272. hasCommentsButton ? innerGeometry.y() : 0));
  273. _bubble.widget->show();
  274. _bubble.widget->stackUnder(this);
  275. _bubble.widget->paintRequest(
  276. ) | rpl::start_with_next([=](const QRect &r) {
  277. Painter p(_bubble.widget);
  278. p.fillRect(r, Qt::transparent);
  279. const auto progress = _animation.value(0.);
  280. const auto revProgress = 1. - progress;
  281. const auto divider = 1. - kSurroundingProgress;
  282. const auto alpha = (divider - revProgress) / divider;
  283. p.setOpacity(alpha);
  284. const auto scale = anim::interpolateF(_minScale, 1., progress);
  285. p.translate(
  286. revProgress * OffsetMid(width() + scaleOffset.x(), _minScale),
  287. revProgress * OffsetMid(height() + scaleOffset.y(), _minScale));
  288. p.scale(scale, scale);
  289. const auto currentView = maybeView();
  290. if (!checkView(currentView)) {
  291. return;
  292. }
  293. auto context = _toInfo.paintContext();
  294. context.skipDrawingParts = Context::SkipDrawingParts::Content;
  295. context.outbg = currentView->hasOutLayout();
  296. context.translate(paintOffsetLeft, 0);
  297. p.translate(-paintOffsetLeft, 0);
  298. currentView->draw(p, context);
  299. }, _bubble.widget->lifetime());
  300. }
  301. } // namespace
  302. MessageSendingAnimationController::MessageSendingAnimationController(
  303. not_null<Window::SessionController*> controller)
  304. : _controller(controller) {
  305. subscribeToDestructions();
  306. }
  307. void MessageSendingAnimationController::subscribeToDestructions() {
  308. _controller->session().data().itemIdChanged(
  309. ) | rpl::start_with_next([=](Data::Session::IdChange change) {
  310. _itemSendPending.remove(change.oldId);
  311. }, _lifetime);
  312. _controller->session().data().itemRemoved(
  313. ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
  314. _itemSendPending.remove(item->id);
  315. _processing.remove(item);
  316. }, _lifetime);
  317. }
  318. void MessageSendingAnimationController::appendSending(
  319. MessageSendingAnimationFrom from) {
  320. if (anim::Disabled()) {
  321. return;
  322. }
  323. if (from.localId) {
  324. _itemSendPending[*from.localId] = std::move(from);
  325. }
  326. }
  327. void MessageSendingAnimationController::startAnimation(SendingInfoTo &&to) {
  328. if (anim::Disabled()) {
  329. return;
  330. }
  331. const auto container = _controller->content();
  332. const auto item = to.view()->data();
  333. const auto it = _itemSendPending.find(item->fullId().msg);
  334. if (it == end(_itemSendPending)) {
  335. return;
  336. }
  337. auto from = std::move(it->second);
  338. _itemSendPending.erase(it);
  339. auto content = base::make_unique_q<Content>(
  340. container,
  341. _controller,
  342. std::move(from),
  343. std::move(to));
  344. content->destroyRequests(
  345. ) | rpl::start_with_next([=] {
  346. _processing.erase(item);
  347. }, content->lifetime());
  348. _processing.emplace(item, std::move(content));
  349. }
  350. bool MessageSendingAnimationController::hasAnimatedMessage(
  351. not_null<HistoryItem*> item) const {
  352. return _processing.contains(item);
  353. }
  354. void MessageSendingAnimationController::clear() {
  355. _itemSendPending.clear();
  356. _processing.clear();
  357. }
  358. bool MessageSendingAnimationController::checkExpectedType(
  359. not_null<HistoryItem*> item) {
  360. const auto it = _itemSendPending.find(item->id);
  361. if (it == end(_itemSendPending)) {
  362. return false;
  363. }
  364. const auto type = it->second.type;
  365. const auto isSticker = type == MessageSendingAnimationFrom::Type::Sticker;
  366. const auto isGif = type == MessageSendingAnimationFrom::Type::Gif;
  367. if (isSticker || isGif) {
  368. if (item->emptyText()) {
  369. if (const auto media = item->media()) {
  370. if (const auto document = media->document()) {
  371. if ((isGif && document->isGifv())
  372. || (isSticker && document->sticker())) {
  373. return true;
  374. }
  375. }
  376. }
  377. }
  378. }
  379. _itemSendPending.erase(it);
  380. return false;
  381. }
  382. } // namespace Ui