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