media_player_panel.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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/player/media_player_panel.h"
  8. #include "media/player/media_player_instance.h"
  9. #include "info/media/info_media_list_widget.h"
  10. #include "history/history.h"
  11. #include "history/history_item.h"
  12. #include "data/data_session.h"
  13. #include "data/data_document.h"
  14. #include "data/data_channel.h"
  15. #include "data/data_chat.h"
  16. #include "ui/widgets/shadow.h"
  17. #include "ui/widgets/scroll_area.h"
  18. #include "ui/cached_round_corners.h"
  19. #include "ui/ui_utility.h"
  20. #include "mainwindow.h"
  21. #include "main/main_session.h"
  22. #include "styles/style_overview.h"
  23. #include "styles/style_media_player.h"
  24. #include "styles/style_info.h"
  25. namespace Media {
  26. namespace Player {
  27. namespace {
  28. using ListWidget = Info::Media::ListWidget;
  29. constexpr auto kPlaylistIdsLimit = 32;
  30. constexpr auto kDelayedHideTimeout = crl::time(3000);
  31. } // namespace
  32. Panel::Panel(
  33. QWidget *parent,
  34. not_null<Window::SessionController*> window)
  35. : RpWidget(parent)
  36. , AbstractController(window)
  37. , _showTimer([=] { startShow(); })
  38. , _hideTimer([=] { startHideChecked(); })
  39. , _scroll(this, st::mediaPlayerScroll) {
  40. hide();
  41. updateSize();
  42. }
  43. bool Panel::overlaps(const QRect &globalRect) {
  44. if (isHidden() || _a_appearance.animating()) return false;
  45. auto marginLeft = rtl() ? contentRight() : contentLeft();
  46. auto marginRight = rtl() ? contentLeft() : contentRight();
  47. return rect().marginsRemoved(QMargins(marginLeft, contentTop(), marginRight, contentBottom())).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
  48. }
  49. void Panel::resizeEvent(QResizeEvent *e) {
  50. updateControlsGeometry();
  51. }
  52. void Panel::listHeightUpdated(int newHeight) {
  53. if (newHeight > emptyInnerHeight()) {
  54. updateSize();
  55. } else {
  56. _hideTimer.callOnce(0);
  57. }
  58. }
  59. bool Panel::contentTooSmall() const {
  60. const auto innerHeight = _scroll->widget()
  61. ? _scroll->widget()->height()
  62. : emptyInnerHeight();
  63. return (innerHeight <= emptyInnerHeight());
  64. }
  65. int Panel::emptyInnerHeight() const {
  66. return st::infoMediaMargin.top()
  67. + st::overviewFileLayout.songPadding.top()
  68. + st::overviewFileLayout.songThumbSize
  69. + st::overviewFileLayout.songPadding.bottom()
  70. + st::infoMediaMargin.bottom();
  71. }
  72. bool Panel::preventAutoHide() const {
  73. if (const auto list = static_cast<ListWidget*>(_scroll->widget())) {
  74. return list->preventAutoHide();
  75. }
  76. return false;
  77. }
  78. void Panel::updateControlsGeometry() {
  79. const auto scrollTop = contentTop();
  80. const auto width = contentWidth();
  81. const auto scrollHeight = qMax(
  82. height() - scrollTop - contentBottom() - scrollMarginBottom(),
  83. 0);
  84. if (scrollHeight > 0) {
  85. _scroll->setGeometryToRight(contentRight(), scrollTop, width, scrollHeight);
  86. }
  87. if (const auto widget = static_cast<TWidget*>(_scroll->widget())) {
  88. widget->resizeToWidth(width);
  89. }
  90. }
  91. int Panel::bestPositionFor(int left) const {
  92. left -= contentLeft();
  93. left -= st::mediaPlayerFileLayout.songPadding.left();
  94. left -= st::mediaPlayerFileLayout.songThumbSize / 2;
  95. return left;
  96. }
  97. void Panel::scrollPlaylistToCurrentTrack() {
  98. if (const auto list = static_cast<ListWidget*>(_scroll->widget())) {
  99. const auto rect = list->getCurrentSongGeometry();
  100. _scroll->scrollToY(rect.y() - st::infoMediaMargin.top());
  101. }
  102. }
  103. void Panel::updateSize() {
  104. auto width = contentLeft() + st::mediaPlayerPanelWidth + contentRight();
  105. auto height = contentTop();
  106. auto listHeight = 0;
  107. if (auto widget = _scroll->widget()) {
  108. listHeight = widget->height();
  109. }
  110. auto scrollVisible = (listHeight > 0);
  111. auto scrollHeight = scrollVisible ? (qMin(listHeight, st::mediaPlayerListHeightMax) + st::mediaPlayerListMarginBottom) : 0;
  112. height += scrollHeight + contentBottom();
  113. resize(width, height);
  114. _scroll->setVisible(scrollVisible);
  115. }
  116. void Panel::paintEvent(QPaintEvent *e) {
  117. auto p = QPainter(this);
  118. if (!_cache.isNull()) {
  119. bool animating = _a_appearance.animating();
  120. if (animating) {
  121. p.setOpacity(_a_appearance.value(_hiding ? 0. : 1.));
  122. } else if (_hiding || isHidden()) {
  123. hideFinished();
  124. return;
  125. }
  126. p.drawPixmap(0, 0, _cache);
  127. if (!animating) {
  128. showChildren();
  129. _cache = QPixmap();
  130. }
  131. return;
  132. }
  133. // draw shadow
  134. auto shadowedRect = myrtlrect(contentLeft(), contentTop(), contentWidth(), contentHeight());
  135. auto shadowedSides = (rtl() ? RectPart::Right : RectPart::Left)
  136. | RectPart::Bottom
  137. | (rtl() ? RectPart::Left : RectPart::Right)
  138. | RectPart::Top;
  139. Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides);
  140. Ui::FillRoundRect(p, shadowedRect, st::menuBg, Ui::MenuCorners);
  141. }
  142. void Panel::enterEventHook(QEnterEvent *e) {
  143. if (_ignoringEnterEvents || contentTooSmall()) return;
  144. _hideTimer.cancel();
  145. if (_a_appearance.animating()) {
  146. startShow();
  147. } else {
  148. _showTimer.callOnce(0);
  149. }
  150. return RpWidget::enterEventHook(e);
  151. }
  152. void Panel::leaveEventHook(QEvent *e) {
  153. if (preventAutoHide()) {
  154. return;
  155. }
  156. _showTimer.cancel();
  157. if (_a_appearance.animating()) {
  158. startHide();
  159. } else {
  160. _hideTimer.callOnce(300);
  161. }
  162. return RpWidget::leaveEventHook(e);
  163. }
  164. void Panel::showFromOther() {
  165. _hideTimer.cancel();
  166. if (_a_appearance.animating()) {
  167. startShow();
  168. } else {
  169. _showTimer.callOnce(300);
  170. }
  171. }
  172. void Panel::hideFromOther() {
  173. _showTimer.cancel();
  174. if (_a_appearance.animating()) {
  175. startHide();
  176. } else {
  177. _hideTimer.callOnce(0);
  178. }
  179. }
  180. void Panel::ensureCreated() {
  181. if (_scroll->widget()) return;
  182. _refreshListLifetime = instance()->playlistChanges(
  183. AudioMsgId::Type::Song
  184. ) | rpl::start_with_next([=] {
  185. refreshList();
  186. });
  187. refreshList();
  188. macWindowDeactivateEvents(
  189. ) | rpl::filter([=] {
  190. return !isHidden();
  191. }) | rpl::start_with_next([=] {
  192. leaveEvent(nullptr);
  193. }, _refreshListLifetime);
  194. _ignoringEnterEvents = false;
  195. }
  196. void Panel::refreshList() {
  197. const auto current = instance()->current(AudioMsgId::Type::Song);
  198. const auto contextId = current.contextId();
  199. const auto peer = [&]() -> PeerData* {
  200. if (const auto document = current.audio()) {
  201. if (&document->session() != &session()) {
  202. // Different account is playing music.
  203. return nullptr;
  204. }
  205. }
  206. const auto item = contextId
  207. ? session().data().message(contextId)
  208. : nullptr;
  209. const auto media = item ? item->media() : nullptr;
  210. const auto document = media ? media->document() : nullptr;
  211. if (!document
  212. || !document->isSharedMediaMusic()
  213. || (!item->isRegular() && !item->isScheduled())) {
  214. return nullptr;
  215. }
  216. const auto result = item->history()->peer;
  217. if (const auto migrated = result->migrateTo()) {
  218. return migrated;
  219. }
  220. return result;
  221. }();
  222. const auto migrated = peer ? peer->migrateFrom() : nullptr;
  223. if (_listPeer != peer || _listMigratedPeer != migrated) {
  224. _scroll->takeWidget<QWidget>().destroy();
  225. _listPeer = _listMigratedPeer = nullptr;
  226. }
  227. if (peer && !_listPeer) {
  228. _listPeer = peer;
  229. _listMigratedPeer = migrated;
  230. auto list = object_ptr<ListWidget>(this, infoController());
  231. const auto weak = _scroll->setOwnedWidget(std::move(list));
  232. updateSize();
  233. updateControlsGeometry();
  234. weak->checkForHide(
  235. ) | rpl::start_with_next([this] {
  236. if (!rect().contains(mapFromGlobal(QCursor::pos()))) {
  237. _hideTimer.callOnce(kDelayedHideTimeout);
  238. }
  239. }, weak->lifetime());
  240. weak->heightValue(
  241. ) | rpl::start_with_next([this](int newHeight) {
  242. listHeightUpdated(newHeight);
  243. }, weak->lifetime());
  244. weak->scrollToRequests(
  245. ) | rpl::start_with_next([this](int newScrollTop) {
  246. _scroll->scrollToY(newScrollTop);
  247. }, weak->lifetime());
  248. // MSVC BUG + REGRESSION rpl::mappers::tuple :(
  249. using namespace rpl::mappers;
  250. rpl::combine(
  251. _scroll->scrollTopValue(),
  252. _scroll->heightValue()
  253. ) | rpl::start_with_next([=](int top, int height) {
  254. const auto bottom = top + height;
  255. weak->setVisibleTopBottom(top, bottom);
  256. }, weak->lifetime());
  257. auto memento = Info::Media::Memento(
  258. peer,
  259. migratedPeerId(),
  260. section().mediaType());
  261. memento.setAroundId(contextId);
  262. memento.setIdsLimit(kPlaylistIdsLimit);
  263. memento.setScrollTopItem({ contextId, peer->session().uniqueId() });
  264. memento.setScrollTopShift(-st::infoMediaMargin.top());
  265. weak->restoreState(&memento);
  266. }
  267. }
  268. void Panel::performDestroy() {
  269. if (!_scroll->widget()) return;
  270. _scroll->takeWidget<QWidget>().destroy();
  271. _listPeer = _listMigratedPeer = nullptr;
  272. _refreshListLifetime.destroy();
  273. }
  274. Info::Key Panel::key() const {
  275. return Info::Key(_listPeer);
  276. }
  277. PeerData *Panel::migrated() const {
  278. return _listMigratedPeer;
  279. }
  280. Info::Section Panel::section() const {
  281. return Info::Section(Info::Section::MediaType::MusicFile);
  282. }
  283. void Panel::startShow() {
  284. ensureCreated();
  285. if (contentTooSmall()) {
  286. return;
  287. }
  288. if (isHidden()) {
  289. scrollPlaylistToCurrentTrack();
  290. show();
  291. } else if (!_hiding) {
  292. return;
  293. }
  294. _hiding = false;
  295. startAnimation();
  296. }
  297. void Panel::hideIgnoringEnterEvents() {
  298. _ignoringEnterEvents = true;
  299. if (isHidden()) {
  300. hideFinished();
  301. } else {
  302. startHide();
  303. }
  304. }
  305. void Panel::startHideChecked() {
  306. if (!contentTooSmall() && preventAutoHide()) {
  307. return;
  308. }
  309. if (isHidden()) {
  310. hideFinished();
  311. } else {
  312. startHide();
  313. }
  314. }
  315. void Panel::startHide() {
  316. if (_hiding || isHidden()) return;
  317. _hiding = true;
  318. startAnimation();
  319. }
  320. void Panel::startAnimation() {
  321. auto from = _hiding ? 1. : 0.;
  322. auto to = _hiding ? 0. : 1.;
  323. if (_cache.isNull()) {
  324. showChildren();
  325. _cache = Ui::GrabWidget(this);
  326. }
  327. hideChildren();
  328. _a_appearance.start([this] { appearanceCallback(); }, from, to, st::defaultInnerDropdown.duration);
  329. }
  330. void Panel::appearanceCallback() {
  331. if (!_a_appearance.animating() && _hiding) {
  332. _hiding = false;
  333. hideFinished();
  334. } else {
  335. update();
  336. }
  337. }
  338. void Panel::hideFinished() {
  339. hide();
  340. _cache = QPixmap();
  341. performDestroy();
  342. }
  343. int Panel::contentLeft() const {
  344. return st::mediaPlayerPanelMarginLeft;
  345. }
  346. int Panel::contentTop() const {
  347. return st::mediaPlayerPanelMarginLeft;
  348. }
  349. int Panel::contentRight() const {
  350. return st::mediaPlayerPanelMarginLeft;
  351. }
  352. int Panel::contentBottom() const {
  353. return st::mediaPlayerPanelMarginBottom;
  354. }
  355. int Panel::scrollMarginBottom() const {
  356. return 0;// st::mediaPlayerPanelMarginBottom;
  357. }
  358. } // namespace Player
  359. } // namespace Media