ttl_media_layer_widget.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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 "chat_helpers/ttl_media_layer_widget.h"
  8. #include "base/event_filter.h"
  9. #include "data/data_document.h"
  10. #include "data/data_session.h"
  11. #include "editor/editor_layer_widget.h"
  12. #include "history/history.h"
  13. #include "history/history_item.h"
  14. #include "history/view/history_view_element.h"
  15. #include "history/view/media/history_view_document.h"
  16. #include "lang/lang_keys.h"
  17. #include "main/main_session.h"
  18. #include "mainwidget.h"
  19. #include "media/audio/media_audio.h"
  20. #include "media/player/media_player_instance.h"
  21. #include "ui/chat/chat_style.h"
  22. #include "ui/chat/chat_theme.h"
  23. #include "ui/effects/path_shift_gradient.h"
  24. #include "ui/painter.h"
  25. #include "ui/rect.h"
  26. #include "ui/text/text_utilities.h"
  27. #include "ui/widgets/buttons.h"
  28. #include "ui/widgets/labels.h"
  29. #include "ui/widgets/tooltip.h"
  30. #include "ui/ui_utility.h"
  31. #include "window/section_widget.h" // Window::ChatThemeValueFromPeer.
  32. #include "window/themes/window_theme.h"
  33. #include "window/window_controller.h"
  34. #include "window/window_session_controller.h"
  35. #include "styles/style_chat.h"
  36. #include "styles/style_chat_helpers.h"
  37. #include "styles/style_dialogs.h"
  38. namespace ChatHelpers {
  39. namespace {
  40. class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
  41. public:
  42. PreviewDelegate(
  43. not_null<QWidget*> parent,
  44. not_null<Ui::ChatStyle*> st,
  45. rpl::producer<bool> chatWideValue,
  46. Fn<void()> update);
  47. bool elementAnimationsPaused() override;
  48. not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
  49. HistoryView::Context elementContext() override;
  50. bool elementIsChatWide() override;
  51. private:
  52. const not_null<QWidget*> _parent;
  53. const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
  54. rpl::variable<bool> _chatWide;
  55. };
  56. PreviewDelegate::PreviewDelegate(
  57. not_null<QWidget*> parent,
  58. not_null<Ui::ChatStyle*> st,
  59. rpl::producer<bool> chatWideValue,
  60. Fn<void()> update)
  61. : _parent(parent)
  62. , _pathGradient(HistoryView::MakePathShiftGradient(st, update))
  63. , _chatWide(std::move(chatWideValue)) {
  64. }
  65. bool PreviewDelegate::elementAnimationsPaused() {
  66. return _parent->window()->isActiveWindow();
  67. }
  68. not_null<Ui::PathShiftGradient*> PreviewDelegate::elementPathShiftGradient() {
  69. return _pathGradient.get();
  70. }
  71. HistoryView::Context PreviewDelegate::elementContext() {
  72. return HistoryView::Context::TTLViewer;
  73. }
  74. bool PreviewDelegate::elementIsChatWide() {
  75. return _chatWide.current();
  76. }
  77. class PreviewWrap final : public Ui::RpWidget {
  78. public:
  79. PreviewWrap(
  80. not_null<Ui::RpWidget*> parent,
  81. not_null<HistoryItem*> item,
  82. rpl::producer<QRect> viewportValue,
  83. rpl::producer<bool> chatWideValue,
  84. rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme);
  85. ~PreviewWrap();
  86. [[nodiscard]] rpl::producer<> closeRequests() const;
  87. private:
  88. void paintEvent(QPaintEvent *e) override;
  89. void createView();
  90. [[nodiscard]] bool goodItem() const;
  91. void clear();
  92. const not_null<HistoryItem*> _item;
  93. const std::unique_ptr<Ui::ChatStyle> _style;
  94. const std::unique_ptr<PreviewDelegate> _delegate;
  95. rpl::variable<QRect> _globalViewport;
  96. rpl::variable<bool> _chatWide;
  97. std::shared_ptr<Ui::ChatTheme> _theme;
  98. std::unique_ptr<HistoryView::Element> _element;
  99. QRect _viewport;
  100. QRect _elementGeometry;
  101. rpl::variable<QRect> _elementInner;
  102. rpl::lifetime _elementLifetime;
  103. QImage _lastFrameCache;
  104. rpl::event_stream<> _closeRequests;
  105. };
  106. PreviewWrap::PreviewWrap(
  107. not_null<Ui::RpWidget*> parent,
  108. not_null<HistoryItem*> item,
  109. rpl::producer<QRect> viewportValue,
  110. rpl::producer<bool> chatWideValue,
  111. rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme)
  112. : RpWidget(parent)
  113. , _item(item)
  114. , _style(std::make_unique<Ui::ChatStyle>(
  115. item->history()->session().colorIndicesValue()))
  116. , _delegate(std::make_unique<PreviewDelegate>(
  117. parent,
  118. _style.get(),
  119. std::move(chatWideValue),
  120. [=] { update(_elementGeometry); }))
  121. , _globalViewport(std::move(viewportValue)) {
  122. const auto closeCallback = [=] { _closeRequests.fire({}); };
  123. HistoryView::TTLVoiceStops(
  124. item->fullId()
  125. ) | rpl::start_with_next([=] {
  126. _lastFrameCache = Ui::GrabWidgetToImage(this, _elementGeometry);
  127. closeCallback();
  128. }, lifetime());
  129. const auto isRound = _item
  130. && _item->media()
  131. && _item->media()->document()
  132. && _item->media()->document()->isVideoMessage();
  133. std::move(
  134. theme
  135. ) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> theme) {
  136. _theme = std::move(theme);
  137. _style->apply(_theme.get());
  138. }, lifetime());
  139. const auto session = &_item->history()->session();
  140. session->data().viewRepaintRequest(
  141. ) | rpl::start_with_next([=](not_null<const HistoryView::Element*> view) {
  142. if (view == _element.get()) {
  143. update(_elementGeometry);
  144. }
  145. }, lifetime());
  146. session->data().itemViewRefreshRequest(
  147. ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
  148. if (item == _item) {
  149. if (goodItem()) {
  150. createView();
  151. update();
  152. } else {
  153. clear();
  154. _closeRequests.fire({});
  155. }
  156. }
  157. }, lifetime());
  158. session->data().itemDataChanges(
  159. ) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
  160. if (item == _item) {
  161. _element->itemDataChanged();
  162. }
  163. }, lifetime());
  164. session->data().itemRemoved(
  165. ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
  166. if (item == _item) {
  167. _closeRequests.fire({});
  168. }
  169. }, lifetime());
  170. {
  171. const auto close = Ui::CreateChild<Ui::RoundButton>(
  172. this,
  173. item->out()
  174. ? tr::lng_close()
  175. : tr::lng_ttl_voice_close_in(),
  176. st::ttlMediaButton);
  177. close->setFullRadius(true);
  178. close->setClickedCallback(closeCallback);
  179. close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
  180. rpl::combine(
  181. sizeValue(),
  182. _elementInner.value()
  183. ) | rpl::start_with_next([=](QSize size, QRect inner) {
  184. close->moveToLeft(
  185. inner.x() + (inner.width() - close->width()) / 2,
  186. (size.height()
  187. - close->height()
  188. - st::ttlMediaButtonBottomSkip));
  189. }, close->lifetime());
  190. }
  191. QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false);
  192. createView();
  193. {
  194. auto text = item->out()
  195. ? (isRound
  196. ? tr::lng_ttl_round_tooltip_out
  197. : tr::lng_ttl_voice_tooltip_out)(
  198. lt_user,
  199. rpl::single(
  200. item->history()->peer->shortName()
  201. ) | Ui::Text::ToRichLangValue(),
  202. Ui::Text::RichLangValue)
  203. : (isRound
  204. ? tr::lng_ttl_round_tooltip_in
  205. : tr::lng_ttl_voice_tooltip_in)(Ui::Text::RichLangValue);
  206. const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
  207. this,
  208. object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
  209. this,
  210. Ui::MakeNiceTooltipLabel(
  211. parent,
  212. std::move(text),
  213. st::dialogsStoriesTooltipMaxWidth,
  214. st::ttlMediaImportantTooltipLabel),
  215. st::defaultImportantTooltip.padding),
  216. st::dialogsStoriesTooltip);
  217. tooltip->toggleFast(true);
  218. _elementInner.value(
  219. ) | rpl::filter([](const QRect &inner) {
  220. return !inner.isEmpty();
  221. }) | rpl::start_with_next([=](const QRect &inner) {
  222. tooltip->pointAt(inner, RectPart::Top, [=](QSize size) {
  223. return QPoint{
  224. inner.x() + (inner.width() - size.width()) / 2,
  225. (inner.y()
  226. - st::normalFont->height
  227. - size.height()
  228. - st::defaultImportantTooltip.padding.top()),
  229. };
  230. });
  231. }, tooltip->lifetime());
  232. }
  233. }
  234. rpl::producer<> PreviewWrap::closeRequests() const {
  235. return _closeRequests.events();
  236. }
  237. bool PreviewWrap::goodItem() const {
  238. const auto media = _item->media();
  239. if (!media || !media->ttlSeconds()) {
  240. return false;
  241. }
  242. const auto document = media->document();
  243. return document
  244. && (document->isVoiceMessage() || document->isVideoMessage());
  245. }
  246. void PreviewWrap::createView() {
  247. clear();
  248. _element = _item->createView(_delegate.get());
  249. _element->initDimensions();
  250. rpl::combine(
  251. sizeValue(),
  252. _globalViewport.value()
  253. ) | rpl::start_with_next([=](QSize outer, QRect globalViewport) {
  254. _viewport = globalViewport.isEmpty()
  255. ? rect()
  256. : mapFromGlobal(globalViewport);
  257. if (_viewport.width() < st::msgMinWidth) {
  258. return;
  259. }
  260. _element->resizeGetHeight(_viewport.width());
  261. _elementGeometry = QRect(
  262. (_viewport.width() - _element->width()) / 2,
  263. (_viewport.height() - _element->height()) / 2,
  264. _element->width(),
  265. _element->height()
  266. ).translated(_viewport.topLeft());
  267. _elementInner = _element->innerGeometry().translated(
  268. _elementGeometry.topLeft());
  269. update();
  270. }, _elementLifetime);
  271. }
  272. void PreviewWrap::clear() {
  273. _elementLifetime.destroy();
  274. _element = nullptr;
  275. }
  276. PreviewWrap::~PreviewWrap() {
  277. clear();
  278. }
  279. void PreviewWrap::paintEvent(QPaintEvent *e) {
  280. if (!_element || _elementGeometry.isEmpty()) {
  281. return;
  282. }
  283. auto p = Painter(this);
  284. p.translate(_elementGeometry.topLeft());
  285. if (!_lastFrameCache.isNull()) {
  286. p.drawImage(0, 0, _lastFrameCache);
  287. } else {
  288. auto context = _theme->preparePaintContext(
  289. _style.get(),
  290. Rect(_element->currentSize()),
  291. Rect(_element->currentSize()),
  292. !window()->isActiveWindow());
  293. context.outbg = _element->hasOutLayout();
  294. _element->draw(p, context);
  295. }
  296. }
  297. rpl::producer<QRect> GlobalViewportForWindow(
  298. not_null<Window::SessionController*> controller) {
  299. const auto delegate = controller->window().floatPlayerDelegate();
  300. return rpl::single(rpl::empty) | rpl::then(
  301. delegate->floatPlayerAreaUpdates()
  302. ) | rpl::map([=] {
  303. auto section = (Media::Player::FloatSectionDelegate*)nullptr;
  304. delegate->floatPlayerEnumerateSections([&](
  305. not_null<Media::Player::FloatSectionDelegate*> check,
  306. Window::Column column) {
  307. if ((column == Window::Column::First && !section)
  308. || column == Window::Column::Second) {
  309. section = check;
  310. }
  311. });
  312. if (section) {
  313. const auto rect = section->floatPlayerAvailableRect();
  314. if (rect.width() >= st::msgMinWidth) {
  315. return rect;
  316. }
  317. }
  318. return QRect();
  319. });
  320. }
  321. } // namespace
  322. void ShowTTLMediaLayerWidget(
  323. not_null<Window::SessionController*> controller,
  324. not_null<HistoryItem*> item) {
  325. const auto parent = controller->content();
  326. const auto show = controller->uiShow();
  327. auto preview = base::make_unique_q<PreviewWrap>(
  328. parent,
  329. item,
  330. GlobalViewportForWindow(controller),
  331. controller->adaptive().chatWideValue(),
  332. Window::ChatThemeValueFromPeer(
  333. controller,
  334. item->history()->peer));
  335. preview->closeRequests(
  336. ) | rpl::start_with_next([=] {
  337. show->hideLayer();
  338. }, preview->lifetime());
  339. auto layer = std::make_unique<Editor::LayerWidget>(
  340. parent,
  341. std::move(preview));
  342. layer->lifetime().add([] { ::Media::Player::instance()->stop(); });
  343. base::install_event_filter(layer.get(), [=](not_null<QEvent*> e) {
  344. if (e->type() == QEvent::KeyPress) {
  345. const auto k = static_cast<QKeyEvent*>(e.get());
  346. if (k->key() == Qt::Key_Escape) {
  347. show->hideLayer();
  348. }
  349. return base::EventFilterResult::Cancel;
  350. }
  351. return base::EventFilterResult::Continue;
  352. });
  353. controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
  354. }
  355. } // namespace ChatHelpers